mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-27 20:47:42 +03:00
Compare commits
2491 Commits
dev-killsw
...
feat/agw-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
163cb11319 | ||
|
|
71ca24d56b | ||
|
|
1d55372da4 | ||
|
|
f85b810efe | ||
|
|
70222943dd | ||
|
|
1fbd49f55b | ||
|
|
ff8ebbfdc9 | ||
|
|
7736fa1946 | ||
|
|
7e6e70d1d3 | ||
|
|
c859128cb2 | ||
|
|
5d16645b84 | ||
|
|
203a092dc9 | ||
|
|
d8b8590bc4 | ||
|
|
9b8bfaa6f8 | ||
|
|
890103a16a | ||
|
|
56ab82f87f | ||
|
|
3984acbb44 | ||
|
|
cc404378f9 | ||
|
|
594635e5cf | ||
|
|
f9b106cf5b | ||
|
|
a9861d18b7 | ||
|
|
c14138f031 | ||
|
|
60686fde24 | ||
|
|
bd0747296e | ||
|
|
ba61019a50 | ||
|
|
113f967006 | ||
|
|
bcee58b08b | ||
|
|
52de1acebf | ||
|
|
027a12a1df | ||
|
|
0a659a2d74 | ||
|
|
6f119cd083 | ||
|
|
1753aed3fc | ||
|
|
c714d98bd1 | ||
|
|
4787f3915b | ||
|
|
7a383116b2 | ||
|
|
d3de5f0f48 | ||
|
|
8749d683e3 | ||
|
|
9de9d082bc | ||
|
|
a4233fef41 | ||
|
|
4890dd1d74 | ||
|
|
564630827e | ||
|
|
fbe15d965b | ||
|
|
b29515c380 | ||
|
|
0658a8f565 | ||
|
|
482ec04b4a | ||
|
|
d40d24fcf9 | ||
|
|
fb5666057b | ||
|
|
a49892c7e7 | ||
|
|
277b295fd8 | ||
|
|
8c33779fc3 | ||
|
|
f0299ca9fe | ||
|
|
c7b1c2809f | ||
|
|
c9ed0baf3b | ||
|
|
2a3e3126ac | ||
|
|
98771027b7 | ||
|
|
0433e03bdc | ||
|
|
cb48667b91 | ||
|
|
d0a1af0381 | ||
|
|
fd0c773918 | ||
|
|
06372c8fd7 | ||
|
|
009ca981d5 | ||
|
|
c0cae0ff01 | ||
|
|
c28452a5da | ||
|
|
396ce23228 | ||
|
|
b05ee0a654 | ||
|
|
fd5051262d | ||
|
|
847bb6923b | ||
|
|
2edd7de413 | ||
|
|
f0da2b003f | ||
|
|
650c1c6ebb | ||
|
|
8dbded1624 | ||
|
|
cebfcc846e | ||
|
|
4c18ceaa50 | ||
|
|
ebe3a5dac6 | ||
|
|
92deee5f67 | ||
|
|
a75bd0cf5e | ||
|
|
46f5b3894b | ||
|
|
493ee22883 | ||
|
|
ad14847eb5 | ||
|
|
cd50e0b8a5 | ||
|
|
78f504e35c | ||
|
|
bf3d11e5c4 | ||
|
|
9a0222aee3 | ||
|
|
f0f0f7c5be | ||
|
|
36b1a863bf | ||
|
|
4103c5bbcf | ||
|
|
fa69da6d56 | ||
|
|
aaf2c9ddeb | ||
|
|
dbbc7119ec | ||
|
|
c57162c4cc | ||
|
|
40e39895c9 | ||
|
|
ec3ab2a03c | ||
|
|
ddecfcad26 | ||
|
|
67bd880cdf | ||
|
|
477afb9d85 | ||
|
|
f969fcdbb8 | ||
|
|
b0ca16d861 | ||
|
|
9963359948 | ||
|
|
ca639d293d | ||
|
|
83d045af64 | ||
|
|
aea8ff4961 | ||
|
|
1892db4375 | ||
|
|
c86a641e05 | ||
|
|
befb2bf19a | ||
|
|
7ad6bc340c | ||
|
|
9164e38c34 | ||
|
|
8f7559f01b | ||
|
|
af56200735 | ||
|
|
3874050fae | ||
|
|
3087163e34 | ||
|
|
1fa152845c | ||
|
|
50e23ef233 | ||
|
|
ea648466de | ||
|
|
b782775016 | ||
|
|
89a7fe1081 | ||
|
|
e8bb096025 | ||
|
|
fd5c7c8322 | ||
|
|
e798d0f503 | ||
|
|
bbb0abb596 | ||
|
|
0925aec86a | ||
|
|
b084c4c284 | ||
|
|
87288ebccd | ||
|
|
fcd7eadf4c | ||
|
|
0373338fb7 | ||
|
|
42f070fe9d | ||
|
|
02be6dc5f9 | ||
|
|
bfcf7f0305 | ||
|
|
2bce595ade | ||
|
|
cd1e561fd4 | ||
|
|
9bd1e6a0f5 | ||
|
|
5058c9aa6f | ||
|
|
d78416835c | ||
|
|
40e6c6aae3 | ||
|
|
911a999c64 | ||
|
|
b4f4184aa6 | ||
|
|
5c6db4b7a4 | ||
|
|
f6277cdbb2 | ||
|
|
99312e61d3 | ||
|
|
9f0ae75a2f | ||
|
|
7960d8015d | ||
|
|
5dcc64e5e5 | ||
|
|
964436ad43 | ||
|
|
4fc3900fd5 | ||
|
|
8f5e42dd61 | ||
|
|
24895752c1 | ||
|
|
87eccfb4ca | ||
|
|
a983d0504e | ||
|
|
d0b8535395 | ||
|
|
f84480cf56 | ||
|
|
de7a026ec1 | ||
|
|
a128c7d247 | ||
|
|
f316f0e25a | ||
|
|
ea5242e29b | ||
|
|
b31a62c55f | ||
|
|
02e3107a23 | ||
|
|
1862850108 | ||
|
|
f73792844c | ||
|
|
a7199ca6f5 | ||
|
|
5e757cdd3b | ||
|
|
92af1f3268 | ||
|
|
aad9d6dae2 | ||
|
|
423fe3fd4f | ||
|
|
b591dd7445 | ||
|
|
a45bb5ea4f | ||
|
|
d859b111ca | ||
|
|
52031efc48 | ||
|
|
d78202c612 | ||
|
|
6bac948633 | ||
|
|
a4c4ef71fb | ||
|
|
127f85f4f0 | ||
|
|
13d4ddd292 | ||
|
|
7265e09c85 | ||
|
|
2e629b6dac | ||
|
|
92aba49705 | ||
|
|
bec06b3a5e | ||
|
|
91cd9474ea | ||
|
|
6178b05643 | ||
|
|
46ce22b85c | ||
|
|
36edafb985 | ||
|
|
d77eaba500 | ||
|
|
6a3d43fbb0 | ||
|
|
4975955bbe | ||
|
|
8f508783e3 | ||
|
|
f50817c43c | ||
|
|
54f67b3d82 | ||
|
|
d669adb707 | ||
|
|
5103bc640e | ||
|
|
3e6f0c0342 | ||
|
|
40950b92ee | ||
|
|
ac77b4ee75 | ||
|
|
fbf652f818 | ||
|
|
bbbf4891e6 | ||
|
|
20d005d66c | ||
|
|
c81ae2b060 | ||
|
|
105c42db1c | ||
|
|
89818ff63d | ||
|
|
414c422177 | ||
|
|
b39ac8556c | ||
|
|
5e1742262d | ||
|
|
5a07a1274f | ||
|
|
7b8ff1fd6e | ||
|
|
c7221832e0 | ||
|
|
eb7d031c7d | ||
|
|
3b3a0aaceb | ||
|
|
01ec79b7d5 | ||
|
|
3d6339e2dd | ||
|
|
b4d78d865a | ||
|
|
b53cdcff08 | ||
|
|
3cc18c5807 | ||
|
|
5fdce1e49e | ||
|
|
2ee61a040b | ||
|
|
741b5cc0f9 | ||
|
|
aaf0e070dc | ||
|
|
e0e126eda8 | ||
|
|
236daf6b3b | ||
|
|
f1481b1b1f | ||
|
|
f6e7d3ccf1 | ||
|
|
a754a11913 | ||
|
|
4d25e3b6f6 | ||
|
|
1fac280497 | ||
|
|
c886c5e6a7 | ||
|
|
cd7f78b9ca | ||
|
|
a587d3230f | ||
|
|
93e7b45136 | ||
|
|
e024f71ce1 | ||
|
|
50d1be7b4a | ||
|
|
3ec6d8973b | ||
|
|
3ea47d31a9 | ||
|
|
30c8cc4548 | ||
|
|
98586d2dd9 | ||
|
|
c66d8ecca0 | ||
|
|
db535f7e7d | ||
|
|
89f30d8c31 | ||
|
|
8bce432824 | ||
|
|
f3539b2632 | ||
|
|
7a96c212f3 | ||
|
|
2d5dc54e0f | ||
|
|
cef4c262e9 | ||
|
|
34309261a8 | ||
|
|
657eeb40c7 | ||
|
|
b4938c2cc9 | ||
|
|
524fefc5cb | ||
|
|
73f13404bb | ||
|
|
5fc68cca83 | ||
|
|
fcb7b8fa8d | ||
|
|
a81e32ff95 | ||
|
|
c897052107 | ||
|
|
4d0efc7ea5 | ||
|
|
a77842c9e3 | ||
|
|
0ded9db780 | ||
|
|
58d480fcb5 | ||
|
|
7154428d26 | ||
|
|
02a52d0169 | ||
|
|
ec60764072 | ||
|
|
17d2fa5532 | ||
|
|
3ca8b534e8 | ||
|
|
e88f7c5e46 | ||
|
|
3ac5d7bd1f | ||
|
|
19cad00a00 | ||
|
|
1ea716a163 | ||
|
|
4551659c2a | ||
|
|
c568bf8c24 | ||
|
|
a412d91105 | ||
|
|
ad01f23bbe | ||
|
|
656070b132 | ||
|
|
c907f5ca36 | ||
|
|
94a13b2b54 | ||
|
|
169f11d9c7 | ||
|
|
816dc3af95 | ||
|
|
b802863de5 | ||
|
|
8dc2a4b76c | ||
|
|
beb1c6dbf2 | ||
|
|
3eb06916c7 | ||
|
|
30d0f84a4f | ||
|
|
251f2aa5db | ||
|
|
16d92ddb7c | ||
|
|
e9d4fd8482 | ||
|
|
9fdcf5ab13 | ||
|
|
a6e6de33c8 | ||
|
|
53c7fd4d81 | ||
|
|
2608ea4367 | ||
|
|
d20ed4ad01 | ||
|
|
eae2936449 | ||
|
|
da8ad1f6ba | ||
|
|
5472347969 | ||
|
|
a43f7a6926 | ||
|
|
47f917de0b | ||
|
|
dbeb7edd7a | ||
|
|
6cede712f5 | ||
|
|
d328739192 | ||
|
|
d15c0bd962 | ||
|
|
d53c794936 | ||
|
|
e5dcb25a4a | ||
|
|
f9002b4f43 | ||
|
|
0531508a75 | ||
|
|
174e85a20a | ||
|
|
e9abb6f1e2 | ||
|
|
5be44f9596 | ||
|
|
90efaaff92 | ||
|
|
99b554e7c3 | ||
|
|
ac0ce8a6f6 | ||
|
|
9f9da885b7 | ||
|
|
f51fd2bf3e | ||
|
|
c8378fd32d | ||
|
|
d767214f10 | ||
|
|
e027c504ae | ||
|
|
669a95d975 | ||
|
|
a96df5d518 | ||
|
|
c5c81735a0 | ||
|
|
c933745707 | ||
|
|
6710fd18b3 | ||
|
|
1b78a71529 | ||
|
|
1909d3c94e | ||
|
|
10a107716c | ||
|
|
5445e6637b | ||
|
|
2380cd5cfb | ||
|
|
42661618dc | ||
|
|
8a7e901d7a | ||
|
|
f8bea71716 | ||
|
|
efcc0b7efc | ||
|
|
4d17e913b5 | ||
|
|
b341934863 | ||
|
|
127f8ed3bb | ||
|
|
9dca80de18 | ||
|
|
b0a6bcc055 | ||
|
|
f0626e2eca | ||
|
|
979ab42c5a | ||
|
|
e152e84ddc | ||
|
|
2605978889 | ||
|
|
a2d30efaab | ||
|
|
d3715d00ae | ||
|
|
c37662dbe2 | ||
|
|
768ca1e73d | ||
|
|
a20516850c | ||
|
|
7a203868ec | ||
|
|
43c3ce9a6e | ||
|
|
369e08844f | ||
|
|
48a5452a65 | ||
|
|
c2f9340db6 | ||
|
|
a6508e642a | ||
|
|
a3e73797c2 | ||
|
|
df7bf204ea | ||
|
|
e16243ff55 | ||
|
|
e23cbe67ad | ||
|
|
7702f2f74c | ||
|
|
b457ef9a3f | ||
|
|
a28ed6a977 | ||
|
|
0c73682cfc | ||
|
|
7e380b6cfb | ||
|
|
63b5257986 | ||
|
|
acc4485e81 | ||
|
|
2c44999a31 | ||
|
|
e59a48f9f4 | ||
|
|
b86356b0cc | ||
|
|
f6d7552b58 | ||
|
|
5bd88ac2e9 | ||
|
|
94fa5b59f3 | ||
|
|
7169480999 | ||
|
|
c44ce0d77c | ||
|
|
7fd71a8408 | ||
|
|
68db721089 | ||
|
|
a180e12bdf | ||
|
|
f3a4a1b1be | ||
|
|
6977a8ecbc | ||
|
|
d00f64e6ad | ||
|
|
d5b3da6ba3 | ||
|
|
c245318339 | ||
|
|
b3b0fec2e1 | ||
|
|
9d571a4c71 | ||
|
|
f283858490 | ||
|
|
76fe203767 | ||
|
|
b9a47f2f50 | ||
|
|
27cb17c640 | ||
|
|
ef8fb89eb3 | ||
|
|
f1b045f8a8 | ||
|
|
050066132b | ||
|
|
2a6e6a1e24 | ||
|
|
92689d084c | ||
|
|
00f314039d | ||
|
|
fcb75e837d | ||
|
|
9fbea76b74 | ||
|
|
b3ff120bcf | ||
|
|
9dea98f020 | ||
|
|
c4701d4e7a | ||
|
|
48903ca3a1 | ||
|
|
0c9fd4aef4 | ||
|
|
b2af2e46ac | ||
|
|
efc76a0683 | ||
|
|
c4a553c166 | ||
|
|
69a00b0252 | ||
|
|
4257c08b43 | ||
|
|
c9e5b92f79 | ||
|
|
99818c2ad8 | ||
|
|
99e3afabad | ||
|
|
d3339a7f3a | ||
|
|
678bfffe49 | ||
|
|
728b48044c | ||
|
|
7ccbfa48bc | ||
|
|
83460bc29b | ||
|
|
c28e1b468a | ||
|
|
abd7fdd19c | ||
|
|
2b1ec9c693 | ||
|
|
19fcddfdaf | ||
|
|
0bca78eca9 | ||
|
|
68046a0b7c | ||
|
|
d19017f87b | ||
|
|
46536bc60a | ||
|
|
6a424e9858 | ||
|
|
8afe50cd87 | ||
|
|
48980c486e | ||
|
|
5f6cd282d3 | ||
|
|
95121c06e2 | ||
|
|
c2b17c128d | ||
|
|
eda24765e7 | ||
|
|
35e0e146e6 | ||
|
|
a5254ac238 | ||
|
|
517b5e5ca6 | ||
|
|
cfeb6cbffd | ||
|
|
c128ba981c | ||
|
|
a1ca994c8b | ||
|
|
52c12940c4 | ||
|
|
25d759374c | ||
|
|
e9250afd2b | ||
|
|
eb83086d5c | ||
|
|
9398e0e695 | ||
|
|
915c8f46c5 | ||
|
|
ec132ac96c | ||
|
|
101838404e | ||
|
|
db3164223a | ||
|
|
5a7b5d34fb | ||
|
|
9420333c76 | ||
|
|
f6403fe82e | ||
|
|
c55b025eee | ||
|
|
fc6fc26148 | ||
|
|
48b43ee102 | ||
|
|
e091020692 | ||
|
|
07baf0ed65 | ||
|
|
42d3d9b98a | ||
|
|
389c1f5327 | ||
|
|
703b9137e0 | ||
|
|
b183a3b232 | ||
|
|
f163f0fc1d | ||
|
|
3b49d5ca59 | ||
|
|
236e5ca2e3 | ||
|
|
2f6e28b980 | ||
|
|
46d96a8887 | ||
|
|
56221881da | ||
|
|
3f55f6a629 | ||
|
|
7c8ae9c311 | ||
|
|
b173dcaa17 | ||
|
|
da5fe1d766 | ||
|
|
a15ea0e8a1 | ||
|
|
fbbba648c4 | ||
|
|
f79bfa9d2e | ||
|
|
3011a0e306 | ||
|
|
76640311ab | ||
|
|
e707471b04 | ||
|
|
6425700d1c | ||
|
|
36045c6694 | ||
|
|
52ecd6899b | ||
|
|
49a6a9ed76 | ||
|
|
4869429eb6 | ||
|
|
956dd6e37a | ||
|
|
665a2911be | ||
|
|
1cfa4e0630 | ||
|
|
5bda624576 | ||
|
|
d1f0560595 | ||
|
|
df07fc1b1f | ||
|
|
8ca31e0c90 | ||
|
|
f1c6067485 | ||
|
|
ca04c63f5e | ||
|
|
89cdd2bece | ||
|
|
73d7dfa54f | ||
|
|
0a5b54a2e4 | ||
|
|
e43aa02a5b | ||
|
|
c3fb62a6ab | ||
|
|
62f3a339b7 | ||
|
|
767b14b37a | ||
|
|
e7fa160c9c | ||
|
|
7350d79c50 | ||
|
|
86f08554cd | ||
|
|
a741186c21 | ||
|
|
6acaab0ffa | ||
|
|
212e9b3a91 | ||
|
|
2bff37efae | ||
|
|
b88ab8e432 | ||
|
|
48f6cf904e | ||
|
|
367789bda2 | ||
|
|
d06924c59d | ||
|
|
2db99715b1 | ||
|
|
9688a8e52d | ||
|
|
b2c429f74d | ||
|
|
6ea6ab1bd9 | ||
|
|
c5aa070bf4 | ||
|
|
d67201ede9 | ||
|
|
4323fb2063 | ||
|
|
6d5452b8ee | ||
|
|
569d63ef0f | ||
|
|
ea910ba300 | ||
|
|
1c1e74d06f | ||
|
|
5dc16c06f1 | ||
|
|
4efaf20a1c | ||
|
|
9d96b1cd13 | ||
|
|
1d721ffb9a | ||
|
|
2130131a9d | ||
|
|
e0b091b474 | ||
|
|
8547de82ea | ||
|
|
aa871bd1c9 | ||
|
|
23806e1def | ||
|
|
31867993ce | ||
|
|
7b7a922d92 | ||
|
|
09bd958d8d | ||
|
|
576e2226fe | ||
|
|
1533270e4e | ||
|
|
e7b25719e4 | ||
|
|
7183e8541c | ||
|
|
9e71e64cbd | ||
|
|
4f3bae4a9a | ||
|
|
990059f8a6 | ||
|
|
af55af5e76 | ||
|
|
82d96a9691 | ||
|
|
9f3f215452 | ||
|
|
2dfc6a87b8 | ||
|
|
7261a86c48 | ||
|
|
2946dd2278 | ||
|
|
5065262aac | ||
|
|
4685d3b543 | ||
|
|
7a389e8755 | ||
|
|
4e5daf22a3 | ||
|
|
3bf9c10d7d | ||
|
|
2e175cb9fc | ||
|
|
823c1b5d3a | ||
|
|
92bc1a6f09 | ||
|
|
d511220f8b | ||
|
|
923e358aaa | ||
|
|
92b19eccf6 | ||
|
|
5358aaeb00 | ||
|
|
e31a2066c0 | ||
|
|
928c4f18c9 | ||
|
|
628e22869d | ||
|
|
c9cd860654 | ||
|
|
17984adae5 | ||
|
|
5601bc4fdf | ||
|
|
e14681801e | ||
|
|
f106b4d367 | ||
|
|
74802f30ed | ||
|
|
d63bf15011 | ||
|
|
60de146f03 | ||
|
|
c4f32eed31 | ||
|
|
2c9067b0de | ||
|
|
6844a2375b | ||
|
|
7b838e77a0 | ||
|
|
694e781beb | ||
|
|
399a8c6d28 | ||
|
|
dce08b3ecc | ||
|
|
2763da960f | ||
|
|
d4fff4af3c | ||
|
|
f0903c32f3 | ||
|
|
ea8875478e | ||
|
|
4c08e9f3bc | ||
|
|
e8736102bf | ||
|
|
371cadcc02 | ||
|
|
c3805195af | ||
|
|
2ef267bc44 | ||
|
|
02a98b9d68 | ||
|
|
94bae4b859 | ||
|
|
425acc5f8b | ||
|
|
bb87c0838d | ||
|
|
1542adba82 | ||
|
|
3aa8a46f6e | ||
|
|
1f08d78b43 | ||
|
|
268adfb0a1 | ||
|
|
c681611102 | ||
|
|
4fc2a23f49 | ||
|
|
23f4a6ec8e | ||
|
|
504862c2b8 | ||
|
|
a22a9448ca | ||
|
|
862e83ddf5 | ||
|
|
8735eee662 | ||
|
|
ff82cf5dc4 | ||
|
|
8648790583 | ||
|
|
b881d92a80 | ||
|
|
7ad7f31e4d | ||
|
|
138e6f70a4 | ||
|
|
6f94f4646a | ||
|
|
4a01d2cf20 | ||
|
|
8948601caa | ||
|
|
aa92ccd06d | ||
|
|
253ae75795 | ||
|
|
87cb5f620a | ||
|
|
46cd740a84 | ||
|
|
76e5039578 | ||
|
|
c6b131aa4c | ||
|
|
5e72bf945c | ||
|
|
eebf7eccec | ||
|
|
168c293bfe | ||
|
|
aae3cdcac1 | ||
|
|
96566f04ee | ||
|
|
fff15fffe2 | ||
|
|
4e5a03e7f1 | ||
|
|
7571bbc36e | ||
|
|
db4a1a62e5 | ||
|
|
581773ce03 | ||
|
|
46058f614e | ||
|
|
9cab51fb00 | ||
|
|
918be16372 | ||
|
|
175477d31f | ||
|
|
cd70b7e619 | ||
|
|
22011e263e | ||
|
|
88a2b9a07a | ||
|
|
248f487d4e | ||
|
|
572ef09296 | ||
|
|
03078236ab | ||
|
|
b39a0a1d94 | ||
|
|
e94fc688ba | ||
|
|
558f613acc | ||
|
|
d800a95a1d | ||
|
|
b8f100d4fa | ||
|
|
51618fb882 | ||
|
|
14f537ba76 | ||
|
|
3458ed78d7 | ||
|
|
4bc571f609 | ||
|
|
ee61f842e5 | ||
|
|
758b25947c | ||
|
|
b036c38981 | ||
|
|
eab2b8e45a | ||
|
|
dfdec2bf4b | ||
|
|
843156cf1b | ||
|
|
01413e5a4c | ||
|
|
743304c359 | ||
|
|
6c5d590169 | ||
|
|
a1e68f5506 | ||
|
|
424315b17f | ||
|
|
b83e74427e | ||
|
|
8fefae0325 | ||
|
|
ede633ea03 | ||
|
|
b4469064a2 | ||
|
|
393e289784 | ||
|
|
d18423ee8c | ||
|
|
3fc9edd346 | ||
|
|
1a1f75d873 | ||
|
|
df02e0bf78 | ||
|
|
264d77463d | ||
|
|
0a37ffd5e3 | ||
|
|
1343d10aa7 | ||
|
|
6f96ebd8bf | ||
|
|
cb531dacb3 | ||
|
|
dfd0b4d0e5 | ||
|
|
f978f55e7f | ||
|
|
73516c28af | ||
|
|
dc85a99e08 | ||
|
|
ef06fcb4f4 | ||
|
|
ffe2314d47 | ||
|
|
4e970322d0 | ||
|
|
8dee0d27cf | ||
|
|
c520f9a2a4 | ||
|
|
003c3a23c4 | ||
|
|
1754a82f67 | ||
|
|
3384008277 | ||
|
|
af22115706 | ||
|
|
4b114fd3b6 | ||
|
|
9d531f5d74 | ||
|
|
a5564148f5 | ||
|
|
196f7778fc | ||
|
|
de20add857 | ||
|
|
c59216b58a | ||
|
|
acf7fa261a | ||
|
|
c3eddc92bd | ||
|
|
18c74f4b02 | ||
|
|
3f90ee915d | ||
|
|
401ad0db0e | ||
|
|
5945133d30 | ||
|
|
ff4fbde0b0 | ||
|
|
74ae4f3e67 | ||
|
|
ae4b33d042 | ||
|
|
53fa280037 | ||
|
|
8ecde90bc7 | ||
|
|
34a583f272 | ||
|
|
5de4b8eeb8 | ||
|
|
aae420e469 | ||
|
|
0612f70c06 | ||
|
|
d0c82efa1c | ||
|
|
cf8492240e | ||
|
|
2bceb9f7ba | ||
|
|
760f935965 | ||
|
|
eeeb2805c5 | ||
|
|
9a592d67ad | ||
|
|
ea6618b2f6 | ||
|
|
7b092e73ad | ||
|
|
b2e25c42c7 | ||
|
|
c8dd38ac31 | ||
|
|
563ee4703f | ||
|
|
beceed81de | ||
|
|
3bf96253db | ||
|
|
da2d0ec203 | ||
|
|
008b858203 | ||
|
|
130fc8277d | ||
|
|
468d3357b8 | ||
|
|
f1271da527 | ||
|
|
249a7c7ca3 | ||
|
|
0094d0ebc4 | ||
|
|
834b504dff | ||
|
|
a516d0e757 | ||
|
|
afdfbdbc59 | ||
|
|
ef712b7054 | ||
|
|
c22f9ff08a | ||
|
|
04fb1825d5 | ||
|
|
4f8f873682 | ||
|
|
9fe75c6120 | ||
|
|
bb7e8f46cb | ||
|
|
5db0c281ee | ||
|
|
aac9bfcea6 | ||
|
|
e6ee9085a2 | ||
|
|
d62ade58a5 | ||
|
|
d8020878d5 | ||
|
|
b027fff103 | ||
|
|
a0c06048cd | ||
|
|
53746f2f66 | ||
|
|
2649dba4ad | ||
|
|
6a1e3c07b1 | ||
|
|
a365eff76f | ||
|
|
8f9acd9367 | ||
|
|
53fdf5f70d | ||
|
|
9be13ea465 | ||
|
|
871aced1d1 | ||
|
|
2254bfc128 | ||
|
|
b71dcb8dd0 | ||
|
|
33d1518fd2 | ||
|
|
ee5344a4ea | ||
|
|
abb3c918e3 | ||
|
|
ff348a348c | ||
|
|
d67c378bff | ||
|
|
d85a0439c5 | ||
|
|
9faabe9e7d | ||
|
|
5bd8c33a6d | ||
|
|
24759c92ad | ||
|
|
9e92ee020e | ||
|
|
7a4f6b628b | ||
|
|
7e2f223d7f | ||
|
|
eb48e4b668 | ||
|
|
9ace09a604 | ||
|
|
702735c2ca | ||
|
|
174f2ac3db | ||
|
|
e3b5b4a9d9 | ||
|
|
72ba012765 | ||
|
|
0f9bbcd060 | ||
|
|
a9d038d8bf | ||
|
|
54a6845315 | ||
|
|
0c7059a476 | ||
|
|
5bed92ab0b | ||
|
|
49a14785c6 | ||
|
|
2c78c06dda | ||
|
|
cf8a0efd0d | ||
|
|
5211cdd4c0 | ||
|
|
d10aa43d8b | ||
|
|
6b0f1ed429 | ||
|
|
4bde1ccb44 | ||
|
|
03c18c44e2 | ||
|
|
72ffc7ce6a | ||
|
|
87b738ef16 | ||
|
|
b868831bcb | ||
|
|
477d7214c5 | ||
|
|
f3cd3d4f06 | ||
|
|
aea4cc2389 | ||
|
|
245aa8eb8c | ||
|
|
f14a2add0f | ||
|
|
89703ba58f | ||
|
|
23715fca8b | ||
|
|
d90685600e | ||
|
|
f007e5eb5c | ||
|
|
a8ccea00c7 | ||
|
|
cd2ee00769 | ||
|
|
c98a418807 | ||
|
|
0e4ae26bae | ||
|
|
d50e7dd3f4 | ||
|
|
f0085f52eb | ||
|
|
5c19b08e5e | ||
|
|
79edbe52a3 | ||
|
|
0dd181bb5b | ||
|
|
d8682003fa | ||
|
|
f4a2cf9984 | ||
|
|
98e6358fd3 | ||
|
|
af90065d2e | ||
|
|
f372f4074b | ||
|
|
6a2e5f83a1 | ||
|
|
a2badd46c4 | ||
|
|
8623a983b8 | ||
|
|
151e662027 | ||
|
|
f588fe29db | ||
|
|
030b0351a2 | ||
|
|
d4453a5f38 | ||
|
|
2252905596 | ||
|
|
ec650a65f7 | ||
|
|
6953f8d814 | ||
|
|
624a84cbfb | ||
|
|
506d9793e1 | ||
|
|
ef52f6ab08 | ||
|
|
5312a6e885 | ||
|
|
fdd600794e | ||
|
|
7bfbdca72a | ||
|
|
a22c08a41d | ||
|
|
10ea9b418a | ||
|
|
e39efb1d68 | ||
|
|
7db84122f9 | ||
|
|
84ad167ab4 | ||
|
|
ed7e217a6b | ||
|
|
c1b0d4a4a7 | ||
|
|
2f84e24353 | ||
|
|
f73586185b | ||
|
|
653ffb9a68 | ||
|
|
fe8c2d157a | ||
|
|
86367a1276 | ||
|
|
b0fcf92ada | ||
|
|
283b6ebf81 | ||
|
|
d0a7fc5116 | ||
|
|
9851aacba7 | ||
|
|
51f9fb9e0a | ||
|
|
0325761f3e | ||
|
|
a6ca1b12da | ||
|
|
82a9e7e27d | ||
|
|
f5301e1315 | ||
|
|
adab30fc81 | ||
|
|
e7bd24f065 | ||
|
|
2ec448ba13 | ||
|
|
c6e6f2ae84 | ||
|
|
e9468a4c2f | ||
|
|
45de951897 | ||
|
|
db8d966fac | ||
|
|
6b69bc9618 | ||
|
|
0089b0b799 | ||
|
|
e4841e809b | ||
|
|
ba4237f1dd | ||
|
|
f6acec53c0 | ||
|
|
5f631eaa76 | ||
|
|
7730dd510c | ||
|
|
30bd264f17 | ||
|
|
5206665fa0 | ||
|
|
073491ccb4 | ||
|
|
561b62cd40 | ||
|
|
1284ed4d84 | ||
|
|
6f34443191 | ||
|
|
02f186c54e | ||
|
|
784c6cf585 | ||
|
|
14f132e127 | ||
|
|
9cb624e681 | ||
|
|
516e3da7e2 | ||
|
|
0e83586cae | ||
|
|
95bdae68f4 | ||
|
|
294778884b | ||
|
|
10caecbffd | ||
|
|
553a6a73dd | ||
|
|
e646b85e56 | ||
|
|
b7c513c05f | ||
|
|
9f82b4c21f | ||
|
|
02b2da38cf | ||
|
|
f51077b2be | ||
|
|
33f49bfddb | ||
|
|
9a81f13f81 | ||
|
|
915fb6759a | ||
|
|
c5a5bfde69 | ||
|
|
0a90fd110d | ||
|
|
541d6eb0b8 | ||
|
|
d443a0063d | ||
|
|
f0c6edb670 | ||
|
|
9189b53a0d | ||
|
|
fceccaefcc | ||
|
|
fbeabf43ca | ||
|
|
78c7893f90 | ||
|
|
cb9a25006c | ||
|
|
0e87550d85 | ||
|
|
dceb0ab832 | ||
|
|
a33590476a | ||
|
|
deaf618520 | ||
|
|
3d8a56d922 | ||
|
|
36af7cf471 | ||
|
|
ebd3449b4a | ||
|
|
99182f4a67 | ||
|
|
da84ba1a4d | ||
|
|
bca68fc185 | ||
|
|
59a7265bac | ||
|
|
9201ca1e03 | ||
|
|
6b6a76d2cc | ||
|
|
840c388ab9 | ||
|
|
5b4ec608c8 | ||
|
|
79ff1b81e0 | ||
|
|
ea67c01da8 | ||
|
|
1137e169ea | ||
|
|
17748cca47 | ||
|
|
080e1d98c6 | ||
|
|
ca633ae882 | ||
|
|
bb7b64fb96 | ||
|
|
bf901631bf | ||
|
|
0c0ce54b1f | ||
|
|
ee762c4cef | ||
|
|
ed9efb5a79 | ||
|
|
73eb85f2f4 | ||
|
|
cd055cff62 | ||
|
|
f8b2cce618 | ||
|
|
e648054c7a | ||
|
|
fe558163cc | ||
|
|
3883b8ff34 | ||
|
|
d286664763 | ||
|
|
b05ad2392b | ||
|
|
6dbdb85aaf | ||
|
|
26b48cfe4f | ||
|
|
2f39136143 | ||
|
|
8d0d3c5ce9 | ||
|
|
256081e4ed | ||
|
|
1dd7b0a221 | ||
|
|
82c0b28906 | ||
|
|
985fe083f0 | ||
|
|
6a0000dc4b | ||
|
|
1dd2f38066 | ||
|
|
004e1e3ca5 | ||
|
|
7c560d709b | ||
|
|
d3743ad62f | ||
|
|
ac234b77e2 | ||
|
|
9886987e68 | ||
|
|
d34cb8898f | ||
|
|
13aadbda64 | ||
|
|
c7c7c8eb01 | ||
|
|
b1e5bba33f | ||
|
|
474e7c6d62 | ||
|
|
794ec921b8 | ||
|
|
b674240362 | ||
|
|
a768c7c451 | ||
|
|
28d2a4ec2c | ||
|
|
9f1210d18f | ||
|
|
3012559627 | ||
|
|
b3ed57aee7 | ||
|
|
89d0a8107d | ||
|
|
6c0b71bd1b | ||
|
|
61abf74b2d | ||
|
|
21fdf02921 | ||
|
|
7a245d80ee | ||
|
|
da85922f23 | ||
|
|
a5356b6319 | ||
|
|
3828891b9b | ||
|
|
15d866ce04 | ||
|
|
560eb3d620 | ||
|
|
ac894254cc | ||
|
|
17e3fbde25 | ||
|
|
ee11a8410c | ||
|
|
ff5c51cfd9 | ||
|
|
b3943ae5e3 | ||
|
|
a32952fde6 | ||
|
|
9c4ee4014d | ||
|
|
dc9069f1f4 | ||
|
|
e402cacc05 | ||
|
|
a98cd248d6 | ||
|
|
00fbfb6a01 | ||
|
|
86c31c3766 | ||
|
|
698cfe910c | ||
|
|
16db23c159 | ||
|
|
b05a5ee1c6 | ||
|
|
8cb298937f | ||
|
|
68fe20ddf6 | ||
|
|
fab167bb34 | ||
|
|
f640d4b5f5 | ||
|
|
074562b141 | ||
|
|
fd030a5fd4 | ||
|
|
82fa6b13c6 | ||
|
|
bf16298c40 | ||
|
|
bcebb0a2b5 | ||
|
|
b27442cf74 | ||
|
|
92fbbd4812 | ||
|
|
321ed810e3 | ||
|
|
17ff530683 | ||
|
|
2b413736a4 | ||
|
|
a416d03614 | ||
|
|
4de9a274dd | ||
|
|
0b8f3c9d9d | ||
|
|
f3a168fd43 | ||
|
|
158c11a0ec | ||
|
|
f2d13148a3 | ||
|
|
6c8b4e1fb2 | ||
|
|
c95dffff0c | ||
|
|
6346fc04ae | ||
|
|
3a87354f8d | ||
|
|
e0863a58aa | ||
|
|
dba05aab07 | ||
|
|
fec904fd28 | ||
|
|
db321bed5d | ||
|
|
28cc0218c5 | ||
|
|
a2d5f25b58 | ||
|
|
3100160927 | ||
|
|
1c3fdd3c72 | ||
|
|
0c13eda1f5 | ||
|
|
f37b7cec87 | ||
|
|
962c75b2ca | ||
|
|
9a62e2ae41 | ||
|
|
0f00dc4e7c | ||
|
|
0f04cf6eae | ||
|
|
dc76ad820f | ||
|
|
e0cde9f138 | ||
|
|
db1dc78e38 | ||
|
|
fd98ef1250 | ||
|
|
cdf46c968a | ||
|
|
72b20ef563 | ||
|
|
30a0ac0def | ||
|
|
090208bd2c | ||
|
|
1e5c9c9c4d | ||
|
|
5918f37ffa | ||
|
|
554e1b1b91 | ||
|
|
8f510c1431 | ||
|
|
4723019624 | ||
|
|
1be9078b6c | ||
|
|
520658a295 | ||
|
|
b9ec722abb | ||
|
|
d917c798d7 | ||
|
|
0d168c039f | ||
|
|
af8f265555 | ||
|
|
c0e0d64284 | ||
|
|
9466a71141 | ||
|
|
d28a586a97 | ||
|
|
6fde0b6663 | ||
|
|
d143b9213b | ||
|
|
16433e9e46 | ||
|
|
39479d1999 | ||
|
|
a03b766e33 | ||
|
|
7df2655ba0 | ||
|
|
5c2ca9803d | ||
|
|
4e6af947fa | ||
|
|
bc9d5c8fd6 | ||
|
|
f412ac6260 | ||
|
|
26d8dfbb7f | ||
|
|
b45517bafd | ||
|
|
3edb6755b4 | ||
|
|
bda64fa391 | ||
|
|
e7040f7cc8 | ||
|
|
229970e799 | ||
|
|
7b0a9a055f | ||
|
|
06d370f716 | ||
|
|
ce0a4f1f96 | ||
|
|
3240aa3cb3 | ||
|
|
5c5935c738 | ||
|
|
00b2b1abcd | ||
|
|
e0891e1a15 | ||
|
|
f7df621c56 | ||
|
|
0b6dc5bcfc | ||
|
|
cbd6755aa5 | ||
|
|
3afbc248b1 | ||
|
|
30af81fe0a | ||
|
|
427b43c99b | ||
|
|
ed08ac6b46 | ||
|
|
1a2c1fa1b5 | ||
|
|
709fbac231 | ||
|
|
6b80a56f92 | ||
|
|
5c9d45a8a8 | ||
|
|
2edac24945 | ||
|
|
b68c9cc145 | ||
|
|
6f02d4eb62 | ||
|
|
6e60688e70 | ||
|
|
140b5c4292 | ||
|
|
8ee2ebbd20 | ||
|
|
b3eda4106d | ||
|
|
885e22be7c | ||
|
|
83ec073734 | ||
|
|
0160b0f9dc | ||
|
|
f55bd5e1a1 | ||
|
|
4d88eb8e79 | ||
|
|
874de74ac8 | ||
|
|
b3a4b34d48 | ||
|
|
f7b9d2bae7 | ||
|
|
ec4574bfcf | ||
|
|
73cfab166f | ||
|
|
0e8f85057d | ||
|
|
2e4908c557 | ||
|
|
401784414a | ||
|
|
8495124bc8 | ||
|
|
ba6ed540f5 | ||
|
|
19a60ea856 | ||
|
|
aecf1b463c | ||
|
|
ff24ba1011 | ||
|
|
802b708232 | ||
|
|
adeff3efb9 | ||
|
|
0103c1722e | ||
|
|
f9123e7b71 | ||
|
|
0f9ed4e69c | ||
|
|
9dfc95bac0 | ||
|
|
9b7914538f | ||
|
|
85c9cd260d | ||
|
|
73751cc049 | ||
|
|
2b61c48303 | ||
|
|
47b03f2bf4 | ||
|
|
3e02dfef63 | ||
|
|
4176d0130a | ||
|
|
f6175c2c69 | ||
|
|
b8b423ca19 | ||
|
|
3702d69b9d | ||
|
|
3253de9384 | ||
|
|
51070635a5 | ||
|
|
9b533f1ba6 | ||
|
|
9371dd405e | ||
|
|
27d8108e55 | ||
|
|
820a3f0b77 | ||
|
|
3d61b20271 | ||
|
|
eec81f8124 | ||
|
|
d7fbddb97f | ||
|
|
9170cb0318 | ||
|
|
76dec6fa9c | ||
|
|
4e3edcfc0a | ||
|
|
ad21d7ab64 | ||
|
|
f576eb509d | ||
|
|
33e229d0b2 | ||
|
|
78420d617b | ||
|
|
2552e33d64 | ||
|
|
301141c755 | ||
|
|
fac57ac89a | ||
|
|
da5ad0a845 | ||
|
|
60aeefe1c2 | ||
|
|
d527f6a5ce | ||
|
|
2802b42747 | ||
|
|
66c5d2f0a8 | ||
|
|
6dbf4ac62c | ||
|
|
ae2872830b | ||
|
|
6f4a3587e4 | ||
|
|
145f51906e | ||
|
|
12e72bc74b | ||
|
|
65a04799ef | ||
|
|
88cd5825d3 | ||
|
|
ecdad2a315 | ||
|
|
0398ddd6a2 | ||
|
|
50ea4d3b0f | ||
|
|
77be8169f2 | ||
|
|
7a435f76b6 | ||
|
|
42949e0dea | ||
|
|
26db423232 | ||
|
|
645cf52803 | ||
|
|
673b8ad5b2 | ||
|
|
2a03834bb2 | ||
|
|
c1b6149e49 | ||
|
|
0690d86e52 | ||
|
|
bb8a11d110 | ||
|
|
4f7ba4c9a8 | ||
|
|
aa41e4d915 | ||
|
|
bb43b5451f | ||
|
|
69dd415ab5 | ||
|
|
e605f549bd | ||
|
|
a961932b2e | ||
|
|
e8cc80f046 | ||
|
|
67f29ac483 | ||
|
|
c9bde5cdc0 | ||
|
|
e878911819 | ||
|
|
f1a0b7f0ef | ||
|
|
3e0a5104e7 | ||
|
|
8f53d563a4 | ||
|
|
414740ffb7 | ||
|
|
7437d47d92 | ||
|
|
a68f19d72f | ||
|
|
11641c5e22 | ||
|
|
8d3e21d46a | ||
|
|
5ad54bfdc1 | ||
|
|
a8f5e95fb1 | ||
|
|
a4f3d08c02 | ||
|
|
3d2174d84e | ||
|
|
12fbc7d258 | ||
|
|
164b7e2551 | ||
|
|
eafac491d8 | ||
|
|
3cfb6e968d | ||
|
|
375825125f | ||
|
|
a8520e7545 | ||
|
|
ac154cdd83 | ||
|
|
9290775ab5 | ||
|
|
d14e8cdee4 | ||
|
|
7aac9f9d0e | ||
|
|
41aaac7d32 | ||
|
|
c8d2399db9 | ||
|
|
f37c8e5fd4 | ||
|
|
9f5025c10b | ||
|
|
c27d999c41 | ||
|
|
9681bea237 | ||
|
|
8905d6352c | ||
|
|
8599b20678 | ||
|
|
491f09a51b | ||
|
|
0f1519a21f | ||
|
|
1a17f2956a | ||
|
|
d94e27bfa9 | ||
|
|
b892156092 | ||
|
|
7b1b8dc749 | ||
|
|
6d0167dcf3 | ||
|
|
0c2d661e1c | ||
|
|
95d1440d6f | ||
|
|
0cabf60dc4 | ||
|
|
836ca1cc6b | ||
|
|
be799daafe | ||
|
|
ad9d674a03 | ||
|
|
f52c3c430f | ||
|
|
a91ab0e910 | ||
|
|
39d1f1677f | ||
|
|
b0dcae3586 | ||
|
|
ed6351f8f1 | ||
|
|
e486a2fb26 | ||
|
|
195bdb947e | ||
|
|
1576aed1ea | ||
|
|
e66fbc3289 | ||
|
|
b4c89ad58f | ||
|
|
8cc5846808 | ||
|
|
2eaaf01ca1 | ||
|
|
67694c0f96 | ||
|
|
163e5b2c52 | ||
|
|
508f1d3a42 | ||
|
|
d2207a5255 | ||
|
|
bf03f5c9ae | ||
|
|
656223f57d | ||
|
|
dc6e3ec53b | ||
|
|
5835a756ce | ||
|
|
f3f98a50ed | ||
|
|
0f4bb78712 | ||
|
|
c4014518cb | ||
|
|
3605f62feb | ||
|
|
5c3e253067 | ||
|
|
8d43cee52e | ||
|
|
1e64413904 | ||
|
|
6fd1ea26ee | ||
|
|
e619fd4af9 | ||
|
|
e7658f9859 | ||
|
|
3defb09da9 | ||
|
|
a672434909 | ||
|
|
c3fdd977b1 | ||
|
|
dd233f77fc | ||
|
|
02efd9c217 | ||
|
|
20f3c0388a | ||
|
|
ef530780bd | ||
|
|
d7ec611ff4 | ||
|
|
b897e7102e | ||
|
|
1cc5c5384e | ||
|
|
db602ac65b | ||
|
|
eaa209bc3a | ||
|
|
51d4aea9e2 | ||
|
|
9738ada946 | ||
|
|
8ec105bee0 | ||
|
|
4fd0852bb3 | ||
|
|
a53e904f7b | ||
|
|
2d22b52b5d | ||
|
|
426ac49f6f | ||
|
|
c164814abd | ||
|
|
3084892ed8 | ||
|
|
9297f877c4 | ||
|
|
1bf808c9ee | ||
|
|
91f44fb394 | ||
|
|
385a52f676 | ||
|
|
8ef16781eb | ||
|
|
ad5ea1ca44 | ||
|
|
0ba5d754d5 | ||
|
|
ccdcfdce8a | ||
|
|
712fb4d0d3 | ||
|
|
de65a03998 | ||
|
|
6d6710db4a | ||
|
|
c34c3e51ea | ||
|
|
5dc3b64e0b | ||
|
|
e8ceeb6e20 | ||
|
|
d38c7ce6a5 | ||
|
|
e625543b94 | ||
|
|
679bd4e4c9 | ||
|
|
c6a312845a | ||
|
|
ef0530ec6b | ||
|
|
2c98a90d60 | ||
|
|
b90fad6664 | ||
|
|
5a5ea4a018 | ||
|
|
bc68c487ee | ||
|
|
617cdf14ad | ||
|
|
f1c970461f | ||
|
|
2fde47a86f | ||
|
|
4e5f2f44b6 | ||
|
|
e8a2e54d05 | ||
|
|
d77f3ecee8 | ||
|
|
06776ebe8f | ||
|
|
e1eec55f62 | ||
|
|
fcabf08e74 | ||
|
|
b4694313a0 | ||
|
|
abb2cae1f8 | ||
|
|
b0004fd9dc | ||
|
|
362a82f944 | ||
|
|
19fe61ed29 | ||
|
|
72de38b4fb | ||
|
|
02c0f96e5e | ||
|
|
aee82282ac | ||
|
|
8497aeeb91 | ||
|
|
5e9f688000 | ||
|
|
6a7e346695 | ||
|
|
071738116e | ||
|
|
147726ecb0 | ||
|
|
ae4ee6431d | ||
|
|
9cfcb714ae | ||
|
|
d1ccde2a4b | ||
|
|
4848091203 | ||
|
|
282f159311 | ||
|
|
4ef8c77a2d | ||
|
|
08c1cf2439 | ||
|
|
2fc33875bb | ||
|
|
9e92e4b5ff | ||
|
|
7f2cf70bf5 | ||
|
|
40725d4155 | ||
|
|
8164026891 | ||
|
|
0e23b3a1ac | ||
|
|
1739d4861e | ||
|
|
a6b6e7850d | ||
|
|
3e9dea6f07 | ||
|
|
1b37ca805f | ||
|
|
c772f56da7 | ||
|
|
bc183e39bb | ||
|
|
306d4f70a8 | ||
|
|
a386d39495 | ||
|
|
22b14dff5f | ||
|
|
e749cc7578 | ||
|
|
3e03002ead | ||
|
|
4ae9cddcce | ||
|
|
16724645ce | ||
|
|
6a12cad1c9 | ||
|
|
c15665803d | ||
|
|
97090888d5 | ||
|
|
4642308fbb | ||
|
|
59bccb1188 | ||
|
|
cd8fc007ac | ||
|
|
7cfb38307e | ||
|
|
994aa32745 | ||
|
|
f0b872e86b | ||
|
|
0c33432436 | ||
|
|
0bb4dd9442 | ||
|
|
7a54dc15da | ||
|
|
e16a1100d8 | ||
|
|
99214e22e3 | ||
|
|
c77d35a2ed | ||
|
|
d98fdbdc5c | ||
|
|
4551cf0a21 | ||
|
|
023c3474d2 | ||
|
|
2a4cefb4bf | ||
|
|
09305724fa | ||
|
|
360fda1ba7 | ||
|
|
dadf0cf96e | ||
|
|
3d60ac751e | ||
|
|
32793eef8c | ||
|
|
da1cdfd6fa | ||
|
|
58ad7dc161 | ||
|
|
0a15f44193 | ||
|
|
e1dec3c1ba | ||
|
|
7834860245 | ||
|
|
2da1025f26 | ||
|
|
78c83b2e21 | ||
|
|
414a47e2f2 | ||
|
|
6c78b4ec8f | ||
|
|
a6949bd3ae | ||
|
|
f7bed04ab2 | ||
|
|
6ec773079c | ||
|
|
366e27a321 | ||
|
|
32c304dc1b | ||
|
|
338499247d | ||
|
|
79e1761c1f | ||
|
|
4ea1a19572 | ||
|
|
e2ae341ba9 | ||
|
|
de03435bac | ||
|
|
e16c425f87 | ||
|
|
c461e00c5c | ||
|
|
fcf6bb43b7 | ||
|
|
f5f72f87a6 | ||
|
|
3340451245 | ||
|
|
c14f1b5000 | ||
|
|
a46e55d5c2 | ||
|
|
4b64bfaec0 | ||
|
|
2f0c1eeecc | ||
|
|
546d4c1d3d | ||
|
|
160d88f002 | ||
|
|
a83cd29f72 | ||
|
|
94304b5777 | ||
|
|
61ddfe01a1 | ||
|
|
00d334f704 | ||
|
|
f4a4979997 | ||
|
|
03171e4743 | ||
|
|
5369e68267 | ||
|
|
9eb23e38bd | ||
|
|
2a0166bb26 | ||
|
|
2df612ec1f | ||
|
|
36ba3758db | ||
|
|
7cc0f39d3c | ||
|
|
9cf5590371 | ||
|
|
81f835458f | ||
|
|
e01b1db706 | ||
|
|
cdb18de305 | ||
|
|
8e0eef3316 | ||
|
|
221d45f564 | ||
|
|
2a4a01a4be | ||
|
|
501670bdd2 | ||
|
|
24637a1693 | ||
|
|
7bd1340190 | ||
|
|
cb5c09d967 | ||
|
|
a01ba5909c | ||
|
|
1c4678af95 | ||
|
|
5f5435c645 | ||
|
|
2f7dc2c46c | ||
|
|
9bc1c9dd03 | ||
|
|
4c81cdb4a2 | ||
|
|
3406ffa7a2 | ||
|
|
6d05b6845e | ||
|
|
29b4966119 | ||
|
|
d0f8358431 | ||
|
|
a75bd07cd8 | ||
|
|
8c1835950b | ||
|
|
c7cd8e4c80 | ||
|
|
f65e4066e3 | ||
|
|
37c18c5d3c | ||
|
|
8885f580b2 | ||
|
|
512ac74ee6 | ||
|
|
384ce9853b | ||
|
|
b6d2030041 | ||
|
|
3ac09181c6 | ||
|
|
ffc9e5823a | ||
|
|
f5448fed59 | ||
|
|
8163e51434 | ||
|
|
3836836c72 | ||
|
|
b78bf39767 | ||
|
|
25f8283edd | ||
|
|
7c8399ce88 | ||
|
|
9fe2a1dd41 | ||
|
|
846f554157 | ||
|
|
d1f66cbf4d | ||
|
|
a4624c7377 | ||
|
|
10435cea69 | ||
|
|
ce9a23e021 | ||
|
|
4b7c8f21c2 | ||
|
|
6ddebdbbd1 | ||
|
|
d92729d346 | ||
|
|
fa06dbbd29 | ||
|
|
5d59a1a10e | ||
|
|
222a251180 | ||
|
|
9d6559f0d7 | ||
|
|
da02f49850 | ||
|
|
992961c488 | ||
|
|
0ce30a4e81 | ||
|
|
bb2d794b6f | ||
|
|
45dc302de4 | ||
|
|
4a2706a9d9 | ||
|
|
00be3c3ccc | ||
|
|
cb7fe50d46 | ||
|
|
d364dbac2c | ||
|
|
042788bec3 | ||
|
|
def261f578 | ||
|
|
c08e23085e | ||
|
|
e01dd2bf57 | ||
|
|
61396ec82e | ||
|
|
357c283437 | ||
|
|
1b38cd6ca7 | ||
|
|
bdfa8bfe5b | ||
|
|
7f2ef65fe6 | ||
|
|
102d0472c7 | ||
|
|
445fc6efb1 | ||
|
|
135726f177 | ||
|
|
671ca0a66f | ||
|
|
aa4a79934a | ||
|
|
16fc0617e4 | ||
|
|
64a2f3f8bb | ||
|
|
b7a65343af | ||
|
|
5c121ea48d | ||
|
|
673f28ed64 | ||
|
|
3fb97d16bb | ||
|
|
079c9176ef | ||
|
|
9377a0b545 | ||
|
|
1357c4a309 | ||
|
|
d77be5a244 | ||
|
|
08863edb52 | ||
|
|
3a77705142 | ||
|
|
1eafa9a38a | ||
|
|
396b7aac18 | ||
|
|
08defbbbd8 | ||
|
|
79d371fb76 | ||
|
|
9df262d502 | ||
|
|
6f392ce126 | ||
|
|
a83ec10b61 | ||
|
|
a93f75fb5a | ||
|
|
2353cc4f2c | ||
|
|
30709c66ef | ||
|
|
70e6a3d303 | ||
|
|
cc89939d05 | ||
|
|
7d4a01c757 | ||
|
|
e2d61cb518 | ||
|
|
fb1a9c9867 | ||
|
|
776ae04cbe | ||
|
|
2447ab4305 | ||
|
|
52124b15e8 | ||
|
|
b1e9e8677b | ||
|
|
617e772cc1 | ||
|
|
27f770604b | ||
|
|
5bfc581ad2 | ||
|
|
2664a52007 | ||
|
|
6dbbf1fc89 | ||
|
|
c254f2fdc4 | ||
|
|
cf450fa4e4 | ||
|
|
304f29bfac | ||
|
|
50b8b3d649 | ||
|
|
4e6c1094f3 | ||
|
|
a6134ca10f | ||
|
|
3d999a503c | ||
|
|
39c2124a26 | ||
|
|
4e3955b39d | ||
|
|
784ae0da53 | ||
|
|
eaede032b4 | ||
|
|
4ed153373f | ||
|
|
07d7fac490 | ||
|
|
5535b6a6e3 | ||
|
|
b7fbb84a58 | ||
|
|
19bd94ed02 | ||
|
|
54b45a36e1 | ||
|
|
ed1afa7549 | ||
|
|
2986a18c8f | ||
|
|
b2072c06b7 | ||
|
|
762018883f | ||
|
|
0322c01c0e | ||
|
|
16ccfb8714 | ||
|
|
68095700a2 | ||
|
|
058f8b544e | ||
|
|
4cb871849b | ||
|
|
423305c35a | ||
|
|
b55313527e | ||
|
|
af53c456ea | ||
|
|
37024eb91d | ||
|
|
ee99565b63 | ||
|
|
1a8c08799f | ||
|
|
8b08a5bee0 | ||
|
|
51497d87e0 | ||
|
|
7ede1a8d83 | ||
|
|
b4df5c076e | ||
|
|
52400252dd | ||
|
|
49923c4214 | ||
|
|
81b77c9688 | ||
|
|
7284bb54bc | ||
|
|
6afdd8375d | ||
|
|
af23d9fd14 | ||
|
|
e7aead292c | ||
|
|
fd2678ce2f | ||
|
|
a6d660e708 | ||
|
|
de35a26285 | ||
|
|
18bb045e9a | ||
|
|
9090ec54e7 | ||
|
|
6fa9994366 | ||
|
|
665f2412f1 | ||
|
|
395099aa40 | ||
|
|
97a72a9ee2 | ||
|
|
3414202b7b | ||
|
|
52e5453d56 | ||
|
|
dd039a612f | ||
|
|
f5ab034aeb | ||
|
|
893ec2d61c | ||
|
|
ff41b26e94 | ||
|
|
d4d6fbab88 | ||
|
|
e38fe871b2 | ||
|
|
152d7bc3b3 | ||
|
|
9e7cf3ccd9 | ||
|
|
f7370a0280 | ||
|
|
814c574f26 | ||
|
|
fd9f9ee178 | ||
|
|
29e8f8f5fb | ||
|
|
279692afea | ||
|
|
fd09321f8e | ||
|
|
ad236baa86 | ||
|
|
8965b1fbba | ||
|
|
9b32411659 | ||
|
|
32dda9b904 | ||
|
|
c0cb5b96bf | ||
|
|
ff60030ffb | ||
|
|
f40bf2d9ba | ||
|
|
9eebee3ce3 | ||
|
|
8a3bdf136b | ||
|
|
f62076d3fd | ||
|
|
92f4d6b392 | ||
|
|
96ffd7e147 | ||
|
|
07c38e9b6c | ||
|
|
c0aca97083 | ||
|
|
2fd25f53cc | ||
|
|
2b3383a163 | ||
|
|
421a27ceae | ||
|
|
10022451b4 | ||
|
|
3c8d923299 | ||
|
|
154044e32a | ||
|
|
bfc8c10f3d | ||
|
|
d93b5a7b5c | ||
|
|
4ae608ed93 | ||
|
|
e2aef1fc1d | ||
|
|
16cadfeae8 | ||
|
|
3c9b42b9f7 | ||
|
|
9c0f27edb4 | ||
|
|
f81ee1b267 | ||
|
|
a964d955f4 | ||
|
|
f11c65c393 | ||
|
|
f8e5e9f675 | ||
|
|
72eb36f5b3 | ||
|
|
97c0fe1ece | ||
|
|
844b552bf3 | ||
|
|
6bb85deca6 | ||
|
|
551f7616f0 | ||
|
|
285c508329 | ||
|
|
f751657903 | ||
|
|
89096554e8 | ||
|
|
4bf6cce4ba | ||
|
|
85eae0b74a | ||
|
|
0a5657738e | ||
|
|
3aa0adbf39 | ||
|
|
7dc21ce8a7 | ||
|
|
6a6b200861 | ||
|
|
1c7868312d | ||
|
|
90d1c16783 | ||
|
|
3cfca046ba | ||
|
|
85414eb65f | ||
|
|
fdff57da7c | ||
|
|
7c223feef5 | ||
|
|
3740cb2c30 | ||
|
|
b5dd48ad7b | ||
|
|
e46025739a | ||
|
|
66a3538d05 | ||
|
|
e1fa24c251 | ||
|
|
a76e22c021 | ||
|
|
c166327835 | ||
|
|
4ab006f065 | ||
|
|
7eaaef6e75 | ||
|
|
c4f94efe24 | ||
|
|
2eb729d712 | ||
|
|
0343b6cf98 | ||
|
|
7fc4ea0c68 | ||
|
|
195a3ab170 | ||
|
|
a96f485e3c | ||
|
|
1b3a32f83f | ||
|
|
cacf74af3c | ||
|
|
4e9f68acff | ||
|
|
b58295d1d6 | ||
|
|
cbcf187814 | ||
|
|
4baa003c0d | ||
|
|
8cf8c3c122 | ||
|
|
e3e2c0ab6a | ||
|
|
e8862a3811 | ||
|
|
1c1a82696b | ||
|
|
a2893bac7e | ||
|
|
0eda42f29f | ||
|
|
26f7310707 | ||
|
|
810da0db61 | ||
|
|
8f6aa950cd | ||
|
|
36a2482165 | ||
|
|
639c18395b | ||
|
|
fe08fd3f0a | ||
|
|
0822ee6b96 | ||
|
|
96fdb9241a | ||
|
|
0c6d193e0f | ||
|
|
ece15c7394 | ||
|
|
29bef052c7 | ||
|
|
3ce1e40b43 | ||
|
|
290316e23e | ||
|
|
a74736b100 | ||
|
|
3f7e7f2601 | ||
|
|
abefb011a5 | ||
|
|
259eff3fea | ||
|
|
c271235d16 | ||
|
|
7539afa91e | ||
|
|
4c79905f5b | ||
|
|
b6d020e202 | ||
|
|
5a4dec3dc5 | ||
|
|
23ad006187 | ||
|
|
f7926847ac | ||
|
|
895bd578e4 | ||
|
|
67a052b117 | ||
|
|
3e41dae3ea | ||
|
|
420c616e9d | ||
|
|
822009ec5f | ||
|
|
b5e1c78461 | ||
|
|
bd0e892447 | ||
|
|
92db453f81 | ||
|
|
0060f57b63 | ||
|
|
2c517de5bb | ||
|
|
9d5f7fdd9c | ||
|
|
ddaa5b784d | ||
|
|
fadd9ff95d | ||
|
|
a40f365a54 | ||
|
|
3964bffce4 | ||
|
|
a8deb3593b | ||
|
|
ba3ef98d1e | ||
|
|
59de0d47a3 | ||
|
|
e0d6e0117e | ||
|
|
e157160337 | ||
|
|
c7d2a3ffd4 | ||
|
|
2f897c9c98 | ||
|
|
de83db10d6 | ||
|
|
4c690dd3c6 | ||
|
|
14fa0b4fd3 | ||
|
|
f8b55eb017 | ||
|
|
2b0bccf2d8 | ||
|
|
c1c68cf72d | ||
|
|
3db5c49009 | ||
|
|
87ffaceca3 | ||
|
|
591d98d8b6 | ||
|
|
e0d93eaa9f | ||
|
|
5510ff7dce | ||
|
|
a1a6185fd6 | ||
|
|
b71b87e6e9 | ||
|
|
41cc518e20 | ||
|
|
508356898f | ||
|
|
0c40a954fa | ||
|
|
784f99a900 | ||
|
|
90ae0b3e44 | ||
|
|
520aaac31f | ||
|
|
8fe1370a74 | ||
|
|
c6ae89bf27 | ||
|
|
822ef8829e | ||
|
|
726e67ebb0 | ||
|
|
d4825b1d40 | ||
|
|
f58a16ca9d | ||
|
|
2c429fd406 | ||
|
|
ebcca0c3b8 | ||
|
|
925fd9f268 | ||
|
|
0058edc24e | ||
|
|
aa66133813 | ||
|
|
66f9a82f31 | ||
|
|
1092abe776 | ||
|
|
0411792ca5 | ||
|
|
b9a13d3a32 | ||
|
|
0a1359ed16 | ||
|
|
9bd8c774ab | ||
|
|
2943f93218 | ||
|
|
5b8a0881b7 | ||
|
|
5d677a9115 | ||
|
|
26bf9aab26 | ||
|
|
d9b4529932 | ||
|
|
35ecb8499d | ||
|
|
ed5dc7cdfd | ||
|
|
75489c00c2 | ||
|
|
3aaa7b62ef | ||
|
|
c13b9754eb | ||
|
|
a97417fd38 | ||
|
|
43261f8469 | ||
|
|
b32935dd97 | ||
|
|
35660ff5e7 | ||
|
|
9b07909ed8 | ||
|
|
b4eb317b00 | ||
|
|
464d77dfb5 | ||
|
|
a67ad12cde | ||
|
|
0dba9b8268 | ||
|
|
4a4c9cd63f | ||
|
|
d0c9c1043c | ||
|
|
795405c47d | ||
|
|
e0c2f873f5 | ||
|
|
1ec50cf6ad | ||
|
|
2ef53c6df9 | ||
|
|
249be451f7 | ||
|
|
34a228adbe | ||
|
|
9782eaaeea | ||
|
|
50d3122bee | ||
|
|
3a264e6baf | ||
|
|
1b75b68373 | ||
|
|
4224e8314b | ||
|
|
7b14ad9616 | ||
|
|
be7386f0d7 | ||
|
|
cd3263db50 | ||
|
|
3034019d5a | ||
|
|
1fd48a1cf8 | ||
|
|
c3f39ad24d | ||
|
|
68d9394d9f | ||
|
|
80fca589af | ||
|
|
420c33d3ba | ||
|
|
de0cd976de | ||
|
|
a116774104 | ||
|
|
1e180489a4 | ||
|
|
e00656d757 | ||
|
|
adcc74ac8e | ||
|
|
3e0085b4a4 | ||
|
|
17fb2a98d6 | ||
|
|
780efc2477 | ||
|
|
7f02fe4157 | ||
|
|
f722576bf2 | ||
|
|
7fecc2cf4c | ||
|
|
3519e070b4 | ||
|
|
ca6b7fbeb2 | ||
|
|
25d7e121c9 | ||
|
|
882077f0aa | ||
|
|
0479113949 | ||
|
|
b5b69ff369 | ||
|
|
f0ad76fff6 | ||
|
|
e651ea7614 | ||
|
|
acca85b99a | ||
|
|
230f44f4e6 | ||
|
|
19c42490e3 | ||
|
|
03a0e2084a | ||
|
|
116fa6777b | ||
|
|
35d4222c7a | ||
|
|
dd0de7e8be | ||
|
|
e3e7503a7c | ||
|
|
b66f4bf2be | ||
|
|
1c8dbae359 | ||
|
|
4f36349630 | ||
|
|
68b27451f2 | ||
|
|
c7acd63ea7 | ||
|
|
cfc17cf290 | ||
|
|
904e173037 | ||
|
|
8a8d38a30f | ||
|
|
a9ebf534c6 | ||
|
|
6429ff0603 | ||
|
|
f5057dfac4 | ||
|
|
00d61def0b | ||
|
|
f5a26c7116 | ||
|
|
54fba99bed | ||
|
|
7216a8b923 | ||
|
|
97e322ba22 | ||
|
|
fc603f11ce | ||
|
|
87f01007cc | ||
|
|
10bca290c3 | ||
|
|
3dabaeb2c9 | ||
|
|
cf74b879c6 | ||
|
|
0ae2a1f177 | ||
|
|
3d63d6c0f2 | ||
|
|
905a3a30f3 | ||
|
|
af29637163 | ||
|
|
7351fe9633 | ||
|
|
1a6b4a1188 | ||
|
|
ec96c1b534 | ||
|
|
8751dd3797 | ||
|
|
9a6df25280 | ||
|
|
ada8912a1f | ||
|
|
de4245025c | ||
|
|
f620f4a92e | ||
|
|
c74c5e0c6d | ||
|
|
2ca85c7829 | ||
|
|
8374404b0f | ||
|
|
c453c6c294 | ||
|
|
a5e5c3d941 | ||
|
|
c242a08653 | ||
|
|
f931603203 | ||
|
|
167d57408d | ||
|
|
ef03a3d9d4 | ||
|
|
ccfc9f2ad2 | ||
|
|
b3456ee96c | ||
|
|
05cd4ac14b | ||
|
|
ba2ecf9789 | ||
|
|
020e6b1cb3 | ||
|
|
2a7365730b | ||
|
|
ad77d74f4b | ||
|
|
ef8668dce4 | ||
|
|
3fb34e28a4 | ||
|
|
871214f907 | ||
|
|
7e717754a3 | ||
|
|
6e67946ae2 | ||
|
|
48d673c853 | ||
|
|
a9bb6f8099 | ||
|
|
f8ef69b88a | ||
|
|
a005ed2a84 | ||
|
|
1aa859b10d | ||
|
|
4aba34c18b | ||
|
|
5e099f522e | ||
|
|
15c33014e7 | ||
|
|
af5b9172ef | ||
|
|
fe9e813c79 | ||
|
|
51d7cdfd31 | ||
|
|
f3aef67be6 | ||
|
|
449af8060a | ||
|
|
fa19f1f5a0 | ||
|
|
9e461ef6e1 | ||
|
|
1ba301ed16 | ||
|
|
7e663b05d5 | ||
|
|
2225a735ca | ||
|
|
90c8fbb495 | ||
|
|
2b14efd3f8 | ||
|
|
3261c36f97 | ||
|
|
c48d65525a | ||
|
|
5fa6e755b2 | ||
|
|
8bbc0b9e1a | ||
|
|
3a5317f16a | ||
|
|
c0bb06bf49 | ||
|
|
8d45af2034 | ||
|
|
5f85bf62f5 | ||
|
|
8fa409d6c1 | ||
|
|
7167c2f20d | ||
|
|
20846c8eff | ||
|
|
1b44a01efd | ||
|
|
c4104dcd33 | ||
|
|
c319c3f520 | ||
|
|
85025695df | ||
|
|
f6ca22ecdd | ||
|
|
8d9594ba36 | ||
|
|
fe7c66cf1d | ||
|
|
b5aa940701 | ||
|
|
698dfb67d1 | ||
|
|
8e61d77497 | ||
|
|
38f2f1a3e4 | ||
|
|
b98c6ca5c5 | ||
|
|
5641874811 | ||
|
|
1dd34035c9 | ||
|
|
6bf170e273 | ||
|
|
084ac9b916 | ||
|
|
cebf061f85 | ||
|
|
5641db0026 | ||
|
|
a42ec8eddb | ||
|
|
ea9917dacc | ||
|
|
6272ae842c | ||
|
|
b000eda126 | ||
|
|
9962bac50b | ||
|
|
f0c3f1b9ba | ||
|
|
ec8eeef428 | ||
|
|
7eb1a4b489 | ||
|
|
23b23f0e38 | ||
|
|
cba78190a8 | ||
|
|
e097a49186 | ||
|
|
e895c784c7 | ||
|
|
7e1bcf84f0 | ||
|
|
a92f1b82d0 | ||
|
|
433e6901c6 | ||
|
|
e16d835727 | ||
|
|
a5cf2d37d4 | ||
|
|
4bd6fbd445 | ||
|
|
8f18933713 | ||
|
|
caad670dbf | ||
|
|
2d30f86cc6 | ||
|
|
1dd79d9e31 | ||
|
|
4171afe275 | ||
|
|
a287192649 | ||
|
|
f2e9631af4 | ||
|
|
c756ab70ae | ||
|
|
2e57fe0dfd | ||
|
|
be39d28be7 | ||
|
|
23a292b8ff | ||
|
|
cdbb247780 | ||
|
|
69d4f0ce10 | ||
|
|
2580475f67 | ||
|
|
138789149c | ||
|
|
1be66659d3 | ||
|
|
6d3ba0ae72 | ||
|
|
04f61677d7 | ||
|
|
0096e65fe6 | ||
|
|
b870306c5d | ||
|
|
480b2181f0 | ||
|
|
ddc3fe7807 | ||
|
|
a86e8659f7 | ||
|
|
ae10dd639b | ||
|
|
a0141624b9 | ||
|
|
dc137620eb | ||
|
|
e9b92b216a | ||
|
|
24bf057d17 | ||
|
|
b68c6b6807 | ||
|
|
34a268624b | ||
|
|
cdd527f3ac | ||
|
|
1f048a6d04 | ||
|
|
58cf10ffb2 | ||
|
|
1ef14e129f | ||
|
|
7c4030aaef | ||
|
|
c1bf0f8799 | ||
|
|
4b0a4aa5d2 | ||
|
|
115be88e5d | ||
|
|
268bd8f1a6 | ||
|
|
1d9ff17380 | ||
|
|
0fa0d72300 | ||
|
|
677d166b1e | ||
|
|
b5778a9cb5 | ||
|
|
b2fd94d20e | ||
|
|
16969bc39b | ||
|
|
3943314142 | ||
|
|
f7ee532add | ||
|
|
2fdab4c196 | ||
|
|
e6a95370c1 | ||
|
|
92907bced3 | ||
|
|
ca30b8b62a | ||
|
|
da9ac6ff25 | ||
|
|
475de5250e | ||
|
|
7a9860ac29 | ||
|
|
32ca02bcc7 | ||
|
|
ddefb99e5e | ||
|
|
ca4d6e0b2f | ||
|
|
c3fc3a3132 | ||
|
|
78b8a1c36f | ||
|
|
638c3492d6 | ||
|
|
4c1699935c | ||
|
|
25829451c8 | ||
|
|
74fbce8b96 | ||
|
|
542039df01 | ||
|
|
d652ecff21 | ||
|
|
00c4cf300a | ||
|
|
da47054497 | ||
|
|
051a2a3ef2 | ||
|
|
ee609f3e8f | ||
|
|
eb66354c76 | ||
|
|
c7fb7e58cf | ||
|
|
c2b02a45a2 | ||
|
|
e7ed532545 | ||
|
|
2a031320c2 | ||
|
|
d0379fdede | ||
|
|
45016b76e7 | ||
|
|
ee8cd8ef26 | ||
|
|
18d89e9cad | ||
|
|
623c8ca6b0 | ||
|
|
39736e865e | ||
|
|
8ea80a616e | ||
|
|
c5df7f9bb7 | ||
|
|
92374966fb | ||
|
|
6bebfb861c | ||
|
|
30ae13b245 | ||
|
|
891f990e35 | ||
|
|
2118327ae0 | ||
|
|
6aed2902a7 | ||
|
|
e149e32ce1 | ||
|
|
41f2af8c4e | ||
|
|
016f808345 | ||
|
|
bc69524654 | ||
|
|
3c6280c419 | ||
|
|
9d01a52a4a | ||
|
|
a8273a8fdc | ||
|
|
d6d3bf6943 | ||
|
|
f6e8346841 | ||
|
|
b9717e9894 | ||
|
|
6b3209e6ee | ||
|
|
0a6da5ead1 | ||
|
|
53441148b8 | ||
|
|
732d018819 | ||
|
|
e261a8ba21 | ||
|
|
c3c87bff74 | ||
|
|
c3f423aad5 | ||
|
|
580975fda1 | ||
|
|
9f114a1dad | ||
|
|
0ae93d0657 | ||
|
|
3a210c5bab | ||
|
|
44d2627e2a | ||
|
|
6e882760f1 | ||
|
|
ff79ffd1c8 | ||
|
|
3175b0c4ff | ||
|
|
21779395ef | ||
|
|
32cbb698ee | ||
|
|
3f99c52349 | ||
|
|
19a41b2792 | ||
|
|
599910daea | ||
|
|
bee42ea2fb | ||
|
|
221ea5ebb0 | ||
|
|
f24df9fb05 | ||
|
|
ce2a122d51 | ||
|
|
e8242aec85 | ||
|
|
6ed9dcbe93 | ||
|
|
58371e6e43 | ||
|
|
2d8957489e | ||
|
|
1fd0794bfb | ||
|
|
58da60985f | ||
|
|
6b6a3cd38e | ||
|
|
4b1df16ecf | ||
|
|
24ea686e4d | ||
|
|
a7030cdcb9 | ||
|
|
8c137ecc52 | ||
|
|
20e44aa891 | ||
|
|
118bb53c03 | ||
|
|
7c3be9f0b0 | ||
|
|
d1990a4263 | ||
|
|
396422ee3a | ||
|
|
f735b401df | ||
|
|
a42beb86c0 | ||
|
|
85020270d5 | ||
|
|
b1b9044021 | ||
|
|
8da30b216f | ||
|
|
167d3caa5d | ||
|
|
3259e6f0e8 | ||
|
|
64526c5232 | ||
|
|
d9630afafd | ||
|
|
c8c6e62aa0 | ||
|
|
7f561c30b3 | ||
|
|
1d8f342417 | ||
|
|
6ec090ea0d | ||
|
|
08a8eadb49 | ||
|
|
3e2835bef6 | ||
|
|
686fc754b2 | ||
|
|
ea17124d6d | ||
|
|
4000041308 | ||
|
|
1a50ed0316 | ||
|
|
42038cd6e7 | ||
|
|
44c5b41cc7 | ||
|
|
da7bd91514 | ||
|
|
0206623c6f | ||
|
|
3b2948d4dd | ||
|
|
918aeb670e | ||
|
|
fd9fec48a2 | ||
|
|
77e51b40b8 | ||
|
|
195663c6e3 | ||
|
|
ce4ca5c4d5 | ||
|
|
3e276c4111 | ||
|
|
41a4dc2fa2 | ||
|
|
7851047421 | ||
|
|
a30e478cbd | ||
|
|
ad4b3dfad1 | ||
|
|
ad7fc937a9 | ||
|
|
fd905ef308 | ||
|
|
3f257af7a9 | ||
|
|
030bb8fe76 | ||
|
|
131601d9d2 | ||
|
|
64317ffef5 | ||
|
|
911e65af55 | ||
|
|
82165eaf37 | ||
|
|
4a75f2ebca | ||
|
|
be39b3be8c | ||
|
|
50e8aff8fa | ||
|
|
804a790392 | ||
|
|
d04566a6c4 | ||
|
|
6c614a4b3c | ||
|
|
3277717a7f | ||
|
|
f9b2829396 | ||
|
|
81cf108471 | ||
|
|
5075fe358e | ||
|
|
c8085a368f | ||
|
|
8191c25dd7 | ||
|
|
dc4b2bd52e | ||
|
|
ca60afbcee | ||
|
|
374b74b710 | ||
|
|
ae0d3d78cd | ||
|
|
f9f197afd0 | ||
|
|
93c43ecbc3 | ||
|
|
1fd1b0388b | ||
|
|
833364a94e | ||
|
|
893c105bf2 | ||
|
|
e481bd4ec5 | ||
|
|
b0489aa61b | ||
|
|
376bc29e95 | ||
|
|
bd22e330a7 | ||
|
|
9ce7114f8c | ||
|
|
1303da1c20 | ||
|
|
c6bb33fa84 | ||
|
|
22939a6707 | ||
|
|
9222877306 | ||
|
|
5d8264e854 | ||
|
|
77124e098c | ||
|
|
bd0a70d024 | ||
|
|
914700712e | ||
|
|
80d792915a | ||
|
|
54dc363231 | ||
|
|
36c436af50 | ||
|
|
73eb9259e2 | ||
|
|
951d915326 | ||
|
|
14c25480ce | ||
|
|
77d87f8c50 | ||
|
|
ab389d4817 | ||
|
|
58b9e0cd7c | ||
|
|
ed2f5af204 | ||
|
|
5b2f1f8969 | ||
|
|
a2122cb7d7 | ||
|
|
d680702b15 | ||
|
|
d15de499dc | ||
|
|
bd382e3cc7 | ||
|
|
d417fa58ab | ||
|
|
ca9cb997b5 | ||
|
|
ebd920f3b2 | ||
|
|
a611ddea2d | ||
|
|
75614e0bbb | ||
|
|
dafef6463a | ||
|
|
61d502eea4 | ||
|
|
71aa525dfd | ||
|
|
c0e3875dfd | ||
|
|
9c188e0acd | ||
|
|
1de143362c | ||
|
|
7e37b6c151 | ||
|
|
0fa84a8b84 | ||
|
|
236ae57d01 | ||
|
|
7345f464a5 | ||
|
|
3c45f2abe2 | ||
|
|
9c5e1faf46 | ||
|
|
ce02d3a829 | ||
|
|
1b80c59e65 | ||
|
|
f20f528a11 | ||
|
|
814b66c04a | ||
|
|
00ad2e7a80 | ||
|
|
8f7e5e491f | ||
|
|
19101176a7 | ||
|
|
0bc383fec2 | ||
|
|
4df918d6a5 | ||
|
|
d3cbd8cdae | ||
|
|
0fa5dc225c | ||
|
|
b14f14c45a | ||
|
|
0851f4fdd4 | ||
|
|
807b3370e7 | ||
|
|
a0f114e15c | ||
|
|
1401fcd97d | ||
|
|
efcd291e65 | ||
|
|
f98792714e | ||
|
|
5649f85b58 | ||
|
|
cfaba932e0 | ||
|
|
b1db4e8a7a | ||
|
|
ab9c11e038 | ||
|
|
4d621dcbfe | ||
|
|
a45537cbf3 | ||
|
|
2d3ac286ac | ||
|
|
4e96b5d4a6 | ||
|
|
9294324732 | ||
|
|
b92422594b | ||
|
|
ea2637b1b4 | ||
|
|
c97b4859c1 | ||
|
|
7a2f42de30 | ||
|
|
99f5f78fc7 | ||
|
|
668f0ca675 | ||
|
|
6a89a51ea6 | ||
|
|
df6b0f3945 | ||
|
|
65cf243373 | ||
|
|
2ef7813219 | ||
|
|
216efd74bf | ||
|
|
b9d027a44a | ||
|
|
9097dd9645 | ||
|
|
d784e26913 | ||
|
|
cb69298385 | ||
|
|
a8fc42a17e | ||
|
|
005008814a | ||
|
|
ad3bbaedb3 | ||
|
|
cad0dabe42 | ||
|
|
48fa3c8aec | ||
|
|
fb585cbac0 | ||
|
|
dd1adda1a6 | ||
|
|
3742583508 | ||
|
|
3c6cd623af | ||
|
|
70c6e69b36 | ||
|
|
943e58d32a | ||
|
|
5237058016 | ||
|
|
b87c5f8a51 | ||
|
|
43e7a03af4 | ||
|
|
19fce4975d | ||
|
|
a9217810e7 | ||
|
|
a87610c856 | ||
|
|
0ec0cb1b19 | ||
|
|
9b1678a06c | ||
|
|
0da63062d7 | ||
|
|
c8dd12eb20 | ||
|
|
008592f13b | ||
|
|
56b9972053 | ||
|
|
1335c94bbc | ||
|
|
21a6ab369e | ||
|
|
2a80117d42 | ||
|
|
e65d312503 | ||
|
|
5fc34e643c | ||
|
|
3463a84903 | ||
|
|
48dc532de6 | ||
|
|
20cb62483f | ||
|
|
dcb5828313 | ||
|
|
74860256b9 | ||
|
|
97457f17c1 | ||
|
|
de9167cae6 | ||
|
|
2e5171c205 | ||
|
|
a0b5491178 | ||
|
|
fc8ef8678b | ||
|
|
2dc1f2ea5b | ||
|
|
7999527582 | ||
|
|
14be6506ee | ||
|
|
c85ad470ba | ||
|
|
8b6afcc5ec | ||
|
|
813dced6df | ||
|
|
0a39866045 | ||
|
|
48734689d8 | ||
|
|
dfe927dcbd | ||
|
|
ca158def31 | ||
|
|
409386336d | ||
|
|
eeed6c3474 | ||
|
|
3296cd6c39 | ||
|
|
60e25d9c67 | ||
|
|
ec65e2025e | ||
|
|
f09020c3bc | ||
|
|
91baaed96d | ||
|
|
64de64ce33 | ||
|
|
b685e7008e | ||
|
|
375b3d776c | ||
|
|
c1fddd7164 | ||
|
|
3af9bf9e12 | ||
|
|
9ef5d0c144 | ||
|
|
3d1b37dc85 | ||
|
|
80ce8347f6 | ||
|
|
3eb7e1392d | ||
|
|
ac78a44d74 | ||
|
|
87fed9fde3 | ||
|
|
2564430046 | ||
|
|
663861dc09 | ||
|
|
2d6a12101e | ||
|
|
e40110fa4c | ||
|
|
6ac162f3cd | ||
|
|
aea1d16e31 | ||
|
|
16e26ef215 | ||
|
|
7270e701d4 | ||
|
|
c8010d4d52 | ||
|
|
6941b7463e | ||
|
|
99a6cd82b2 | ||
|
|
1a44307664 | ||
|
|
53d7a92a0d | ||
|
|
4ba1f47423 | ||
|
|
c38e47b726 | ||
|
|
e6a4d79b86 | ||
|
|
b62d0697be | ||
|
|
f90ebbbb4e | ||
|
|
ab39802512 | ||
|
|
84da67adda | ||
|
|
cfff3c6d97 | ||
|
|
cc3d9e0d2d | ||
|
|
442e7eb015 | ||
|
|
59248b7c2e | ||
|
|
f2d7a45b74 | ||
|
|
c79b6147ea | ||
|
|
d93be76505 | ||
|
|
5fff65db5a | ||
|
|
53e240add7 | ||
|
|
9cfc65eeda | ||
|
|
47c305d557 | ||
|
|
c5ba89b054 | ||
|
|
de8977723a | ||
|
|
8cea93de94 | ||
|
|
e25cbe54d9 | ||
|
|
b356522f94 | ||
|
|
7a6c1de5d5 | ||
|
|
fb07adf7c1 | ||
|
|
9ceadd44c9 | ||
|
|
e1c529ab91 | ||
|
|
ce27af6083 | ||
|
|
ac7d224645 | ||
|
|
df26f492a7 | ||
|
|
448c01ca99 | ||
|
|
bd7a9e5444 | ||
|
|
1e717710b6 | ||
|
|
3cb14ad3bc | ||
|
|
4769a67936 | ||
|
|
279f866bf5 | ||
|
|
5cbe7600a6 | ||
|
|
cdb1a4c288 | ||
|
|
7d09d41a7d | ||
|
|
340bbd8727 | ||
|
|
53c916ea4f | ||
|
|
6451337274 | ||
|
|
294b75ce2d | ||
|
|
0ca4f3b104 | ||
|
|
ac74510d47 | ||
|
|
33ec69d33a | ||
|
|
f6d329ac48 | ||
|
|
a56fbeb611 | ||
|
|
8c20a67cfa | ||
|
|
fffa4fc031 | ||
|
|
3316b73ab6 | ||
|
|
211f7b7965 | ||
|
|
3a5a7bf674 | ||
|
|
7c749a964c | ||
|
|
997c8c87d0 | ||
|
|
ed6a417d7e | ||
|
|
4a54f545a4 | ||
|
|
789902b79a | ||
|
|
266859af19 | ||
|
|
9cca1a4819 | ||
|
|
aae1da3aa8 | ||
|
|
aed688224b | ||
|
|
3ce1ec708d | ||
|
|
510a564797 | ||
|
|
b36517babb | ||
|
|
415d18338e | ||
|
|
14384131f4 | ||
|
|
b3d54ce57e | ||
|
|
b5890340e3 | ||
|
|
050d987d3b | ||
|
|
c4651cd915 | ||
|
|
b5f97c0d94 | ||
|
|
35165ba2b8 | ||
|
|
aeb1dcdf15 | ||
|
|
37730744e7 | ||
|
|
f821fe0356 | ||
|
|
ba16fdb548 | ||
|
|
144e6f59c8 | ||
|
|
2e42c5e875 | ||
|
|
eec8743e2f | ||
|
|
e5e5684e2e | ||
|
|
efddc6ccec | ||
|
|
5aef46733b | ||
|
|
201782184c | ||
|
|
5f2ef046e1 | ||
|
|
42398950e4 | ||
|
|
506c8af1ea | ||
|
|
70d9c516af | ||
|
|
11ad86b9db | ||
|
|
2e9cfa6973 | ||
|
|
13e2c325e1 | ||
|
|
7e428a273f | ||
|
|
aa4bd516a9 | ||
|
|
82f8675b68 | ||
|
|
fdcb994e7a | ||
|
|
e89fa23533 | ||
|
|
21d4f0f569 | ||
|
|
625201e559 | ||
|
|
d36378fcbf | ||
|
|
929ecfd42b | ||
|
|
44496e424a | ||
|
|
1eb789e847 | ||
|
|
1ab76617df | ||
|
|
3bff653bbb | ||
|
|
4b7a8c6d6e | ||
|
|
9236be7fbd | ||
|
|
fd9b922b21 | ||
|
|
eea88f6e11 | ||
|
|
eb47c968ae | ||
|
|
560019b26c | ||
|
|
589e07f869 | ||
|
|
585de53148 | ||
|
|
1e85b25438 | ||
|
|
71b57bfed1 | ||
|
|
870cb26e01 | ||
|
|
9a180b098f | ||
|
|
06682c333f | ||
|
|
18c0aa5c81 | ||
|
|
e49b468fd5 | ||
|
|
7e5748b3a6 | ||
|
|
e99aa86863 | ||
|
|
bc6ae1d1b5 | ||
|
|
df94a87a51 | ||
|
|
9f7b2de311 | ||
|
|
dab6e10881 | ||
|
|
3b78c3a929 | ||
|
|
7029968c47 | ||
|
|
a8fe4e6aab | ||
|
|
b93dd8cb9b | ||
|
|
0ba614961d | ||
|
|
79c56d440c | ||
|
|
9601506270 | ||
|
|
9ed16b81e8 | ||
|
|
1800c1ff12 | ||
|
|
a8067bee36 | ||
|
|
f879663033 | ||
|
|
d618af19d6 | ||
|
|
8aa61bf5bc | ||
|
|
cd70f4b1c9 | ||
|
|
240e55029b | ||
|
|
9aabe7c72e | ||
|
|
6744dce57e | ||
|
|
d38e40d53f | ||
|
|
aa1b561bc0 | ||
|
|
6674b7890c | ||
|
|
a3a62165e9 | ||
|
|
8eb5a62737 | ||
|
|
c5ef462937 | ||
|
|
eca397b45f | ||
|
|
9f7a583c92 | ||
|
|
a59077b94f | ||
|
|
d61bfaa993 | ||
|
|
a17690f88b | ||
|
|
271e948c1f | ||
|
|
ac7de6213a | ||
|
|
7ff6b64742 | ||
|
|
531e0ad19d | ||
|
|
cc7112fb66 | ||
|
|
cf08af31ac | ||
|
|
1fdcf3877e | ||
|
|
0fadf035db | ||
|
|
58b1c4b511 | ||
|
|
0cc8feac57 | ||
|
|
ba1efd57a5 | ||
|
|
29656fb9a6 | ||
|
|
92d79ebeea | ||
|
|
3a69107eac | ||
|
|
4bfb528526 | ||
|
|
fe9dd1d014 | ||
|
|
4829c8b314 | ||
|
|
59b4bf5267 | ||
|
|
c19f34570d | ||
|
|
94d29796b8 | ||
|
|
e9f44ffcc6 | ||
|
|
a261ab4f0c | ||
|
|
929bcf03a0 | ||
|
|
a6a69ab1b6 | ||
|
|
39de79d3cd | ||
|
|
40ab540179 | ||
|
|
7d7b6f4475 | ||
|
|
ccdd433e35 | ||
|
|
4a6ea38ef8 | ||
|
|
b20e25f052 | ||
|
|
8591d4e96c | ||
|
|
912051637a | ||
|
|
c233f767f4 | ||
|
|
e1293c2c74 | ||
|
|
b63bf2465f | ||
|
|
505c9c6218 | ||
|
|
14350240eb | ||
|
|
cb21991efa | ||
|
|
49e58c25f2 | ||
|
|
ac6000a2ae | ||
|
|
6d1c9edc24 | ||
|
|
1a7fa3746d | ||
|
|
f7a57ffeb4 | ||
|
|
ad9d45a154 | ||
|
|
5fffb2afe3 | ||
|
|
1269114074 | ||
|
|
d24f6ae064 | ||
|
|
95fe09489c | ||
|
|
1a144da36d | ||
|
|
8e26da1759 | ||
|
|
daf53226c3 | ||
|
|
2b9e615e51 | ||
|
|
02acbecef5 | ||
|
|
8f28964ce2 | ||
|
|
495e74e2f0 | ||
|
|
543bd1a777 | ||
|
|
7c0518843f | ||
|
|
7131257354 | ||
|
|
db527be97c | ||
|
|
7c46e42820 | ||
|
|
6a98cdf974 | ||
|
|
9ca8c66c47 | ||
|
|
56754d616b | ||
|
|
39e348948c | ||
|
|
6583090d4f | ||
|
|
40996888c9 | ||
|
|
1ce26b3ada | ||
|
|
e7092ae769 | ||
|
|
b8b0ffb626 |
39
.clang-format
Normal file
39
.clang-format
Normal file
@@ -0,0 +1,39 @@
|
||||
BasedOnStyle: WebKit
|
||||
AccessModifierOffset: '-4'
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveMacros: 'true'
|
||||
AlignTrailingComments: 'true'
|
||||
AllowAllArgumentsOnNextLine: 'true'
|
||||
AllowAllParametersOfDeclarationOnNextLine: 'true'
|
||||
AllowShortBlocksOnASingleLine: 'false'
|
||||
AllowShortCaseLabelsOnASingleLine: 'true'
|
||||
AllowShortEnumsOnASingleLine: 'false'
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AlwaysBreakTemplateDeclarations: 'No'
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: true
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
ColumnLimit: '120'
|
||||
CommentPragmas: '"^!|^:"'
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
|
||||
ConstructorInitializerIndentWidth: '4'
|
||||
ContinuationIndentWidth: '8'
|
||||
IndentPPDirectives: BeforeHash
|
||||
NamespaceIndentation: All
|
||||
PenaltyExcessCharacter: '10'
|
||||
PointerAlignment: Right
|
||||
SortIncludes: 'true'
|
||||
SpaceAfterTemplateKeyword: 'false'
|
||||
Standard: Auto
|
||||
20
.clang-format-ignore
Normal file
20
.clang-format-ignore
Normal file
@@ -0,0 +1,20 @@
|
||||
/client/3rd
|
||||
/client/3rd-prebuild
|
||||
/client/android
|
||||
/client/cmake
|
||||
/client/core/utils/serialization
|
||||
/client/daemon
|
||||
/client/fonts
|
||||
/client/images
|
||||
/client/ios
|
||||
/client/mozilla
|
||||
/client/platforms/dummy
|
||||
/client/platforms/linux
|
||||
/client/platforms/macos
|
||||
/client/platforms/windows
|
||||
/client/server_scripts
|
||||
/client/translations
|
||||
/deploy
|
||||
/docs
|
||||
/metadata
|
||||
/service/src
|
||||
6
.gitattributes
vendored
Normal file
6
.gitattributes
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
deploy/data/windows/x64/tap/windows_7/OemVista.inf eol=crlf
|
||||
deploy/data/windows/x64/tap/windows_10/OemVista.inf eol=crlf
|
||||
deploy/data/windows/x32/tap/windows_7/OemVista.inf eol=crlf
|
||||
deploy/data/windows/x32/tap/windows_10/OemVista.inf eol=crlf
|
||||
client/3rd/* linguist-vendored
|
||||
client/android/gradlew.bat eol=crlf
|
||||
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Log files**
|
||||
Attach log files to help explain your problem.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows 10]
|
||||
- Version [e.g. 2.1.2]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Version [e.g. 2.1.2]
|
||||
|
||||
**Server (please complete the following information):**
|
||||
- OS: [e.g. Ubuntu 22.04]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
38
.github/actions/apple-install-cert/action.yml
vendored
Normal file
38
.github/actions/apple-install-cert/action.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# .github/actions/apple-install-cert/action.yml
|
||||
|
||||
name: Setup apple keychain
|
||||
description: Creates and configures a temporary build keychain
|
||||
|
||||
inputs:
|
||||
keychain-path:
|
||||
description: Path to the keychain
|
||||
required: true
|
||||
keychain-password:
|
||||
description: Password to the keychain
|
||||
required: true
|
||||
cert-base64:
|
||||
description: Base64-encoded certificate
|
||||
required: true
|
||||
cert-password:
|
||||
description: Certificate password
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Create keychain
|
||||
shell: bash
|
||||
env:
|
||||
KEYCHAIN_PATH: ${{ inputs.keychain-path }}
|
||||
KEYCHAIN_PASSWORD: ${{ inputs.keychain-password }}
|
||||
CERT_BASE64: ${{ inputs.cert-base64 }}
|
||||
CERT_PASSWORD: ${{ inputs.cert-password }}
|
||||
run: |
|
||||
CERT_PATH=$(mktemp /tmp/cert_XXXXXX.p12)
|
||||
trap "rm -f '$CERT_PATH'" EXIT
|
||||
|
||||
echo -n "$CERT_BASE64" | base64 --decode -o "$CERT_PATH"
|
||||
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security import "$CERT_PATH" -k "$KEYCHAIN_PATH" -P "$CERT_PASSWORD" -A -t cert -f pkcs12
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
BIN
.github/actions/apple-setup-keychain/AppleWWDRCAG3.cer
vendored
Normal file
BIN
.github/actions/apple-setup-keychain/AppleWWDRCAG3.cer
vendored
Normal file
Binary file not shown.
BIN
.github/actions/apple-setup-keychain/DeveloperIDG2CA.cer
vendored
Normal file
BIN
.github/actions/apple-setup-keychain/DeveloperIDG2CA.cer
vendored
Normal file
Binary file not shown.
57
.github/actions/apple-setup-keychain/action.yml
vendored
Normal file
57
.github/actions/apple-setup-keychain/action.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
# .github/actions/apple-setup-keychain/action.yml
|
||||
|
||||
name: Setup apple keychain
|
||||
description: Creates and configures a temporary build keychain
|
||||
|
||||
inputs:
|
||||
keychain-name:
|
||||
description: Name of the keychain
|
||||
required: false
|
||||
default: "ci-amnezia"
|
||||
keychain-password:
|
||||
description: The keychain password
|
||||
required: true
|
||||
lock-timeout:
|
||||
description: A timeout after exceeding which the keychain would be locked
|
||||
required: false
|
||||
default: "0"
|
||||
|
||||
outputs:
|
||||
keychain-path:
|
||||
description: "Full path to the keychain created"
|
||||
value: ${{ steps.setup.outputs.keychain-path }}
|
||||
keychain-name:
|
||||
description: "Actual name of the keychain created"
|
||||
value: ${{ steps.setup.outputs.keychain-name }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Setup keychain
|
||||
id: setup
|
||||
shell: bash
|
||||
env:
|
||||
KEYCHAIN_NAME: ${{ inputs.keychain-name }}
|
||||
KEYCHAIN_PASSWORD: ${{ inputs.keychain-password }}
|
||||
LOCK_TIMEOUT: ${{ inputs.lock-timeout }}
|
||||
run: |
|
||||
KEYCHAIN_PATH="$HOME/Library/Keychains/$KEYCHAIN_NAME.keychain-db"
|
||||
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
if [[ "$LOCK_TIMEOUT" == "0" ]]; then
|
||||
security set-keychain-settings "$KEYCHAIN_PATH"
|
||||
else
|
||||
security set-keychain-settings -u -t "$LOCK_TIMEOUT" "$KEYCHAIN_PATH"
|
||||
fi
|
||||
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
security import "${{ github.action_path }}/DeveloperIDG2CA.cer" -k "$KEYCHAIN_PATH" -A
|
||||
security import "${{ github.action_path }}/AppleWWDRCAG3.cer" -k "$KEYCHAIN_PATH" -A
|
||||
|
||||
security list-keychains -d user -s "$KEYCHAIN_PATH"
|
||||
security default-keychain -s "$KEYCHAIN_PATH"
|
||||
|
||||
echo "keychain-name=$KEYCHAIN_NAME" >> $GITHUB_OUTPUT
|
||||
echo "keychain-path=$KEYCHAIN_PATH" >> $GITHUB_OUTPUT
|
||||
31
.github/actions/apple-setup-provisioning-profile/action.yml
vendored
Normal file
31
.github/actions/apple-setup-provisioning-profile/action.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# .github/actions/apple-setup-provisioning-profile/action.yml
|
||||
|
||||
name: Setup provisioning profiles
|
||||
description: Decodes and installs provisioning profiles
|
||||
|
||||
inputs:
|
||||
provisioning_profile_base64:
|
||||
description: Base64-encoded provisioning profile
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Setup provisioning profile
|
||||
shell: bash
|
||||
run: |
|
||||
PROFILES_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||
TEMP_FILE=$(mktemp)
|
||||
|
||||
echo "${{ inputs.provisioning_profile_base64 }}" | base64 --decode > "$TEMP_FILE"
|
||||
|
||||
PROFILE_UUID=$(grep UUID -A1 -a "$TEMP_FILE" | grep -io "[-A-F0-9]\{36\}")
|
||||
if [[ -z "$PROFILE_UUID" ]]; then
|
||||
echo "Failed to extract UUID from provisioning profile"
|
||||
rm -f "$TEMP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$PROFILES_DIR"
|
||||
mv "$TEMP_FILE" "$PROFILES_DIR/$PROFILE_UUID.mobileprovision"
|
||||
echo "Installed profile: $PROFILE_UUID"
|
||||
923
.github/workflows/deploy.yml
vendored
Normal file
923
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,923 @@
|
||||
name: 'Deploy workflow'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
env:
|
||||
QT_MIRROR: https://mirrors.ocf.berkeley.edu/qt/ # https://download.qt.io/static/mirrorlist/
|
||||
|
||||
jobs:
|
||||
Detect-Changes:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
recipes_changed: ${{ steps.filter.outputs.recipes }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
recipes:
|
||||
- 'recipes/**'
|
||||
- 'conanfile.py'
|
||||
- '.github/workflows/deploy.yml'
|
||||
- 'cmake/conan_provider.cmake'
|
||||
- 'cmake/platform_settings.cmake'
|
||||
- 'cmake/recipes_bootstrap.cmake'
|
||||
|
||||
Bake-Prebuilts-Linux:
|
||||
runs-on: ubuntu-latest
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
shell: bash
|
||||
run: |
|
||||
for build_type in Release Debug; do
|
||||
cmake -S . -B build -DPREBUILTS_ONLY=1 "-DCMAKE_BUILD_TYPE=$build_type"
|
||||
done
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Linux-Ubuntu:
|
||||
runs-on: android-runner
|
||||
|
||||
needs: Bake-Prebuilts-Linux
|
||||
if: ${{ always() }}
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.10.1
|
||||
QIF_VERSION: 4.7
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'desktop'
|
||||
arch: 'linux_gcc_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
set-env: 'true'
|
||||
aqtversion: '==3.3.0'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Setup python'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Install system packages'
|
||||
run: sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Build project'
|
||||
shell: bash
|
||||
env:
|
||||
QT_INSTALL_DIR: ${{ runner.temp }}
|
||||
run: deploy/build.sh --generator Ninja --installer all
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/AmneziaVPN_*_linux_x64.run
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload translations artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_translations
|
||||
path: client/translations
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-Windows:
|
||||
runs-on: windows-latest
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -DPREBUILTS_ONLY=1
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
needs: Bake-Prebuilts-Windows
|
||||
if: ${{ always() }}
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.10.1
|
||||
QIF_VERSION: 4.7
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'windows'
|
||||
target: 'desktop'
|
||||
arch: 'win64_msvc2022_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
set-env: 'true'
|
||||
aqtversion: '==3.3.0'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Setup mvsc'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: 'x64'
|
||||
|
||||
- name: 'Setup .NET SDK'
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: 'Install WiX Toolset'
|
||||
shell: powershell
|
||||
run: |
|
||||
dotnet tool install --global wix --version 4.0.6
|
||||
wix extension add -g WixToolset.UI.wixext/4.0.6
|
||||
wix extension add -g WixToolset.Util.wixext/4.0.6
|
||||
wix extension list -g
|
||||
$wixBinDir = Join-Path $env:USERPROFILE ".dotnet\tools"
|
||||
echo "WIX_BIN_DIR=$wixBinDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: 'Setup python'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build project'
|
||||
shell: cmd
|
||||
env:
|
||||
QT_INSTALL_DIR: ${{ runner.temp }}
|
||||
run: |
|
||||
set WIX_ROOT_PATH="${{ env.USERPROFILE }}\.dotnet\tools"
|
||||
deploy\build.bat --installer all
|
||||
|
||||
- name: 'Upload WIX installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/AmneziaVPN_*_windows_x64.msi
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload IFW installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/AmneziaVPN_*_windows_x64.exe
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-iOS:
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: [26.0, 26.4]
|
||||
include:
|
||||
- xcode-version: 26.4
|
||||
os: macos-26
|
||||
|
||||
runs-on: ${{ matrix.os || 'macos-latest' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1 -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphoneos
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-iOS:
|
||||
runs-on: macos-latest
|
||||
|
||||
needs: Bake-Prebuilts-iOS
|
||||
if: ${{ always() }}
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.10.1
|
||||
KEYCHAIN_PASSWORD: ""
|
||||
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: ./.github/actions/apple-setup-keychain
|
||||
id: setup-keychain
|
||||
with:
|
||||
keychain-password: ${{ env.KEYCHAIN_PASSWORD }}
|
||||
|
||||
- name: 'Install signing certificate'
|
||||
uses: ./.github/actions/apple-install-cert
|
||||
with:
|
||||
keychain-path: ${{ steps.setup-keychain.outputs.keychain-path }}
|
||||
keychain-password: ${{ env.KEYCHAIN_PASSWORD }}
|
||||
cert-base64: ${{ secrets.IOS_SIGNING_CERT_BASE64 }}
|
||||
cert-password: ${{ secrets.IOS_SIGNING_CERT_PASSWORD }}
|
||||
|
||||
- name: 'Install app provisioning profile'
|
||||
uses: ./.github/actions/apple-setup-provisioning-profile
|
||||
with:
|
||||
provisioning_profile_base64: ${{ secrets.IOS_APP_PROVISIONING_PROFILE }}
|
||||
|
||||
- name: 'Install NE provisioning profile'
|
||||
uses: ./.github/actions/apple-setup-provisioning-profile
|
||||
with:
|
||||
provisioning_profile_base64: ${{ secrets.IOS_NE_PROVISIONING_PROFILE }}
|
||||
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '26.0'
|
||||
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
||||
arch: 'clang_64'
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: 'true'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install iOS Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'ios'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Setup python'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install deps'
|
||||
run: pip install "conan==2.28.0" jsonschema jinja2
|
||||
|
||||
- name: 'Build project'
|
||||
env:
|
||||
QT_INSTALL_DIR: ${{ runner.temp }}
|
||||
APPSTORE_CONNECT_KEY_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ID }}
|
||||
APPSTORE_CONNECT_ISSUER_ID: ${{ secrets.APPSTORE_CONNECT_ISSUER_ID }}
|
||||
APPSTORE_CONNECT_PRIVATE_KEY: ${{ secrets.APPSTORE_CONNECT_PRIVATE_KEY }}
|
||||
run: |
|
||||
sh deploy/build.sh -t ios | \
|
||||
sed -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-DPROD_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DPROD_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-DDEV_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DDEV_AGW_PUBLIC_KEY/d'
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-MacOS:
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: [16.2, 16.4, 26.4]
|
||||
include:
|
||||
- xcode-version: 26.4
|
||||
os: macos-26
|
||||
|
||||
runs-on: ${{ matrix.os || 'macos-latest' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS:
|
||||
runs-on: macos-latest
|
||||
|
||||
needs: Bake-Prebuilts-MacOS
|
||||
if: ${{ always() }}
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.10.1
|
||||
KEYCHAIN_PASSWORD: ""
|
||||
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: ./.github/actions/apple-setup-keychain
|
||||
id: setup-keychain
|
||||
with:
|
||||
keychain-password: ${{ env.KEYCHAIN_PASSWORD }}
|
||||
|
||||
- uses: ./.github/actions/apple-install-cert
|
||||
with:
|
||||
keychain-path: ${{ steps.setup-keychain.outputs.keychain-path }}
|
||||
keychain-password: ${{ env.KEYCHAIN_PASSWORD }}
|
||||
cert-base64: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||
cert-password: ${{ secrets.MAC_APP_CERT_PW }}
|
||||
|
||||
- uses: ./.github/actions/apple-install-cert
|
||||
with:
|
||||
keychain-path: ${{ steps.setup-keychain.outputs.keychain-path }}
|
||||
keychain-password: ${{ env.KEYCHAIN_PASSWORD }}
|
||||
cert-base64: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
|
||||
cert-password: ${{ secrets.MAC_INSTALL_CERT_PW }}
|
||||
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '16.2.0'
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
arch: 'clang_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
aqtversion: '==3.3.0'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Setup python'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build project'
|
||||
env:
|
||||
QT_INSTALL_DIR: ${{ runner.temp }}
|
||||
CODESIGN_SIGNATURE: ${{ secrets.MAC_SIGNER_ID }}
|
||||
CODESIGN_INSTALLER_SIGNATURE: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
|
||||
NOTARYTOOL_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
NOTARYTOOL_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
|
||||
NOTARYTOOL_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
|
||||
shell: bash
|
||||
run: deploy/build.sh --generator Ninja --installer all
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/AmneziaVPN_*_macos_x64.pkg
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-MacOS-NE:
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
xcode-version: [16.2, 16.4, 26.4]
|
||||
include:
|
||||
- xcode-version: 26.4
|
||||
os: macos-26
|
||||
|
||||
runs-on: ${{ matrix.os || 'macos-latest' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: ${{ matrix.xcode-version }}
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -G Xcode -DPREBUILTS_ONLY=1 -DMACOS_NE=TRUE
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS-NE:
|
||||
runs-on: macos-latest
|
||||
|
||||
needs: Bake-Prebuilts-MacOS-NE
|
||||
if: ${{ always() }}
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.10.1
|
||||
|
||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
|
||||
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
|
||||
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
|
||||
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- uses: ./.github/actions/apple-setup-provisioning-profile
|
||||
with:
|
||||
provisioning_profile_base64: ${{ secrets.MAC_APP_PROVISIONING_PROFILE }}
|
||||
|
||||
- uses: ./.github/actions/apple-setup-provisioning-profile
|
||||
with:
|
||||
provisioning_profile_base64: ${{ secrets.MAC_NE_PROVISIONING_PROFILE }}
|
||||
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '26.1'
|
||||
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
||||
arch: 'clang_64'
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: 'true'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install go'
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
cache: false
|
||||
|
||||
- name: 'Setup gomobile'
|
||||
run: |
|
||||
export PATH=$PATH:~/go/bin
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
gomobile init
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Setup python'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||
bash deploy/build_macos_ne.sh
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_unpacked
|
||||
path: deploy/build/client/AmneziaVPN.app
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Bake-Prebuilts-Android:
|
||||
runs-on: android-runner
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
env:
|
||||
ANDROID_PLATFORM: android-28
|
||||
NDK_VERSION: 27.0.11718014
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Setup Android SDK'
|
||||
uses: android-actions/setup-android@v4
|
||||
with:
|
||||
packages: "platforms;${{ env.ANDROID_PLATFORM }} ndk;${{ env.NDK_VERSION }}"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: |
|
||||
CMAKE_ANDROID_NDK="$ANDROID_HOME/ndk/$NDK_VERSION"
|
||||
args=(
|
||||
-G Ninja
|
||||
-DPREBUILTS_ONLY=1
|
||||
-DCMAKE_SYSTEM_NAME=Android
|
||||
"-DANDROID_PLATFORM=$ANDROID_PLATFORM"
|
||||
"-DCMAKE_ANDROID_NDK=$CMAKE_ANDROID_NDK"
|
||||
)
|
||||
|
||||
for abi in arm64-v8a armeabi-v7a x86 x86_64; do
|
||||
for build_type in Release Debug; do
|
||||
cmake -S . -B build_${abi//-/_} "${args[@]}" "-DCMAKE_ANDROID_ARCH_ABI=$abi" "-DCMAKE_BUILD_TYPE=$build_type"
|
||||
done
|
||||
done
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan remote login amnezia "${{ secrets.CONAN_USER }}" -p "${{ secrets.CONAN_PASSWORD }}"
|
||||
|
||||
- name: 'Upload baked prebuilts'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
run: conan upload -r amnezia "*" -c
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Android:
|
||||
runs-on: android-runner
|
||||
|
||||
needs: Bake-Prebuilts-Android
|
||||
if: ${{ always() }}
|
||||
|
||||
env:
|
||||
ANDROID_PLATFORM: android-28
|
||||
NDK_VERSION: 27.0.11718014
|
||||
QT_VERSION: 6.10.3
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'desktop'
|
||||
arch: 'linux_gcc_64'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_x86_64 Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_x86_64'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_x86 Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_x86'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_armv7 Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_armv7'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_arm64_v8a Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_arm64_v8a'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Setup Java'
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
- name: 'Setup Android SDK'
|
||||
uses: android-actions/setup-android@v4
|
||||
with:
|
||||
packages: "platforms;${{ env.ANDROID_PLATFORM }} ndk;${{ env.NDK_VERSION }}"
|
||||
|
||||
- name: 'Setup python'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: 'Install conan'
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Decode keystore secret to file'
|
||||
env:
|
||||
KEYSTORE_BASE64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_BASE64 }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo $KEYSTORE_BASE64 | base64 --decode > android.keystore
|
||||
|
||||
- name: 'Build project'
|
||||
env:
|
||||
QT_INSTALL_DIR: ${{ runner.temp }}
|
||||
QT_ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/android.keystore
|
||||
QT_ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }}
|
||||
QT_ANDROID_KEYSTORE_STORE_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }}
|
||||
shell: bash
|
||||
run: |
|
||||
deploy/build.sh -t android --sign --aab
|
||||
|
||||
VERSION=$(grep CMAKE_PROJECT_VERSION:STATIC deploy/build/CMakeCache.txt | cut -d= -f2)
|
||||
|
||||
(cd deploy/build/client/android-build && mv AmneziaVPN.apk AmneziaVPN_${VERSION}_android9+_universal.apk)
|
||||
(cd deploy/build/client/android-build/build/outputs/bundle/release && mv android-build-release.aab AmneziaVPN_${VERSION}.aab)
|
||||
|
||||
for abi in arm64-v8a armeabi-v7a x86 x86_64; do
|
||||
deploy/build.sh -t android --sign --abi ${abi} --build ./deploy/build/${abi}
|
||||
(cd deploy/build/${abi}/client/android-build && mv AmneziaVPN.apk AmneziaVPN_${VERSION}_android9+_${abi}.apk)
|
||||
done
|
||||
|
||||
- name: 'Upload universal APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload AAB'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/client/android-build/build/outputs/bundle/release/*.aab
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload arm64-v8a APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/arm64-v8a/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload armeabi-v7a APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/armeabi-v7a/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload x86 APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/x86/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload x86_64 APK'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
path: deploy/build/x86_64/client/android-build/*.apk
|
||||
archive: false
|
||||
retention-days: 7
|
||||
|
||||
Extra:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Search a corresponding PR
|
||||
uses: octokit/request-action@v2.x
|
||||
id: pull_request
|
||||
with:
|
||||
route: GET /repos/${{ github.repository }}/pulls
|
||||
head: ${{ github.repository_owner }}:${{ github.ref_name }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Add PR link to build summary
|
||||
if: ${{ fromJSON(steps.pull_request.outputs.data)[0].number != '' }}
|
||||
run: |
|
||||
echo "Pull request:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[[#${{ fromJSON(steps.pull_request.outputs.data)[0].number }}] ${{ fromJSON(steps.pull_request.outputs.data)[0].title }}](${{ fromJSON(steps.pull_request.outputs.data)[0].html_url }})" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
150
.github/workflows/tag-deploy.yml
vendored
Normal file
150
.github/workflows/tag-deploy.yml
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
name: 'Release deploy workflow'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# push:
|
||||
# tags:
|
||||
# - **
|
||||
|
||||
jobs:
|
||||
|
||||
Build-Android-Release:
|
||||
name: 'Build-Android-Release'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.4.1
|
||||
QIF_VERSION: 4.5
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'desktop'
|
||||
arch: 'gcc_64'
|
||||
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
|
||||
- name: 'Install android Qt x86_64'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_x86_64'
|
||||
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
|
||||
- name: 'Install android Qt x86'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_x86'
|
||||
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
|
||||
- name: 'Install android Qt arm_v7'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_armv7'
|
||||
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
|
||||
- name: 'Install android Qt arm_v8'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android_arm64_v8a'
|
||||
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z'
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: main
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Preparations before keystore fetching'
|
||||
run: |
|
||||
mkdir keystore
|
||||
|
||||
- name: 'Getting keystore'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: amnezia-vpn/amnezia-android-certificates
|
||||
ssh-key: ${{ secrets.ANDROID_CERTS_SSH_PRIVATE_KEY }}
|
||||
path: keystore
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Setup Java'
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
export QT_HOST_PATH="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64"
|
||||
export NDK_VERSION=23c
|
||||
export ANDROID_NDK_PLATFORM=android-23
|
||||
export ANDROID_NDK_HOME=${{ runner.temp }}/android-ndk-r${NDK_VERSION}
|
||||
export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME
|
||||
|
||||
if [ ! -f $ANDROID_NDK_ROOT/ndk-build ]; then
|
||||
wget https://dl.google.com/android/repository/android-ndk-r${NDK_VERSION}-linux.zip -qO ${{ runner.temp }}/ndk.zip &&
|
||||
unzip -q -d ${{ runner.temp }} ${{ runner.temp }}/ndk.zip ;
|
||||
fi
|
||||
|
||||
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/android_arm64_v8a/bin
|
||||
cd main
|
||||
bash deploy/build_android.sh
|
||||
|
||||
- name: 'Signing APK'
|
||||
run: |
|
||||
pwd
|
||||
|
||||
ANDROID_BUILD_TOOLS_VERSION=30.0.3
|
||||
|
||||
${ANDROID_HOME}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}/zipalign -f -v 4 AmneziaVPN-release-unsigned.apk AmneziaVPN-release-aligned.apk
|
||||
${ANDROID_HOME}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}/apksigner sign --out AmneziaVPN-release-signed.apk --ks keystore/debug.keystore --ks-key-alias ${{ secrets.DEBUG_ANDROID_KEYSTORE_KEY_ALIAS }} --ks-pass pass:${{secrets.DEBUG_ANDROID_KEYSTOTE_KEY_PASS }} AmneziaVPN-release-aligned.apk
|
||||
|
||||
- name: 'Upload'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Release APK
|
||||
path: ${{ runner.temp }}/main/AmneziaVPN-release-signed.apk
|
||||
41
.github/workflows/tag-upload.yml
vendored
Normal file
41
.github/workflows/tag-upload.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: 'Upload a new version'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
RELEASE_VERSION:
|
||||
description: 'Release version (e.g. 1.2.3.4)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
Upload-S3:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.RELEASE_VERSION }}
|
||||
sparse-checkout: |
|
||||
CMakeLists.txt
|
||||
deploy/deploy_s3.sh
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Verify git tag
|
||||
run: |
|
||||
TAG_NAME=${{ inputs.RELEASE_VERSION }}
|
||||
CMAKE_TAG=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/')
|
||||
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
|
||||
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
|
||||
else
|
||||
echo "::error::Mismatch: Git tag ($TAG_NAME) != CMakeLists.txt version ($CMAKE_TAG). Exiting with error..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Setup Rclone
|
||||
uses: AnimMouse/setup-rclone@v1
|
||||
with:
|
||||
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||
|
||||
- name: Send dist to S3
|
||||
run: bash deploy/deploy_s3.sh ${{ inputs.RELEASE_VERSION }}
|
||||
33
.gitignore
vendored
33
.gitignore
vendored
@@ -8,6 +8,8 @@ deploy/build/*
|
||||
deploy/build_32/*
|
||||
deploy/build_64/*
|
||||
winbuild*.bat
|
||||
.cache/
|
||||
.vscode/
|
||||
|
||||
|
||||
# Qt-es
|
||||
@@ -23,8 +25,14 @@ qrc_*.cpp
|
||||
ui_*.h
|
||||
Makefile*
|
||||
*build-*
|
||||
compile_commands.json
|
||||
|
||||
# fastlane
|
||||
client/fastlane/report.xml
|
||||
client/fastlane/build/*
|
||||
|
||||
# Qt-es
|
||||
client/Release-iphoneos/
|
||||
client/Debug-iphoneos/
|
||||
client/.xcode/
|
||||
client/.qmake.cache
|
||||
@@ -39,6 +47,7 @@ client/qrc_*.cpp
|
||||
client/ui_*.h
|
||||
client/ui_*.cpp
|
||||
client/Makefile*
|
||||
client/fastlane/build/
|
||||
client/*build-*
|
||||
client/AmneziaVPN.xcodeproj
|
||||
client/Debug-iphonesimulator/
|
||||
@@ -48,7 +57,7 @@ client/qmlcache_loader.cpp
|
||||
client/rep_ipc_interface_replica.h
|
||||
client/resources_qmlcache.qrc
|
||||
client/3rd/OpenVPNAdpter/build/
|
||||
|
||||
client/3rd/ShadowSocks/build/
|
||||
# QtCreator
|
||||
|
||||
*.autosave
|
||||
@@ -60,12 +69,19 @@ client/3rd/OpenVPNAdpter/build/
|
||||
# QtCtreator CMake
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# Linux files
|
||||
*.7z
|
||||
deploy/AppDir
|
||||
deploy/Tools
|
||||
deploy/AmneziaVPN*Installer*
|
||||
|
||||
# MACOS files
|
||||
.DS_Store
|
||||
client/.DS_Store
|
||||
._.DS_Store
|
||||
._*
|
||||
*.dmg
|
||||
deploy/data/macos/pf/amn.400.allowPIA.conf
|
||||
|
||||
# tmp files
|
||||
*.*~
|
||||
@@ -113,3 +129,18 @@ captures/
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
client/3rd/ShadowSocks/ss_ios.xcconfig
|
||||
|
||||
# UML generated pics
|
||||
out/
|
||||
|
||||
# CMake files
|
||||
CMakeFiles/
|
||||
|
||||
ios-ne-build.sh
|
||||
macos-ne-build.sh
|
||||
macos-signed-build.sh
|
||||
macos-with-sign-build.sh
|
||||
DeveloperIdApplicationCertificate.p12
|
||||
DeveloperIdInstallerCertificate.p12
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
variables:
|
||||
GIT_STRATEGY: clone
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
||||
build-windows:
|
||||
stage: build
|
||||
tags:
|
||||
- windows
|
||||
script:
|
||||
- cmd.exe /k "deploy\windows-env.bat && cd deploy && windows.bat"
|
||||
artifacts:
|
||||
name: artifacts-windows
|
||||
paths:
|
||||
- AmneziaVPN.exe
|
||||
|
||||
build-macos:
|
||||
stage: build
|
||||
tags:
|
||||
- macos
|
||||
script:
|
||||
- cd deploy && ./macos.sh
|
||||
artifacts:
|
||||
name: artifacts-macos
|
||||
paths:
|
||||
- AmneziaVPN.dmg
|
||||
31
.gitmodules
vendored
31
.gitmodules
vendored
@@ -1,15 +1,16 @@
|
||||
[submodule "3rd/QtSsh"]
|
||||
path = 3rd/QtSsh
|
||||
url = https://github.com/amnezia-vpn/QtSsh.git
|
||||
[submodule "client/3rd/wireguard-tools"]
|
||||
path = client/3rd/wireguard-tools
|
||||
url = https://github.com/WireGuard/wireguard-tools/
|
||||
[submodule "client/3rd/wireguard-apple"]
|
||||
path = client/3rd/wireguard-apple
|
||||
url = https://github.com/WireGuard/wireguard-apple
|
||||
[submodule "client/3rd/OpenVPNAdapter"]
|
||||
path = client/3rd/OpenVPNAdapter
|
||||
url = https://github.com/ss-abramchuk/OpenVPNAdapter.git
|
||||
[submodule "client/3rd/qzxing"]
|
||||
path = client/3rd/qzxing
|
||||
url = https://github.com/ftylitak/qzxing.git
|
||||
[submodule "client/3rd/qtkeychain"]
|
||||
path = client/3rd/qtkeychain
|
||||
url = https://github.com/frankosterfeld/qtkeychain.git
|
||||
[submodule "client/3rd/SortFilterProxyModel"]
|
||||
path = client/3rd/SortFilterProxyModel
|
||||
url = https://github.com/mitchcurtis/SortFilterProxyModel.git
|
||||
[submodule "client/3rd/amneziawg-apple"]
|
||||
path = client/3rd/amneziawg-apple
|
||||
url = https://github.com/amnezia-vpn/amneziawg-apple
|
||||
[submodule "client/3rd/QSimpleCrypto"]
|
||||
path = client/3rd/QSimpleCrypto
|
||||
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
|
||||
[submodule "client/3rd/qtgamepad"]
|
||||
path = client/3rd/qtgamepad
|
||||
url = https://github.com/amnezia-vpn/qtgamepad.git
|
||||
branch = 6.6
|
||||
|
||||
47
.gitpod.Dockerfile
vendored
Normal file
47
.gitpod.Dockerfile
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
FROM gitpod/workspace-full-vnc
|
||||
|
||||
USER gitpod
|
||||
|
||||
RUN sudo apt-get -q update \
|
||||
&& sudo apt-get install -yq \
|
||||
build-essential \
|
||||
libgl1-mesa-dev \
|
||||
libgstreamer-gl1.0-0 \
|
||||
libpulse-dev \
|
||||
libsecret-1-dev \
|
||||
libxcb-glx0 \
|
||||
libxcb-icccm4 \
|
||||
libxcb-image0 \
|
||||
libxcb-keysyms1 \
|
||||
libxcb-randr0 \
|
||||
libxcb-render-util0 \
|
||||
libxcb-render0 \
|
||||
libxcb-shape0 \
|
||||
libxcb-shm0 \
|
||||
libxcb-sync1 \
|
||||
libxcb-util1 \
|
||||
libxcb-xfixes0 \
|
||||
libxcb-xinerama0 \
|
||||
libxcb1 \
|
||||
libxkbcommon-dev \
|
||||
libxkbcommon-x11-0 \
|
||||
libxcb-xkb-dev \
|
||||
p7zip-full \
|
||||
&& sudo rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN sudo pip3 install aqtinstall
|
||||
|
||||
ARG QT_VERSION=6.4.1
|
||||
ARG QT_ARCH=gcc_64
|
||||
|
||||
ARG QT_DIR=/opt/qt
|
||||
RUN sudo aqt install-qt --outputdir ${QT_DIR} linux desktop ${QT_VERSION} ${QT_ARCH} --modules \
|
||||
qtremoteobjects \
|
||||
qt5compat \
|
||||
qtshadertools
|
||||
ENV QT_BIN_DIR=${QT_DIR}/${QT_VERSION}/${QT_ARCH}/bin
|
||||
|
||||
ARG QIF_VERSION=4.5
|
||||
ARG QIF_DIR=/opt/qif
|
||||
RUN sudo aqt install-tool --outputdir ${QIF_DIR} linux desktop tools_ifw
|
||||
ENV QIF_BIN_DIR=${QIF_DIR}/Tools/QtInstallerFramework/${QIF_VERSION}/bin
|
||||
8
.gitpod.yml
Normal file
8
.gitpod.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
tasks:
|
||||
- init: >-
|
||||
deploy/build_linux.sh
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
vscode:
|
||||
extensions:
|
||||
- llvm-vs-code-extensions.vscode-clangd
|
||||
144
.travis.yml
144
.travis.yml
@@ -1,144 +0,0 @@
|
||||
language: cpp
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- dev
|
||||
- /\d+\.\d+/
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- name: MacOS
|
||||
os: osx
|
||||
osx_image: xcode12.5
|
||||
|
||||
env:
|
||||
- QT_VERSION=5.15.2
|
||||
- QIF_VERSION=4.1
|
||||
- QT_BIN_DIR=$HOME/Qt/$QT_VERSION/clang_64/bin
|
||||
- QIF_BIN_DIR=$QT_BIN_DIR/../../../Tools/QtInstallerFramework/$QIF_VERSION/bin
|
||||
|
||||
script:
|
||||
- |
|
||||
if [ ! -f $HOME/Qt/$QT_VERSION/clang_64/bin/qmake ]; then \
|
||||
brew install p7zip && \
|
||||
python3 -m pip install --upgrade pip && \
|
||||
pip install -U aqtinstall requests py7zr && \
|
||||
pip show aqtinstall && \
|
||||
python3 -m aqt install --outputdir $HOME/Qt $QT_VERSION mac desktop clang_64 -m qtbase && \
|
||||
python3 -m aqt tool --outputdir $HOME/Qt mac tools_ifw $QIF_VERSION qt.tools.ifw.${QIF_VERSION/./};
|
||||
fi
|
||||
- bash deploy/build_macos.sh
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
token: $GH_TOKEN
|
||||
skip_cleanup: true
|
||||
file:
|
||||
- "AmneziaVPN_unsigned.dmg"
|
||||
on:
|
||||
tags: true
|
||||
branch: master
|
||||
|
||||
- name: Windows_x64
|
||||
os: windows
|
||||
|
||||
env:
|
||||
- PATH=/c/Python39:/c/Python39/Scripts:$PATH
|
||||
- QT_VERSION=5.14.2
|
||||
- QIF_VERSION=4.1
|
||||
- QT_BIN_DIR="c:\\Qt\\$QT_VERSION\\msvc2017_64\\bin"
|
||||
- QIF_BIN_DIR="c:\\Qt\\Tools\\QtInstallerFramework\\${QIF_VERSION}\\bin"
|
||||
- BUILD_ARCH=64
|
||||
|
||||
before_install:
|
||||
- if [ ! -f /C/Qt/$QT_VERSION/msvc2017_64/bin/qmake ]; then choco install python --version 3.9.1; fi
|
||||
|
||||
script:
|
||||
- dir "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build"
|
||||
- dir "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\Common7\Tools"
|
||||
- |
|
||||
if [ ! -f /C/Qt/$QT_VERSION/msvc2017_64/bin/qmake ]; then \
|
||||
python -m pip install --upgrade pip && \
|
||||
pip3 install -U aqtinstall requests py7zr && \
|
||||
pip3 show aqtinstall && \
|
||||
python -m aqt install --outputdir /C/Qt $QT_VERSION windows desktop win64_msvc2017_64 -m qtbase && \
|
||||
python -m aqt tool --outputdir /C/Qt windows tools_ifw $QIF_VERSION qt.tools.ifw.${QIF_VERSION/./}; \
|
||||
fi
|
||||
- echo set BUILD_ARCH=$BUILD_ARCH > winbuild.bat
|
||||
- echo set QT_BIN_DIR="$QT_BIN_DIR" >> winbuild.bat
|
||||
- echo set QIF_BIN_DIR="$QIF_BIN_DIR" >> winbuild.bat
|
||||
- echo call \""C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\VC\\Auxiliary\\Build\\vcvars${BUILD_ARCH}.bat\"" >> winbuild.bat
|
||||
- echo call \""C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\Common7\\Tools\\VsDevCmd.bat\" -arch=amd64" >> winbuild.bat
|
||||
- echo set WIN_CERT_PW=$WIN_CERT_PW >> winbuild.bat
|
||||
- echo call deploy\\build_windows.bat >> winbuild.bat
|
||||
- cmd //c winbuild.bat
|
||||
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
token: $GH_TOKEN
|
||||
skip_cleanup: true
|
||||
file:
|
||||
- "AmneziaVPN_x64.exe"
|
||||
on:
|
||||
tags: true
|
||||
branch: master
|
||||
|
||||
- name: Windows_x32
|
||||
os: windows
|
||||
|
||||
env:
|
||||
- PATH=/c/Python39:/c/Python39/Scripts:$PATH
|
||||
- QT_VERSION=5.14.2
|
||||
- QIF_VERSION=4.1
|
||||
- QT_BIN_DIR="c:\\Qt\\${QT_VERSION}\\msvc2017\\bin"
|
||||
- QIF_BIN_DIR="c:\\Qt\\Tools\\QtInstallerFramework\\${QIF_VERSION}\\bin"
|
||||
- BUILD_ARCH=32
|
||||
|
||||
before_install:
|
||||
- if [ ! -f /C/Qt/$QT_VERSION/msvc2017/bin/qmake ]; then choco install python --version 3.9.1; fi
|
||||
|
||||
script:
|
||||
- dir "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build"
|
||||
- dir "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\Common7\Tools"
|
||||
- |
|
||||
if [ ! -f /C/Qt/$QT_VERSION/msvc2017/bin/qmake ]; then \
|
||||
python -m pip install --upgrade pip && \
|
||||
pip3 install -U aqtinstall requests py7zr && \
|
||||
pip3 show aqtinstall && \
|
||||
python -m aqt install --outputdir /C/Qt $QT_VERSION windows desktop win32_msvc2017 -m qtbase && \
|
||||
python -m aqt tool --outputdir /C/Qt windows tools_ifw $QIF_VERSION qt.tools.ifw.${QIF_VERSION/./}; \
|
||||
fi
|
||||
- echo call \""C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\Common7\\Tools\\VsDevCmd.bat\"" > winbuild.bat
|
||||
- echo set BUILD_ARCH=$BUILD_ARCH >> winbuild.bat
|
||||
- echo set QT_BIN_DIR="$QT_BIN_DIR" >> winbuild.bat
|
||||
- echo set QIF_BIN_DIR="$QIF_BIN_DIR" >> winbuild.bat
|
||||
- echo call \""C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\VC\\Auxiliary\\Build\\vcvars${BUILD_ARCH}.bat\"" >> winbuild.bat
|
||||
- echo set WIN_CERT_PW=$WIN_CERT_PW >> winbuild.bat
|
||||
- echo call deploy\\build_windows.bat >> winbuild.bat
|
||||
- cmd //c winbuild.bat
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
token: $GH_TOKEN
|
||||
skip_cleanup: true
|
||||
file:
|
||||
- "AmneziaVPN_x32.exe"
|
||||
on:
|
||||
tags: true
|
||||
branch: master
|
||||
|
||||
deploy:
|
||||
skip_cleanup: true
|
||||
|
||||
before_cache:
|
||||
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then brew cleanup; fi
|
||||
# Cache only .git files under "/usr/local/Homebrew" so "brew update" does not take 5min every build
|
||||
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then find /usr/local/Homebrew \! -regex ".+\.git.+" -delete; fi
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/Qt
|
||||
- /C/Qt
|
||||
- $HOME/Library/Caches/Homebrew
|
||||
@@ -1,6 +0,0 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = client
|
||||
|
||||
!ios:!android {
|
||||
SUBDIRS += service platform
|
||||
}
|
||||
78
CMakeLists.txt
Normal file
78
CMakeLists.txt
Normal file
@@ -0,0 +1,78 @@
|
||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.2)
|
||||
|
||||
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
|
||||
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
|
||||
${CMAKE_SOURCE_DIR}/cmake/platform_settings.cmake
|
||||
${CMAKE_SOURCE_DIR}/cmake/recipes_bootstrap.cmake
|
||||
${CMAKE_SOURCE_DIR}/cmake/conan_provider.cmake
|
||||
CACHE STRING "" FORCE)
|
||||
|
||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
|
||||
# trigger conan to kick off `conan install` globally
|
||||
find_package(OpenSSL REQUIRED)
|
||||
if (PREBUILTS_ONLY)
|
||||
return()
|
||||
endif()
|
||||
|
||||
string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 2123)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
|
||||
set(MZ_PLATFORM_NAME "windows")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
|
||||
set(MZ_PLATFORM_NAME "macos")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Android")
|
||||
set(MZ_PLATFORM_NAME "android")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "iOS")
|
||||
set(MZ_PLATFORM_NAME "ios")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
|
||||
set(MZ_PLATFORM_NAME "wasm")
|
||||
endif()
|
||||
|
||||
set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
|
||||
|
||||
if(APPLE AND NOT IOS)
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(AMN_PF_RULE_IDENTITY "user { root }")
|
||||
else()
|
||||
set(AMN_PF_RULE_IDENTITY "group { amnvpn }")
|
||||
endif()
|
||||
|
||||
configure_file(
|
||||
"${CMAKE_SOURCE_DIR}/deploy/data/pf-templates/amn.400.allowPIA.conf.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/amn.400.allowPIA.conf"
|
||||
@ONLY
|
||||
)
|
||||
|
||||
file(COPY_FILE
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/amn.400.allowPIA.conf"
|
||||
"${CMAKE_SOURCE_DIR}/deploy/data/macos/pf/amn.400.allowPIA.conf"
|
||||
ONLY_IF_DIFFERENT
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
add_subdirectory(client)
|
||||
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
add_subdirectory(service)
|
||||
endif()
|
||||
|
||||
if ((LINUX AND NOT ANDROID) OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (WIN32))
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/CPack.cmake)
|
||||
endif()
|
||||
191
README.md
191
README.md
@@ -1,50 +1,185 @@
|
||||
# Amnezia VPN
|
||||
## _The best client for self-hosted VPN_
|
||||
|
||||
[](https://travis-ci.com/amnezia-vpn/desktop-client)
|
||||
### _The best client for self-hosted VPN_
|
||||
|
||||
Amnezia is a VPN client with the key feature of deploying your own VPN server on you virtual server.
|
||||
|
||||
[](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
|
||||
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||
|
||||
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
|
||||
|
||||
|
||||
[Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||
|
||||
[](https://amnezia.org)
|
||||
|
||||
### [Website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
|
||||
|
||||
> [!TIP]
|
||||
> If the [Amnezia website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror).
|
||||
|
||||
<a href="https://amnezia.org/en/downloads?utm_source=github&utm_campaign=amnezia_button-readme-en"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/en/downloads&utm_source=github&utm_campaign=amnezia_button-readme-en-mirrow"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
|
||||
|
||||
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
|
||||
<br/>
|
||||
|
||||
<a href="https://www.testiny.io"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/testiny.png" height="28px"></a>
|
||||
|
||||
## Features
|
||||
- Very easy to use - enter your ip address, ssh login and password, and Amnezia client will automatically install VPN docker containers to your server and connect to VPN.
|
||||
- OpenVPN and OpenVPN over ShadowSocks protocols support.
|
||||
- Custom VPN routing mode support - add any sites to client to enable VPN only for them.
|
||||
- Windows and MacOS support.
|
||||
- Unsecure sharing connection profile for family use.
|
||||
|
||||
- Very easy to use - enter your IP address, SSH login, password and Amnezia will automatically install VPN docker containers to your server and connect to the VPN.
|
||||
- Classic VPN-protocols: OpenVPN, WireGuard and IKEv2 protocols.
|
||||
- Protocols with traffic Masking (Obfuscation): OpenVPN over [Cloak](https://github.com/cbeuw/Cloak) plugin, Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
|
||||
- Split tunneling support - add any sites to the client to enable VPN only for them or add Apps (only for Android and Desktop).
|
||||
- Windows, MacOS, Linux, Android, iOS releases.
|
||||
- Support for AmneziaWG protocol configuration on [Keenetic beta firmware](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
|
||||
|
||||
## Links
|
||||
|
||||
- [https://amnezia.org](https://amnezia.org) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Documentation
|
||||
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
|
||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
|
||||
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium
|
||||
|
||||
## Tech
|
||||
|
||||
AmneziaVPN uses a number of open source projects to work:
|
||||
AmneziaVPN uses several open-source projects to work:
|
||||
|
||||
- [OpenSSL](https://www.openssl.org/)
|
||||
- [OpenVPN](https://openvpn.net/)
|
||||
- [ShadowSocks](https://shadowsocks.org/)
|
||||
- [Qt](https://www.qt.io/)
|
||||
- [EasyRSA](https://github.com/OpenVPN/easy-rsa) - part of OpenVPN
|
||||
- [CygWin](https://www.cygwin.com/) - only for Windiws, used for launching EasyRSA scripts
|
||||
- [QtSsh](https://github.com/jaredtao/QtSsh) - forked form Qt Creator
|
||||
- [LibSsh](https://libssh.org)
|
||||
- [WireGuard](https://www.wireguard.com/)
|
||||
- [Xray-core](https://xtls.github.io/en/)
|
||||
- [Conan](https://conan.io/)
|
||||
- and more...
|
||||
|
||||
## Development
|
||||
## Help us with translations
|
||||
|
||||
Download the most actual translation files.
|
||||
|
||||
Go to ["Actions" tab](https://github.com/amnezia-vpn/amnezia-client/actions?query=is%3Asuccess+branch%3Adev), click on the first line.
|
||||
Then scroll down to the "Artifacts" section and download "AmneziaVPN_translations".
|
||||
|
||||
Unzip this file.
|
||||
Each *.ts file contains strings for one corresponding language.
|
||||
|
||||
Translate or correct some strings in one or multiple *.ts files and commit them back to this repository into the ``client/translations`` folder.
|
||||
You can do it via a web-interface or any other method you're familiar with.
|
||||
|
||||
## Checking out the source code
|
||||
|
||||
Make sure to pull all submodules after checking out the repo.
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## Hacking guide
|
||||
|
||||
Want to contribute? Welcome!
|
||||
|
||||
### Building sources and deployment
|
||||
Easiest way to build your own executables - is to fork project and configure [Travis CI](https://travis-ci.com/)
|
||||
Or you can build sources manually using Qt Creator. Qt >= 14.2 supported.
|
||||
Look to the `build_macos.sh` and `build_windows.bat` scripts in `deploy` folder for details.
|
||||
### Build requirements
|
||||
|
||||
* [`CMake`](https://cmake.org/download/)
|
||||
* Compiler and underlying build system, depending on the target:
|
||||
- [Linux] Any of `make` and `gcc`
|
||||
- [Apple] [`Xcode`](https://developer.apple.com/xcode/) or [`Xcode command line tools`](https://developer.apple.com/xcode/)
|
||||
- [Windows] [`Visual Studio 2022`](https://aka.ms/vs/17/release/vs_community.exe) or [`VS 2022 Build Tools`](https://aka.ms/vs/17/release/vs_buildtools.exe)
|
||||
- [Android] [`Android SDK`](#installing-android-sdk) and [`Ninja`](https://ninja-build.org/)
|
||||
* [`Qt 6.10+`](https://www.qt.io/download-open-source) with the following modules:
|
||||
- Core module for targeting platform (Desktop/Android/iOS)
|
||||
- Qt 5 Compatibility module
|
||||
- Qt Remote Objects
|
||||
* [`Conan`](https://conan.io/downloads) package manager
|
||||
- On MacOS is enough just to use `homebrew` or install it in `.venv` in project root
|
||||
- Other systems must have it in `PATH`
|
||||
* (Optional) Installer dependencies:
|
||||
- [Windows/Linux] [`Qt Installer Framework`](https://www.qt.io/download-open-source)
|
||||
- [Windows] [`WIX toolset`](https://github.com/wixtoolset/wix/releases)
|
||||
|
||||
### Building the project using scripts
|
||||
|
||||
* Run scripts located in `deploy` directory
|
||||
* Basically, if dependencies are located in default installation paths, the scripts will find them automatically.
|
||||
* If they differ, specify them using the following variables:
|
||||
- `QT_INSTALL_DIR` - Qt root installation folder
|
||||
- `QT_ROOT_PATH` - Qt framework root directory
|
||||
- `QIF_ROOT_PATH` - Qt Installer Framework root path
|
||||
- `ANDROID_HOME` - Path to Android SDK root folder
|
||||
- and others. Check scripts for more
|
||||
|
||||
Unix-like:
|
||||
```bash
|
||||
# Build executables for the host platform
|
||||
deploy/build.sh
|
||||
|
||||
# Or just
|
||||
deploy/build.sh
|
||||
|
||||
# Build executables and installers for the host platform
|
||||
deploy/build.sh --installer all
|
||||
|
||||
# Build Android APK and AAB
|
||||
deploy/build.sh -t android --aab
|
||||
|
||||
# Call for help
|
||||
deploy/build.sh -h
|
||||
```
|
||||
|
||||
Windows:
|
||||
```batch
|
||||
:: Build executables for Windows
|
||||
deploy/build.bat
|
||||
|
||||
:: Build executables with IFW installer for Windows
|
||||
deploy/build.bat --installer ifw
|
||||
|
||||
:: Build executables with IFW and WIX installer for Windows
|
||||
deploy/build.bat --installer ifw --installer wix
|
||||
|
||||
:: Or just
|
||||
deploy/build.bat --installer all
|
||||
```
|
||||
|
||||
### Developing the project in IDEs
|
||||
|
||||
* Basically, you can use any IDE that handles CMake and Qt kits properly to run configure and build steps, and to navigate through the code nicely. For example:
|
||||
- `Qt Creator`
|
||||
- `Visual Studio Code` with `Qt Extension Pack`
|
||||
- and so on
|
||||
|
||||
* To use `Xcode`, you have to configure project first by using `cmake`. The easiest way to do it is to use `Qt Creator` for configuration. Then open `AmneziaVPN.xcodeproj` file from the build folder by using `Xcode`. Note that none of the files changed are saved - the files actually getting changed in build directory. Copy them manually if necessary
|
||||
|
||||
* `Android studio` could be used in the same way - just configure the project by using `cmake` manually or by using `Qt Creator`. Open `<build-dir>/client/android-build` in `Android studio` then. Do not forget to copy the changes - everything you do is saved under the build directory actually.
|
||||
|
||||
### Installing Android SDK
|
||||
|
||||
* Android SDK could be installed using the following methods:
|
||||
- Using `Qt Creator`. Use `Preferences`->`SDKs`
|
||||
- Using `Android studio`. By default it installs necessary `SDKs` automatically during the installation
|
||||
- Manually by using `sdk-manager`. Check [this](https://developer.android.com/tools) page for details
|
||||
|
||||
## License
|
||||
GPL v.3
|
||||
|
||||
## Contacts
|
||||
[https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
||||
[https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
|
||||
[https://signal.group/...](https://signal.group/#CjQKIB2gUf8QH_IXnOJMGQWMDjYz9cNfmRQipGWLFiIgc4MwEhAKBONrSiWHvoUFbbD0xwdh) - Signal channel
|
||||
[https://amnezia.org](https://amnezia.org) - project website
|
||||
This project is licensed under the GNU General Public License v3.0 (see LICENSE) and also includes third-party components distributed under their own terms (see THIRD_PARTY_LICENSES.md).
|
||||
|
||||
## Donate
|
||||
Bitcoin: bc1qn9rhsffuxwnhcuuu4qzrwp4upkrq94xnh8r26u
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
|
||||
payeer.com: P2561305
|
||||
ko-fi.com: [https://ko-fi.com/amnezia_vpn](https://ko-fi.com/amnezia_vpn)
|
||||
|
||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||
|
||||
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
|
||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||
TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
|
||||
## Acknowledgments
|
||||
|
||||
This project is tested with BrowserStack.
|
||||
We express our gratitude to [BrowserStack](https://www.browserstack.com) for supporting our project.
|
||||
|
||||
180
README_RU.md
Normal file
180
README_RU.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# Amnezia VPN
|
||||
|
||||
### _Лучший клиент для создания VPN на собственном сервере_
|
||||
|
||||
[](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
|
||||
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||
|
||||
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
|
||||
[AmneziaVPN](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||
|
||||
[](https://amnezia.org)
|
||||
|
||||
### [Сайт](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
||||
|
||||
> [!TIP]
|
||||
> Если [сайт Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror).
|
||||
|
||||
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/ru/downloads&utm_source=github&utm_campaign=amnezia_button-readme-ru-mirror"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
|
||||
|
||||
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
|
||||
<br/>
|
||||
|
||||
<a href="https://www.testiny.io"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/testiny.png" height="28px"></a>
|
||||
|
||||
## Особенности
|
||||
|
||||
- Простой в использовании — введите IP-адрес, SSH-логин и пароль, и Amnezia автоматически установит VPN-контейнеры Docker на ваш сервер и подключится к VPN.
|
||||
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
|
||||
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
|
||||
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
|
||||
- Поддерживает платформы: Windows, macOS, Linux, Android, iOS.
|
||||
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
|
||||
|
||||
## Ссылки
|
||||
|
||||
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
|
||||
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
|
||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
|
||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
|
||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
|
||||
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
|
||||
|
||||
## Технологии
|
||||
|
||||
AmneziaVPN использует несколько проектов с открытым исходным кодом:
|
||||
|
||||
- [OpenSSL](https://www.openssl.org/)
|
||||
- [OpenVPN](https://openvpn.net/)
|
||||
- [Qt](https://www.qt.io/)
|
||||
- [LibSsh](https://libssh.org)
|
||||
- [WireGuard](https://www.wireguard.com/)
|
||||
- [Xray-core](https://xtls.github.io/en/)
|
||||
- [Conan](https://conan.io/)
|
||||
- и другие...
|
||||
|
||||
## Помощь с переводами
|
||||
|
||||
Загрузите самые актуальные файлы перевода.
|
||||
|
||||
Перейдите на [вкладку "Actions"](https://github.com/amnezia-vpn/amnezia-client/actions?query=is%3Asuccess+branch%3Adev), нажмите на первую строку. Затем прокрутите вниз до раздела "Artifacts" и скачайте "AmneziaVPN_translations".
|
||||
|
||||
Распакуйте этот файл. Каждый файл с расширением *.ts содержит строки для соответствующего языка.
|
||||
|
||||
Переведите или исправьте строки в одном или нескольких файлах *.ts и загрузите их обратно в этот репозиторий в папку ``client/translations``. Это можно сделать через веб-интерфейс или любым другим знакомым вам способом.
|
||||
|
||||
## Проверка исходного кода
|
||||
|
||||
После клонирования репозитория обязательно загрузите все подмодули.
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## Руководство по разработке
|
||||
|
||||
Хотите внести свой вклад? Добро пожаловать!
|
||||
|
||||
### Требования для сборки
|
||||
|
||||
* [`CMake`](https://cmake.org/download/)
|
||||
* Компилятор и система сборки, в зависимости от таргета:
|
||||
- [Linux] Любые `make` и `gcc`
|
||||
- [Apple] [`Xcode`](https://developer.apple.com/xcode/) или [`Xcode command line tools`](https://developer.apple.com/xcode/)
|
||||
- [Windows] [`Visual Studio 2022`](https://aka.ms/vs/17/release/vs_community.exe) или [`VS 2022 Build Tools`](https://aka.ms/vs/17/release/vs_buildtools.exe)
|
||||
- [Android] [`Android SDK`](#установка-android-sdk) и [`Ninja`](https://ninja-build.org/)
|
||||
* [`Qt 6.10+`](https://www.qt.io/download-open-source) со следующими модулями:
|
||||
- Основные модули для таргета (Desktop/Android/iOS)
|
||||
- Qt 5 Compatibility module
|
||||
- Qt Remote Objects
|
||||
* Пакетный менеджер [`Conan`](https://conan.io/downloads)
|
||||
- На MacOS достаточно использовать `homebrew` или установить в `.venv` в корень проекта
|
||||
- Для остальных систем необходимо прописать пути в `PATH`
|
||||
* (Необязательно) Заивисимости для установщиков:
|
||||
- [Windows/Linux] [`Qt Installer Framework`](https://www.qt.io/download-open-source)
|
||||
- [Windows] [`WIX toolset`](https://github.com/wixtoolset/wix/releases)
|
||||
|
||||
### Сборка проекта через скрипты
|
||||
|
||||
* Запустите скрипты, находящиеся в папке `deploy`
|
||||
* Если все зависимости установлены в стандартных локациях, скрипт найдёт их самостоятельно
|
||||
* Если пути отличаются, их нужно явно указать используя:
|
||||
- `QT_INSTALL_DIR` - корневая папка установки Qt
|
||||
- `QT_ROOT_PATH` - корневая папка Qt Framework
|
||||
- `QIF_ROOT_PATH` - корневая папка Qt Installer Framework
|
||||
- `ANDROID_HOME` - путь к Android SDK
|
||||
- и другие. Их можно получить из вышеуказанных скриптов
|
||||
|
||||
Unix-like:
|
||||
```bash
|
||||
# Build executables for the host platform
|
||||
deploy/build.sh
|
||||
|
||||
# Or just
|
||||
deploy/build.sh
|
||||
|
||||
# Build executables and installers for the host platform
|
||||
deploy/build.sh --installer all
|
||||
|
||||
# Build Android APK and AAB
|
||||
deploy/build.sh -t android --aab
|
||||
|
||||
# Call for help
|
||||
deploy/build.sh -h
|
||||
```
|
||||
|
||||
Windows:
|
||||
```batch
|
||||
:: Build executables for Windows
|
||||
deploy/build.bat
|
||||
|
||||
:: Build executables with IFW installer for Windows
|
||||
deploy/build.bat --installer ifw
|
||||
|
||||
:: Build executables with IFW and WIX installer for Windows
|
||||
deploy/build.bat --installer ifw --installer wix
|
||||
|
||||
:: Or just
|
||||
deploy/build.bat --installer all
|
||||
```
|
||||
|
||||
### Разработка в IDE
|
||||
|
||||
* Можно использовать любые IDE которые умеют работать с CMake и находить Qt Kits. Например:
|
||||
- `Qt Creator`
|
||||
- `Visual Studio Code` with `Qt Extension Pack`
|
||||
- и так далее
|
||||
|
||||
* Для использования `Xcode` нужно сконфигурировать проект с помощью `cmake`. Самый простой способ это сделать - использовать `Qt Creator` для конфигурации. Затем, нужно открыть файл `AmneziaVPN.xcodeproj` из папки сборки с помощью `Xcode`. Учтите, что никакие файлы фактически не сохраняются - они сохраняются в директории сборки. Если требуется, скопируйте файлы вручную
|
||||
|
||||
* `Android studio` может быть использована подобным вышеуказанному способу - нужно использовать `cmake` вручную или через `Qt Creator` для конфигурации. Далее, откройте `<build-dir>/client/android-build` в `Android studio`. Не забудьте скопировать изменённые файлы в папку с исходным кодом - все файлы, изменённые в IDE, сохраняются фактически в папке сборки.
|
||||
|
||||
### Установка Android SDK
|
||||
|
||||
* Android SDK может быть установлен следующими способами:
|
||||
- Используя `Qt Creator`, через настройки в пунктах `Preferences`->`SDKs`
|
||||
- Используя `Android studio`. По умолчанию необходимые `SDK` устанавливаются автоматически.
|
||||
- Вручную, используя `sdk-manager`. Подробности можно найти [здесь](https://developer.android.com/tools)
|
||||
|
||||
## Лицензия
|
||||
|
||||
GPL v3.0
|
||||
|
||||
## Донаты
|
||||
|
||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||
|
||||
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
|
||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||
TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
|
||||
|
||||
## Благодарности
|
||||
|
||||
Этот проект тестируется с помощью BrowserStack.
|
||||
Мы выражаем благодарность [BrowserStack](https://www.browserstack.com) за поддержку нашего проекта.
|
||||
149
THIRD_PARTY_LICENSES.md
Normal file
149
THIRD_PARTY_LICENSES.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Third-Party Licenses
|
||||
|
||||
This project is licensed under the GNU General Public License v3.0.
|
||||
This file lists third-party software components used by this repository.
|
||||
Each component is distributed under its own license as linked below.
|
||||
|
||||
---
|
||||
|
||||
## QtKeychain
|
||||
|
||||
- Source: https://github.com/frankosterfeld/qtkeychain
|
||||
- License: BSD License
|
||||
- License Text: https://www.gnu.org/licenses/license-list.html#ModifiedBSD
|
||||
|
||||
---
|
||||
|
||||
## QSimpleCrypto
|
||||
|
||||
- Source: https://github.com/n1flh31mur/QSimpleCrypto
|
||||
- License: Apache License 2.0
|
||||
- License Text: https://github.com/n1flh31mur/QSimpleCrypto/blob/master/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## SortFilterProxyModel
|
||||
|
||||
- Source: https://github.com/oKcerG/SortFilterProxyModel
|
||||
- License: MIT License
|
||||
- License Text: https://github.com/oKcerG/SortFilterProxyModel/blob/master/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## QJsonStruct
|
||||
|
||||
- Source: https://github.com/Qv2ray/QJsonStruct
|
||||
- License: MIT License
|
||||
- License Text: https://github.com/Qv2ray/QJsonStruct/blob/master/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## QR Code Generator (qrcodegen)
|
||||
|
||||
- Source: https://github.com/nayuki/QR-Code-generator
|
||||
- License: MIT License
|
||||
- License Text: https://www.nayuki.io/page/qr-code-generator-library
|
||||
|
||||
---
|
||||
|
||||
## Qt Gamepad
|
||||
|
||||
- Source: https://github.com/qt/qtgamepad
|
||||
- License: GNU General Public License v3.0 (GPL-3.0)
|
||||
- License Text: https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
||||
---
|
||||
|
||||
## AmneziaWG Apple (WireGuard)
|
||||
|
||||
- Source: https://github.com/amnezia-vpn/amneziawg-apple
|
||||
- License: MIT License
|
||||
- License Text: https://github.com/amnezia-vpn/amneziawg-apple/blob/master/COPYING
|
||||
|
||||
---
|
||||
|
||||
## AmneziaWG Android
|
||||
|
||||
- Source: https://github.com/amnezia-vpn/amneziawg-go
|
||||
- License: MIT License
|
||||
- License Text: https://github.com/amnezia-vpn/amneziawg-go/blob/master/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## Xray Core
|
||||
|
||||
- Source: https://github.com/XTLS/Xray-core
|
||||
- License: Mozilla Public License 2.0 (MPL-2.0)
|
||||
- License Text: https://github.com/XTLS/Xray-core/blob/main/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## Cloak
|
||||
|
||||
- Source: https://github.com/cbeuw/Cloak
|
||||
- License: GNU General Public License v3.0 (GPL-3.0)
|
||||
- License Text: https://github.com/cbeuw/Cloak/blob/master/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## Shadowsocks
|
||||
|
||||
- Source: https://github.com/shadowsocks/shadowsocks-libev
|
||||
- License: GPL-3.0-or-later
|
||||
- License Text: http://www.gnu.org/licenses/
|
||||
|
||||
---
|
||||
|
||||
## OpenSSL
|
||||
|
||||
- Source: https://github.com/openssl/openssl
|
||||
- License: Apache License 2.0
|
||||
- License Text: https://www.openssl.org/source/license.html
|
||||
|
||||
---
|
||||
|
||||
## libssh
|
||||
|
||||
- Source: https://www.libssh.org/
|
||||
- License: GNU Lesser General Public License (LGPL)
|
||||
- License Text: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
|
||||
|
||||
---
|
||||
|
||||
## OpenVPNAdapter
|
||||
|
||||
- Source: https://github.com/ss-abramchuk/OpenVPNAdapter
|
||||
- License: GNU Affero General Public License v3.0 (AGPL-3.0)
|
||||
- License Text: https://github.com/ss-abramchuk/OpenVPNAdapter/blob/master/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## Wintun
|
||||
|
||||
- Source: https://www.wintun.net/
|
||||
- License: Prebuilt Binaries License
|
||||
- License Text: https://github.com/WireGuard/wintun/blob/master/prebuilt-binaries-license.txt
|
||||
|
||||
---
|
||||
|
||||
## Mullvad Split Tunnel Driver
|
||||
|
||||
- Source: https://github.com/mullvad/win-split-tunnel
|
||||
- License: GNU General Public License v3.0 (GPL-3.0) and Mozilla Public License Version 2.0
|
||||
- License Text: https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-GPL.md https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-MPL.txt
|
||||
|
||||
---
|
||||
|
||||
## tun2socks
|
||||
|
||||
- Source: https://github.com/eycorsican/go-tun2socks
|
||||
- License: MIT License
|
||||
- License Text: https://github.com/eycorsican/go-tun2socks/blob/master/LICENSE
|
||||
|
||||
---
|
||||
|
||||
## TAP-Windows Driver
|
||||
|
||||
- Source: https://github.com/OpenVPN/tap-windows6
|
||||
- License: tap-windows6 license
|
||||
- License Text: https://github.com/OpenVPN/tap-windows6/blob/master/COPYING
|
||||
14
agw-sdk/.gitignore
vendored
Normal file
14
agw-sdk/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# Локальные сборки
|
||||
build/
|
||||
build-*/
|
||||
cmake-build-*/
|
||||
|
||||
# Conan
|
||||
CMakeUserPresets.json
|
||||
conan.lock
|
||||
|
||||
# Примеры
|
||||
examples/dart_smoke/.dart_tool/
|
||||
examples/dart_smoke/pubspec.lock
|
||||
examples/c_smoke/smoke
|
||||
test_package/
|
||||
133
agw-sdk/CMakeLists.txt
Normal file
133
agw-sdk/CMakeLists.txt
Normal file
@@ -0,0 +1,133 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
project(agw LANGUAGES CXX VERSION 0.1.0)
|
||||
|
||||
# --- стандарт ---------------------------------------------------------------
|
||||
# floor C++20 (см. решения в docs/plans/gateway-sdk/README.md). C++23 — opt-in позже.
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
# --- опции ------------------------------------------------------------------
|
||||
# Когда SDK подключают через add_subdirectory (отладочная сборка в клиенте), по умолчанию НЕ строим
|
||||
# тесты и НЕ строим shared C-ABI, чтобы не пачкать родительскую сборку. Standalone — всё включено.
|
||||
if(PROJECT_IS_TOP_LEVEL)
|
||||
set(_agw_default_aux ON)
|
||||
else()
|
||||
set(_agw_default_aux OFF)
|
||||
endif()
|
||||
option(AGW_BUILD_TESTS "Build agw-sdk tests" ${_agw_default_aux})
|
||||
|
||||
# Режимы зависимостей (Фаза 5): shared-deps — общий OpenSSL из Conan; vendored — бандл.
|
||||
# На Фазе 1 определяем only-флаг, реальный механизм бандла появится в Фазе 5.
|
||||
set(AGW_DEPS_MODE "shared-deps" CACHE STRING "Dependency mode: shared-deps | vendored")
|
||||
|
||||
# --- зависимости ------------------------------------------------------------
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
# libcurl (транспорт по умолчанию). Если не найден — SDK собирается без него, а клиент обязан
|
||||
# передать свой IHttpClient через Config (см. default_client_fallback.cpp).
|
||||
find_package(CURL QUIET)
|
||||
|
||||
# nlohmann/json: предпочтительно из Conan (find_package), иначе — вендоренный single-header
|
||||
# (он лежит в tests/third_party и нужен только для локальной сборки без Conan).
|
||||
find_package(nlohmann_json QUIET)
|
||||
if(NOT nlohmann_json_FOUND)
|
||||
add_library(agw_nlohmann_fallback INTERFACE)
|
||||
target_include_directories(agw_nlohmann_fallback INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/third_party)
|
||||
add_library(nlohmann_json::nlohmann_json ALIAS agw_nlohmann_fallback)
|
||||
message(STATUS "agw: using vendored nlohmann/json fallback (tests/third_party)")
|
||||
endif()
|
||||
|
||||
# --- библиотека -------------------------------------------------------------
|
||||
# Один раз компилируем объекты, из них собираем static (agw) и shared C-ABI (agw_capi).
|
||||
add_library(agw_obj OBJECT
|
||||
src/gateway_controller.cpp
|
||||
src/c_abi.cpp
|
||||
src/crypto/rng.cpp
|
||||
src/crypto/aes.cpp
|
||||
src/crypto/rsa.cpp
|
||||
src/crypto/hash.cpp
|
||||
src/http/curl_client.cpp
|
||||
src/http/default_client_fallback.cpp
|
||||
src/protocol/request_builder.cpp
|
||||
src/protocol/response.cpp
|
||||
src/protocol/error_mapping.cpp
|
||||
src/failover/bypass_policy.cpp
|
||||
src/failover/proxy_list.cpp
|
||||
src/failover/proxy_picker.cpp
|
||||
src/util/base64.cpp
|
||||
src/util/uuid.cpp
|
||||
src/util/json.cpp
|
||||
src/util/url.cpp
|
||||
src/util/thread_pool.cpp
|
||||
)
|
||||
|
||||
target_include_directories(agw_obj
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
# PUBLIC, чтобы зависимости и include-пути дотекли до static/shared (и до тестов).
|
||||
target_link_libraries(agw_obj
|
||||
PUBLIC OpenSSL::Crypto nlohmann_json::nlohmann_json Threads::Threads
|
||||
)
|
||||
# Скрываем всё по умолчанию: наружу торчат только agw_* (AGW_API = visibility default).
|
||||
set_target_properties(agw_obj PROPERTIES
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
VISIBILITY_INLINES_HIDDEN ON
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
|
||||
if(CURL_FOUND)
|
||||
target_compile_definitions(agw_obj PRIVATE AGW_HAVE_CURL)
|
||||
target_link_libraries(agw_obj PUBLIC CURL::libcurl)
|
||||
message(STATUS "agw: libcurl found — default HTTP client enabled")
|
||||
else()
|
||||
message(STATUS "agw: libcurl NOT found — default HTTP client disabled (inject IHttpClient via Config)")
|
||||
endif()
|
||||
|
||||
# Статическая библиотека (C++ API) — для наших приложений / тестов.
|
||||
add_library(agw STATIC)
|
||||
target_link_libraries(agw PUBLIC agw_obj)
|
||||
add_library(agw::agw ALIAS agw)
|
||||
|
||||
# Shared C-ABI библиотека (для dart:ffi и сторонних). Экспортирует только agw_*.
|
||||
option(AGW_BUILD_CAPI_SHARED "Build shared C-ABI library (agw_capi)" ${_agw_default_aux})
|
||||
if(AGW_BUILD_CAPI_SHARED)
|
||||
add_library(agw_capi SHARED $<TARGET_OBJECTS:agw_obj>)
|
||||
target_link_libraries(agw_capi PRIVATE agw_obj)
|
||||
target_compile_definitions(agw_capi PRIVATE AGW_BUILDING_SHARED)
|
||||
set_target_properties(agw_capi PROPERTIES OUTPUT_NAME agw_capi)
|
||||
message(STATUS "agw: deps mode = ${AGW_DEPS_MODE} (vendored — статическая линковка/скрытие символов, через Conan)")
|
||||
endif()
|
||||
|
||||
set_target_properties(agw PROPERTIES
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
VISIBILITY_INLINES_HIDDEN ON
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
|
||||
# --- установка (только для Conan-пакета / standalone; не при add_subdirectory) ---------------
|
||||
if(PROJECT_IS_TOP_LEVEL)
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS agw ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
if(AGW_BUILD_CAPI_SHARED)
|
||||
install(TARGETS agw_capi
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/agw
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
endif()
|
||||
|
||||
# --- тесты ------------------------------------------------------------------
|
||||
if(AGW_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
110
agw-sdk/README.md
Normal file
110
agw-sdk/README.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# agw-sdk
|
||||
|
||||
Qt-free C++20 транспорт к API-шлюзу Amnezia (вынос `GatewayController`). Узкая поверхность —
|
||||
`post` (sync/async) поверх крипты, выбора эндпоинта и обхода блокировок. Протокол воспроизводится
|
||||
байт-в-байт.
|
||||
|
||||
План и решения: [../docs/plans/gateway-sdk/](../docs/plans/gateway-sdk/) — начни с
|
||||
`agw-sdk-tier1-impl-plan.md` и `README.md` (таблица решений).
|
||||
|
||||
## Статус
|
||||
|
||||
Тир 1, в работе по фазам:
|
||||
|
||||
- [x] **Фаза 1** — каркас + крипта на OpenSSL EVP (AES-256-CBC, RSA-PKCS1 v1.5, SHA-512), base64
|
||||
(std + url), UUID v4, Qt-Indented JSON-сериализатор, golden-тесты крипты.
|
||||
- [x] **Фаза 2** — `IHttpClient`(libcurl) + `Config`/`GatewayController`/`executePost` + sync `post`;
|
||||
`request_builder`/`response`/`error_mapping`; интеграционный тест через in-process mock-шлюз
|
||||
(полный round-trip: SDK шифрует → «сервер» расшифровывает → шифрует ответ → SDK расшифровывает).
|
||||
- [x] **Фаза 3** — failover: `bypass_policy` (`shouldBypassProxy` дословно), `proxy_list`
|
||||
(S3-пути + prod-расшифровка через `SHA-512(pubkey)`), `proxy_picker` (health-check `lmbd-health`),
|
||||
встройка в `executePost` с кешем рабочего прокси на инстансе (под мьютексом). Интеграционный тест:
|
||||
прямой ответ подозрителен → S3 → health → прокси → успех; повторный запрос идёт сразу на кеш.
|
||||
- [x] **Фаза 4** — `util::ThreadPool` (drain в деструкторе), `postAsync`(коллбэк на потоке пула)/
|
||||
`postFuture`(`std::future`) поверх `executePost`, `CancellationToken` (проверки между шагами
|
||||
failover + прерывание трансфера через progress-коллбэк curl → `ErrorCode::Cancelled`). Кеш прокси
|
||||
под мьютексом, пул — последний член Impl (рушится первым, дожидаясь задач). TSan чисто; ASan+UBSan
|
||||
10/10.
|
||||
- [x] **Фаза 5** — C-ABI (`include/agw/c_abi.h` + `src/c_abi.cpp`, `agw_*`: создание/уничтожение,
|
||||
sync/async `post`, токен отмены, освобождение результата; на границе только C-типы). Сборка:
|
||||
object-библиотека → static `agw` + shared `agw_capi` (экспортирует **только** `agw_*`, остальное
|
||||
скрыто). C-smoke (чистый C) и Dart-smoke (`dart:ffi`) проходят. `conan create` (shared-deps)
|
||||
зелёный — пакет с `libagw.a` + `libagw_capi.dylib` + заголовками. Режим `vendored` (статические
|
||||
зависимости) задан в conanfile (`-o deps_mode=vendored`).
|
||||
- [~] **Фаза 6** — интеграция в Qt-клиент. Готово: `GatewayController` переписан тонким адаптером
|
||||
над `agw::GatewayController` (сигнатуры один в один, байт-паритет payload, персистентный клиент на
|
||||
окружение, `onBeforeRequest` = iOS inet + desktop kill-switch, async через `QPromise`+маршалинг);
|
||||
проводка сборки (корневой `conanfile` requires `agw-sdk/0.1.0`, `client/cmake/3rdparty.cmake`
|
||||
линкует `agw::agw`). Осталось (вне этого окружения): Qt-сборка под все платформы, перевод
|
||||
синхронных вызовов (`subscription`/`servicesCatalog` `executeRequest`) на рабочий поток, регрессия
|
||||
против dev/prod. См. `docs/plans/gateway-sdk/agw-sdk-tier1-phase6-integration.md`.
|
||||
|
||||
## Раскладка
|
||||
|
||||
```
|
||||
include/agw/ публичные заголовки (types, config, client, http, cancellation, c_abi)
|
||||
src/crypto/ AES, RSA, SHA-512, RNG
|
||||
src/util/ base64, uuid, json (Qt-Indented), url, thread_pool
|
||||
src/protocol/ имена полей API, request_builder, response, error_mapping
|
||||
src/failover/ bypass_policy, proxy_list, proxy_picker
|
||||
src/http/ curl_client (+ fallback)
|
||||
src/c_abi.cpp C-ABI обёртка
|
||||
tests/ unit + golden + integration (+ вендоренный nlohmann для офлайн-сборки)
|
||||
examples/ c_smoke (чистый C), dart_smoke (dart:ffi)
|
||||
```
|
||||
|
||||
## C-ABI и потребление из Dart/C
|
||||
|
||||
Публичный C-заголовок — `include/agw/c_abi.h`. Shared-библиотека `libagw_capi.*` экспортирует только
|
||||
`agw_*`. Примеры:
|
||||
|
||||
```sh
|
||||
# чистый C
|
||||
cc -std=c11 -Iinclude examples/c_smoke/smoke.c -Lbuild-local -lagw_capi -o /tmp/agw_smoke
|
||||
DYLD_LIBRARY_PATH=build-local /tmp/agw_smoke # → код 1105, OK
|
||||
|
||||
# Dart (dart:ffi)
|
||||
cd examples/dart_smoke && dart pub get && dart run # → код 1105, OK
|
||||
```
|
||||
|
||||
## Локальная сборка и тесты (без Conan)
|
||||
|
||||
Нужны CMake ≥ 3.21 и OpenSSL 3. nlohmann/json берётся из вендоренного single-header
|
||||
(`tests/third_party`), если Conan-пакет не найден.
|
||||
|
||||
```sh
|
||||
cmake -S . -B build-local -DOPENSSL_ROOT_DIR=$(brew --prefix openssl@3)
|
||||
cmake --build build-local -j
|
||||
ctest --test-dir build-local --output-on-failure
|
||||
```
|
||||
|
||||
Санитайзеры (macOS): TSan — на конкурентных тестах; ASan+UBSan — `detect_leaks=0` (LSan на Darwin
|
||||
не поддержан):
|
||||
|
||||
```sh
|
||||
cmake -S . -B build-asan -DOPENSSL_ROOT_DIR=$(brew --prefix openssl@3) \
|
||||
-DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -g -O1" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined"
|
||||
cmake --build build-asan -j
|
||||
ASAN_OPTIONS=detect_leaks=0 ctest --test-dir build-asan --output-on-failure
|
||||
```
|
||||
|
||||
## Сборка через Conan (как в проекте)
|
||||
|
||||
```sh
|
||||
conan create . -o build_tests=True
|
||||
```
|
||||
|
||||
Зависимости: `openssl/3.6.2`, `nlohmann_json/3.11.3` (как в корневом `conanfile.py`); `libcurl` —
|
||||
с Фазы 2.
|
||||
|
||||
## Заметки по паритету
|
||||
|
||||
Крипта сверена с `client/3rd/QSimpleCrypto` и `gatewayController.cpp`. Ключевое:
|
||||
|
||||
- AES-256-CBC, ключ 32 байта, IV генерится 32 — CBC берёт первые 16; salt (8 байт) в локальном AES
|
||||
не участвует, уходит только в `key_payload`.
|
||||
- RSA PKCS#1 v1.5 — паддинг рандомный, поэтому `key_payload` **не** воспроизводим байт-в-байт;
|
||||
golden проверяет его round-trip, а `api_payload` (AES) — точные байты.
|
||||
- JSON собирается в формате `QJsonDocument::toJson(Indented)`: отступ 4 пробела, завершающий `\n`,
|
||||
**отсортированные ключи** (это даёт `aes_iv` раньше `aes_key`).
|
||||
65
agw-sdk/conanfile.py
Normal file
65
agw-sdk/conanfile.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from conan import ConanFile
|
||||
from conan.tools.cmake import CMake, CMakeToolchain, CMakeDeps, cmake_layout
|
||||
|
||||
|
||||
class AgwSdkConan(ConanFile):
|
||||
name = "agw-sdk"
|
||||
version = "0.1.0"
|
||||
license = "TBD"
|
||||
description = "AGW SDK — Qt-free C++ transport to the Amnezia API gateway (Tier 1)"
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
|
||||
# shared-deps: линкуем общий OpenSSL/curl/nlohmann из Conan (наши приложения).
|
||||
# vendored: бандлим зависимости статически + скрытие символов (сторонние/standalone).
|
||||
options = {
|
||||
"deps_mode": ["shared-deps", "vendored"],
|
||||
"build_tests": [True, False],
|
||||
"build_capi_shared": [True, False],
|
||||
}
|
||||
default_options = {
|
||||
"deps_mode": "shared-deps",
|
||||
"build_tests": False,
|
||||
"build_capi_shared": True,
|
||||
}
|
||||
|
||||
exports_sources = "CMakeLists.txt", "include/*", "src/*", "tests/*"
|
||||
|
||||
def requirements(self):
|
||||
# Версия OpenSSL совпадает с приложением (корневой conanfile.py) — без второго OpenSSL.
|
||||
self.requires("openssl/3.6.2")
|
||||
self.requires("libcurl/8.10.1")
|
||||
self.requires("nlohmann_json/3.11.3")
|
||||
|
||||
def configure(self):
|
||||
# vendored: тянем статические зависимости, чтобы забандлить их в библиотеку.
|
||||
if self.options.deps_mode == "vendored":
|
||||
self.options["openssl"].shared = False
|
||||
self.options["libcurl"].shared = False
|
||||
|
||||
def layout(self):
|
||||
cmake_layout(self)
|
||||
|
||||
def generate(self):
|
||||
deps = CMakeDeps(self)
|
||||
deps.generate()
|
||||
tc = CMakeToolchain(self)
|
||||
tc.variables["AGW_DEPS_MODE"] = str(self.options.deps_mode)
|
||||
tc.variables["AGW_BUILD_TESTS"] = bool(self.options.build_tests)
|
||||
tc.variables["AGW_BUILD_CAPI_SHARED"] = bool(self.options.build_capi_shared)
|
||||
tc.generate()
|
||||
|
||||
def build(self):
|
||||
cmake = CMake(self)
|
||||
cmake.configure()
|
||||
cmake.build()
|
||||
|
||||
def package(self):
|
||||
cmake = CMake(self)
|
||||
cmake.install()
|
||||
|
||||
def package_info(self):
|
||||
self.cpp_info.libs = ["agw"]
|
||||
self.cpp_info.includedirs = ["include"]
|
||||
# Потребитель подключает: find_package(agw-sdk) + target agw::agw
|
||||
self.cpp_info.set_property("cmake_file_name", "agw-sdk")
|
||||
self.cpp_info.set_property("cmake_target_name", "agw::agw")
|
||||
39
agw-sdk/examples/c_smoke/smoke.c
Normal file
39
agw-sdk/examples/c_smoke/smoke.c
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Чистый C-потребитель C-ABI: доказывает, что agw_* линкуется и работает из C без C++/Qt.
|
||||
* Детерминированный путь без сети: невалидный публичный ключ → ApiMissingAgwPublicKey (1105).
|
||||
*
|
||||
* Сборка (пример, macOS):
|
||||
* cc -std=c11 -I ../../include smoke.c -L ../../build-local -lagw_capi -o smoke
|
||||
* DYLD_LIBRARY_PATH=../../build-local ./smoke
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "agw/c_abi.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
agw_config cfg;
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
cfg.gateway_endpoint = "gw.example.test";
|
||||
cfg.agw_public_key_pem = "not a real pem key"; /* → 1105 без обращения к сети */
|
||||
cfg.request_timeout_msecs = 5000;
|
||||
|
||||
agw_client *client = agw_client_create(&cfg);
|
||||
if (client == NULL) {
|
||||
printf("FAIL: agw_client_create returned NULL\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
agw_response r = agw_client_post(client, "https://%1/api/v1/test", "{\"x\":1}", "", "", NULL);
|
||||
printf("post error code = %d\n", r.error);
|
||||
|
||||
int ok = (r.error == 1105); /* ApiMissingAgwPublicKey */
|
||||
|
||||
agw_response_free(&r);
|
||||
agw_client_destroy(client);
|
||||
|
||||
printf(ok ? "OK\n" : "FAIL\n");
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
235
agw-sdk/examples/dart_smoke/bin/smoke.dart
Normal file
235
agw-sdk/examples/dart_smoke/bin/smoke.dart
Normal file
@@ -0,0 +1,235 @@
|
||||
// Dart-демо C-ABI agw-sdk через dart:ffi.
|
||||
//
|
||||
// Показывает поток запроса: подключает лог-хук SDK и onBeforeRequest, поэтому печатаются строки
|
||||
// [agw] (post START -> direct request url -> direct response -> failover -> post DONE) — видно,
|
||||
// что запрос ушёл и ответ пришёл. Делает синхронный post, а при AGW_ASYNC=1 — ещё и асинхронный
|
||||
// (agw_client_post_async + NativeCallable.listener: коллбэк прилетает с потока пула SDK).
|
||||
//
|
||||
// Конфиг через переменные окружения (все опциональны):
|
||||
// AGW_GATEWAY хост шлюза с "%1"-подстановкой, напр. "http://gw.dev.amzsvc.com:80/"
|
||||
// AGW_PUBKEY_FILE путь к PEM ПУБЛИЧНОГО ключа шлюза (по умолчанию — тестовый фикстур)
|
||||
// AGW_S3_PRIMARY список S3-адресов через запятую (failover)
|
||||
// AGW_DEV "1" → dev-режим (S3-список открытым текстом)
|
||||
// AGW_ENDPOINT шаблон пути, по умолчанию "%1v1/services"
|
||||
// AGW_PAYLOAD JSON тела запроса
|
||||
// AGW_ASYNC "1" → дополнительно прогнать асинхронный вызов
|
||||
// AGW_CAPI_LIB путь к libagw_capi.* (иначе ../../build-local)
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
final class AgwConfig extends Struct {
|
||||
external Pointer<Utf8> gatewayEndpoint;
|
||||
external Pointer<Utf8> agwPublicKeyPem;
|
||||
external Pointer<Pointer<Utf8>> s3Primary;
|
||||
@Size()
|
||||
external int s3PrimaryCount;
|
||||
external Pointer<Pointer<Utf8>> s3Fallback;
|
||||
@Size()
|
||||
external int s3FallbackCount;
|
||||
@Int32()
|
||||
external int isDevEnvironment;
|
||||
@Int32()
|
||||
external int requestTimeoutMsecs;
|
||||
@Int32()
|
||||
external int proxyHealthTimeoutMsecs;
|
||||
@Int32()
|
||||
external int proxyStorageTimeoutMsecs;
|
||||
@Int32()
|
||||
external int threadPoolSize;
|
||||
external Pointer<Void> onBeforeRequest;
|
||||
external Pointer<Void> onBeforeRequestUserData;
|
||||
external Pointer<Void> log;
|
||||
external Pointer<Void> logUserData;
|
||||
}
|
||||
|
||||
final class AgwResponse extends Struct {
|
||||
@Int32()
|
||||
external int error;
|
||||
external Pointer<Utf8> body;
|
||||
@Size()
|
||||
external int bodyLen;
|
||||
}
|
||||
|
||||
typedef _CreateC = Pointer<Void> Function(Pointer<AgwConfig>);
|
||||
typedef _PostC = AgwResponse Function(Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>,
|
||||
Pointer<Utf8>, Pointer<Utf8>, Pointer<Void>);
|
||||
typedef _PostAsyncC = Void Function(Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>,
|
||||
Pointer<Utf8>, Pointer<Utf8>, Pointer<Void>, Pointer<Void>, Pointer<Void>);
|
||||
typedef _PostAsyncDart = void Function(Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>,
|
||||
Pointer<Utf8>, Pointer<Utf8>, Pointer<Void>, Pointer<Void>, Pointer<Void>);
|
||||
typedef _FreeC = Void Function(Pointer<AgwResponse>);
|
||||
typedef _FreeDart = void Function(Pointer<AgwResponse>);
|
||||
typedef _DestroyC = Void Function(Pointer<Void>);
|
||||
typedef _DestroyDart = void Function(Pointer<Void>);
|
||||
|
||||
typedef _LogNative = Void Function(Int32, Pointer<Utf8>, Pointer<Void>);
|
||||
typedef _BeforeNative = Void Function(Pointer<Utf8>, Pointer<Void>);
|
||||
typedef _PostCbNative = Void Function(AgwResponse, Pointer<Void>);
|
||||
|
||||
const _levels = ['DBG', 'INF', 'WRN', 'ERR'];
|
||||
|
||||
void _printLog(String tag, int level, Pointer<Utf8> message) {
|
||||
final lvl = (level >= 0 && level < _levels.length) ? _levels[level] : '?';
|
||||
stdout.writeln(' $tag [agw][$lvl] ${message.toDartString()}');
|
||||
}
|
||||
|
||||
class _Cfg {
|
||||
final Pointer<AgwConfig> ptr;
|
||||
final List<Pointer<NativeType>> allocs;
|
||||
_Cfg(this.ptr, this.allocs);
|
||||
void free() {
|
||||
for (final p in allocs) {
|
||||
calloc.free(p);
|
||||
}
|
||||
calloc.free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
String _libPath() {
|
||||
final env = Platform.environment['AGW_CAPI_LIB'];
|
||||
if (env != null) return env;
|
||||
final base = '${Directory.current.path}/../../build-local';
|
||||
if (Platform.isMacOS) return '$base/libagw_capi.dylib';
|
||||
if (Platform.isWindows) return '$base/agw_capi.dll';
|
||||
return '$base/libagw_capi.so';
|
||||
}
|
||||
|
||||
String _defaultPubKey() {
|
||||
final f = File('${Directory.current.path}/../../tests/golden/fixtures/test_rsa_pub.pem');
|
||||
return f.existsSync() ? f.readAsStringSync() : 'not a real pem key';
|
||||
}
|
||||
|
||||
late String gateway, pubKey, payload;
|
||||
late int isDev;
|
||||
late List<String> s3List;
|
||||
|
||||
_Cfg buildConfig(Pointer<Void> logFn, Pointer<Void> beforeFn) {
|
||||
final cfg = calloc<AgwConfig>();
|
||||
final allocs = <Pointer<NativeType>>[];
|
||||
|
||||
final gw = gateway.toNativeUtf8();
|
||||
final pk = pubKey.toNativeUtf8();
|
||||
allocs.add(gw);
|
||||
allocs.add(pk);
|
||||
cfg.ref.gatewayEndpoint = gw;
|
||||
cfg.ref.agwPublicKeyPem = pk;
|
||||
cfg.ref.requestTimeoutMsecs = 8000;
|
||||
cfg.ref.isDevEnvironment = isDev;
|
||||
cfg.ref.onBeforeRequest = beforeFn;
|
||||
cfg.ref.log = logFn;
|
||||
|
||||
if (s3List.isNotEmpty) {
|
||||
final arr = calloc<Pointer<Utf8>>(s3List.length);
|
||||
for (var i = 0; i < s3List.length; i++) {
|
||||
arr[i] = s3List[i].toNativeUtf8();
|
||||
allocs.add(arr[i]);
|
||||
}
|
||||
cfg.ref.s3Primary = arr;
|
||||
cfg.ref.s3PrimaryCount = s3List.length;
|
||||
allocs.add(arr);
|
||||
}
|
||||
return _Cfg(cfg, allocs);
|
||||
}
|
||||
|
||||
Future<int> main() async {
|
||||
final env = Platform.environment;
|
||||
final lib = DynamicLibrary.open(_libPath());
|
||||
final create = lib.lookupFunction<_CreateC, _CreateC>('agw_client_create');
|
||||
final post = lib.lookupFunction<_PostC, _PostC>('agw_client_post');
|
||||
final postAsync = lib.lookupFunction<_PostAsyncC, _PostAsyncDart>('agw_client_post_async');
|
||||
final free = lib.lookupFunction<_FreeC, _FreeDart>('agw_response_free');
|
||||
final destroy = lib.lookupFunction<_DestroyC, _DestroyDart>('agw_client_destroy');
|
||||
|
||||
gateway = env['AGW_GATEWAY'] ?? 'http://gw.example.test/';
|
||||
final pubKeyFile = env['AGW_PUBKEY_FILE'];
|
||||
pubKey = pubKeyFile != null ? File(pubKeyFile).readAsStringSync() : _defaultPubKey();
|
||||
final endpoint = env['AGW_ENDPOINT'] ?? '%1v1/services';
|
||||
payload = env['AGW_PAYLOAD'] ??
|
||||
'{"os_version":"macos","app_version":"4.9.0","cli_name":"amnezia","app_language":"en"}';
|
||||
isDev = (env['AGW_DEV'] == '1') ? 1 : 0;
|
||||
s3List = (env['AGW_S3_PRIMARY'] ?? '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toList();
|
||||
|
||||
stdout.writeln('=== agw-sdk Dart demo ===');
|
||||
stdout.writeln('gateway=$gateway endpoint=$endpoint dev=$isDev s3primary=${s3List.length}');
|
||||
stdout.writeln('pubkey=${pubKeyFile ?? "(test fixture)"}');
|
||||
|
||||
final endpointC = endpoint.toNativeUtf8();
|
||||
final payloadC = payload.toNativeUtf8();
|
||||
final svc = ''.toNativeUtf8();
|
||||
|
||||
// ---------- SYNC (коллбэки isolateLocal: ядро sync исполняется на этом потоке) ----------
|
||||
stdout.writeln('\n--- SYNC post ---');
|
||||
final logSync = NativeCallable<_LogNative>.isolateLocal(
|
||||
(int lvl, Pointer<Utf8> m, Pointer<Void> _) => _printLog('[sync]', lvl, m));
|
||||
final beforeSync = NativeCallable<_BeforeNative>.isolateLocal(
|
||||
(Pointer<Utf8> h, Pointer<Void> _) =>
|
||||
stdout.writeln(' [sync] → onBeforeRequest host=${h.toDartString()}'));
|
||||
|
||||
final cfgSync = buildConfig(logSync.nativeFunction.cast(), beforeSync.nativeFunction.cast());
|
||||
final clientSync = create(cfgSync.ptr);
|
||||
final resp = post(clientSync, endpointC, payloadC, svc, svc, nullptr);
|
||||
stdout.writeln(' [sync] RESULT errorCode=${resp.error} bodyLen=${resp.bodyLen}');
|
||||
if (resp.body != nullptr && resp.bodyLen > 0) {
|
||||
final body = resp.body.toDartString(length: resp.bodyLen);
|
||||
stdout.writeln(' [sync] body=${body.length > 200 ? "${body.substring(0, 200)}…" : body}');
|
||||
}
|
||||
final rp = calloc<AgwResponse>()
|
||||
..ref.error = resp.error
|
||||
..ref.body = resp.body
|
||||
..ref.bodyLen = resp.bodyLen;
|
||||
free(rp);
|
||||
calloc.free(rp);
|
||||
destroy(clientSync);
|
||||
cfgSync.free();
|
||||
logSync.close();
|
||||
beforeSync.close();
|
||||
|
||||
// ---------- ASYNC (коллбэки listener: прилетают с потока пула SDK) ----------
|
||||
if (env['AGW_ASYNC'] == '1') {
|
||||
stdout.writeln('\n--- ASYNC post (коллбэк с потока пула) ---');
|
||||
// ВАЖНО: лог-хук/onBeforeRequest сюда НЕ вешаем. Их const char* живут лишь во время вызова на
|
||||
// потоке пула, а NativeCallable.listener выполняется позже на Dart event-loop → указатель был бы
|
||||
// висячим. Result-коллбэк безопасен: body выделен в куче и принадлежит вызывающему (мы его освобождаем).
|
||||
final done = Completer<void>();
|
||||
|
||||
final resultCb = NativeCallable<_PostCbNative>.listener((AgwResponse r, Pointer<Void> _) {
|
||||
stdout.writeln(' [async] CALLBACK (прилетел с потока пула) errorCode=${r.error} bodyLen=${r.bodyLen}');
|
||||
if (r.body != nullptr && r.bodyLen > 0) {
|
||||
final body = r.body.toDartString(length: r.bodyLen);
|
||||
stdout.writeln(' [async] body=${body.length > 200 ? "${body.substring(0, 200)}…" : body}');
|
||||
}
|
||||
final p = calloc<AgwResponse>()
|
||||
..ref.error = r.error
|
||||
..ref.body = r.body
|
||||
..ref.bodyLen = r.bodyLen;
|
||||
free(p);
|
||||
calloc.free(p);
|
||||
done.complete();
|
||||
});
|
||||
|
||||
final cfgAsync = buildConfig(nullptr, nullptr); // без лог/before-хуков (см. выше)
|
||||
final clientAsync = create(cfgAsync.ptr);
|
||||
stdout.writeln(' [async] post_async отправлен, ждём коллбэк…');
|
||||
postAsync(clientAsync, endpointC, payloadC, svc, svc, resultCb.nativeFunction.cast(),
|
||||
nullptr, nullptr);
|
||||
|
||||
await done.future; // ждём, пока коллбэк прилетит с потока пула
|
||||
destroy(clientAsync);
|
||||
cfgAsync.free();
|
||||
resultCb.close();
|
||||
}
|
||||
|
||||
calloc.free(endpointC);
|
||||
calloc.free(payloadC);
|
||||
calloc.free(svc);
|
||||
stdout.writeln('\n=== done ===');
|
||||
return 0;
|
||||
}
|
||||
9
agw-sdk/examples/dart_smoke/pubspec.yaml
Normal file
9
agw-sdk/examples/dart_smoke/pubspec.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
name: agw_dart_smoke
|
||||
description: Smoke-вызов C-ABI agw-sdk через dart:ffi.
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
ffi: ^2.1.0
|
||||
10
agw-sdk/include/agw/agw.h
Normal file
10
agw-sdk/include/agw/agw.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef AGW_AGW_H
|
||||
#define AGW_AGW_H
|
||||
|
||||
#include "agw/cancellation.h"
|
||||
#include "agw/gateway_controller.h"
|
||||
#include "agw/config.h"
|
||||
#include "agw/http.h"
|
||||
#include "agw/types.h"
|
||||
|
||||
#endif
|
||||
80
agw-sdk/include/agw/c_abi.h
Normal file
80
agw-sdk/include/agw/c_abi.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#ifndef AGW_C_ABI_H
|
||||
#define AGW_C_ABI_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#if defined(AGW_BUILDING_SHARED)
|
||||
#define AGW_API __declspec(dllexport)
|
||||
#elif defined(AGW_USING_SHARED)
|
||||
#define AGW_API __declspec(dllimport)
|
||||
#else
|
||||
#define AGW_API
|
||||
#endif
|
||||
#else
|
||||
#define AGW_API __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
typedef struct agw_client agw_client;
|
||||
typedef struct agw_cancel_token agw_cancel_token;
|
||||
|
||||
typedef void (*agw_before_request_fn)(const char *host, void *user_data);
|
||||
typedef void (*agw_log_fn)(int level, const char *message, void *user_data);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const char *gateway_endpoint;
|
||||
const char *agw_public_key_pem;
|
||||
|
||||
const char *const *s3_primary_endpoints;
|
||||
size_t s3_primary_count;
|
||||
const char *const *s3_fallback_endpoints;
|
||||
size_t s3_fallback_count;
|
||||
|
||||
int is_dev_environment;
|
||||
int request_timeout_msecs;
|
||||
int proxy_health_timeout_msecs;
|
||||
int proxy_storage_timeout_msecs;
|
||||
int thread_pool_size;
|
||||
|
||||
agw_before_request_fn on_before_request;
|
||||
void *on_before_request_user_data;
|
||||
agw_log_fn log;
|
||||
void *log_user_data;
|
||||
} agw_config;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int error;
|
||||
char *body;
|
||||
size_t body_len;
|
||||
} agw_response;
|
||||
|
||||
typedef void (*agw_post_callback)(agw_response response, void *user_data);
|
||||
|
||||
AGW_API agw_client *agw_client_create(const agw_config *config);
|
||||
AGW_API void agw_client_destroy(agw_client *client);
|
||||
|
||||
AGW_API agw_response agw_client_post(agw_client *client, const char *endpoint, const char *payload,
|
||||
const char *service_type, const char *user_country_code,
|
||||
agw_cancel_token *cancel_token);
|
||||
|
||||
AGW_API void agw_client_post_async(agw_client *client, const char *endpoint, const char *payload,
|
||||
const char *service_type, const char *user_country_code, agw_post_callback callback,
|
||||
void *user_data, agw_cancel_token *cancel_token);
|
||||
|
||||
AGW_API void agw_response_free(agw_response *response);
|
||||
|
||||
AGW_API agw_cancel_token *agw_cancel_token_create(void);
|
||||
AGW_API void agw_cancel_token_cancel(agw_cancel_token *token);
|
||||
AGW_API void agw_cancel_token_destroy(agw_cancel_token *token);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
30
agw-sdk/include/agw/cancellation.h
Normal file
30
agw-sdk/include/agw/cancellation.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef AGW_CANCELLATION_H
|
||||
#define AGW_CANCELLATION_H
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace agw
|
||||
{
|
||||
class CancellationToken
|
||||
{
|
||||
public:
|
||||
CancellationToken() = default;
|
||||
|
||||
CancellationToken(const CancellationToken &) = delete;
|
||||
CancellationToken &operator=(const CancellationToken &) = delete;
|
||||
|
||||
void cancel() noexcept
|
||||
{
|
||||
m_cancelled.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
bool isCancelled() const noexcept
|
||||
{
|
||||
return m_cancelled.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<bool> m_cancelled { false };
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
38
agw-sdk/include/agw/config.h
Normal file
38
agw-sdk/include/agw/config.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef AGW_CONFIG_H
|
||||
#define AGW_CONFIG_H
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "agw/http.h"
|
||||
#include "agw/types.h"
|
||||
|
||||
namespace agw
|
||||
{
|
||||
struct Config
|
||||
{
|
||||
std::string gatewayEndpoint;
|
||||
|
||||
std::string agwPublicKeyPem;
|
||||
|
||||
std::vector<std::string> s3PrimaryEndpoints;
|
||||
std::vector<std::string> s3FallbackEndpoints;
|
||||
|
||||
bool isDevEnvironment = false;
|
||||
|
||||
int requestTimeoutMsecs = 12000;
|
||||
int proxyHealthTimeoutMsecs = 1000;
|
||||
int proxyStorageTimeoutMsecs = 3000;
|
||||
int threadPoolSize = 4;
|
||||
|
||||
std::function<void(const std::string &host)> onBeforeRequest;
|
||||
|
||||
std::function<void(LogLevel, const std::string &message)> log;
|
||||
|
||||
std::shared_ptr<IHttpClient> httpClient;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
40
agw-sdk/include/agw/gateway_controller.h
Normal file
40
agw-sdk/include/agw/gateway_controller.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef AGW_GATEWAY_CONTROLLER_H
|
||||
#define AGW_GATEWAY_CONTROLLER_H
|
||||
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "agw/cancellation.h"
|
||||
#include "agw/config.h"
|
||||
#include "agw/types.h"
|
||||
|
||||
namespace agw {
|
||||
class GatewayController {
|
||||
public:
|
||||
explicit GatewayController(Config config);
|
||||
~GatewayController();
|
||||
|
||||
GatewayController(GatewayController &&) noexcept;
|
||||
GatewayController &operator=(GatewayController &&) noexcept;
|
||||
GatewayController(const GatewayController &) = delete;
|
||||
GatewayController &operator=(const GatewayController &) = delete;
|
||||
|
||||
Response post(const std::string &endpoint, const std::string &payload, const FailoverContext &ctx,
|
||||
CancellationToken *cancel = nullptr);
|
||||
|
||||
void postAsync(const std::string &endpoint, const std::string &payload,
|
||||
std::function<void(Response)> onResult, const FailoverContext &ctx,
|
||||
CancellationToken *cancel = nullptr);
|
||||
|
||||
std::future<Response> postFuture(const std::string &endpoint, const std::string &payload,
|
||||
const FailoverContext &ctx, CancellationToken *cancel = nullptr);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
51
agw-sdk/include/agw/http.h
Normal file
51
agw-sdk/include/agw/http.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef AGW_HTTP_H
|
||||
#define AGW_HTTP_H
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace agw
|
||||
{
|
||||
enum class TransportError {
|
||||
None = 0,
|
||||
Timeout,
|
||||
Canceled,
|
||||
OperationNotImplemented,
|
||||
ConnectionError,
|
||||
};
|
||||
|
||||
struct HttpRequest
|
||||
{
|
||||
std::string url;
|
||||
std::string method;
|
||||
std::string body;
|
||||
std::vector<std::pair<std::string, std::string>> headers;
|
||||
int timeoutMsecs = 0;
|
||||
|
||||
std::function<bool()> cancelCheck;
|
||||
};
|
||||
|
||||
struct HttpResponse
|
||||
{
|
||||
TransportError error = TransportError::None;
|
||||
std::string errorString;
|
||||
int httpStatusCode = 0;
|
||||
|
||||
bool sslError = false;
|
||||
std::string body;
|
||||
};
|
||||
|
||||
class IHttpClient
|
||||
{
|
||||
public:
|
||||
virtual ~IHttpClient() = default;
|
||||
virtual HttpResponse send(const HttpRequest &request) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IHttpClient> makeDefaultHttpClient();
|
||||
}
|
||||
|
||||
#endif
|
||||
55
agw-sdk/include/agw/types.h
Normal file
55
agw-sdk/include/agw/types.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifndef AGW_TYPES_H
|
||||
#define AGW_TYPES_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace agw
|
||||
{
|
||||
enum class ErrorCode : int {
|
||||
NoError = 0,
|
||||
Cancelled = 1,
|
||||
|
||||
ApiConfigDownloadError = 1100,
|
||||
ApiConfigAlreadyAdded = 1101,
|
||||
ApiConfigEmptyError = 1102,
|
||||
ApiConfigTimeoutError = 1103,
|
||||
ApiConfigSslError = 1104,
|
||||
ApiMissingAgwPublicKey = 1105,
|
||||
ApiConfigDecryptionError = 1106,
|
||||
ApiServicesMissingError = 1107,
|
||||
ApiConfigLimitError = 1108,
|
||||
ApiNotFoundError = 1109,
|
||||
ApiMigrationError = 1110,
|
||||
ApiUpdateRequestError = 1111,
|
||||
ApiSubscriptionExpiredError = 1112,
|
||||
ApiPurchaseError = 1113,
|
||||
ApiSubscriptionNotActiveError = 1114,
|
||||
ApiNoPurchasedSubscriptionsError = 1115,
|
||||
ApiTrialAlreadyUsedError = 1116,
|
||||
ApiCaptchaRequiredError = 1117,
|
||||
ApiCaptchaInvalidError = 1118,
|
||||
ApiCaptchaRefreshError = 1119,
|
||||
ApiRateLimitError = 1120,
|
||||
};
|
||||
|
||||
enum class LogLevel : int {
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
};
|
||||
|
||||
struct Response
|
||||
{
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
std::string body;
|
||||
};
|
||||
|
||||
struct FailoverContext
|
||||
{
|
||||
std::string serviceType;
|
||||
std::string userCountryCode;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
201
agw-sdk/src/c_abi.cpp
Normal file
201
agw-sdk/src/c_abi.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#include "agw/c_abi.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "agw/cancellation.h"
|
||||
#include "agw/gateway_controller.h"
|
||||
#include "agw/config.h"
|
||||
#include "detail/test_hooks.h"
|
||||
|
||||
struct agw_client
|
||||
{
|
||||
explicit agw_client(agw::Config cfg) : client(std::move(cfg))
|
||||
{
|
||||
}
|
||||
agw::GatewayController client;
|
||||
};
|
||||
|
||||
struct agw_cancel_token
|
||||
{
|
||||
agw::CancellationToken token;
|
||||
};
|
||||
|
||||
namespace agw::detail
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::mutex g_testHttpMutex;
|
||||
std::shared_ptr<IHttpClient> g_testHttp;
|
||||
}
|
||||
|
||||
void setNextTestHttpClient(std::shared_ptr<IHttpClient> http)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_testHttpMutex);
|
||||
g_testHttp = std::move(http);
|
||||
}
|
||||
|
||||
std::shared_ptr<IHttpClient> takeNextTestHttpClient()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_testHttpMutex);
|
||||
std::shared_ptr<IHttpClient> h = std::move(g_testHttp);
|
||||
g_testHttp.reset();
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string cstr(const char *s)
|
||||
{
|
||||
return s ? std::string(s) : std::string();
|
||||
}
|
||||
|
||||
agw_response toCResponse(const agw::Response &r)
|
||||
{
|
||||
agw_response out;
|
||||
out.error = static_cast<int>(r.error);
|
||||
out.body = nullptr;
|
||||
out.body_len = r.body.size();
|
||||
|
||||
char *buf = static_cast<char *>(std::malloc(r.body.size() + 1));
|
||||
if (buf != nullptr) {
|
||||
if (!r.body.empty()) {
|
||||
std::memcpy(buf, r.body.data(), r.body.size());
|
||||
}
|
||||
buf[r.body.size()] = '\0';
|
||||
out.body = buf;
|
||||
} else {
|
||||
out.body_len = 0;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
agw_client *agw_client_create(const agw_config *config)
|
||||
{
|
||||
if (config == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
agw::Config cfg;
|
||||
cfg.gatewayEndpoint = cstr(config->gateway_endpoint);
|
||||
cfg.agwPublicKeyPem = cstr(config->agw_public_key_pem);
|
||||
|
||||
for (size_t i = 0; i < config->s3_primary_count; ++i) {
|
||||
cfg.s3PrimaryEndpoints.push_back(cstr(config->s3_primary_endpoints[i]));
|
||||
}
|
||||
for (size_t i = 0; i < config->s3_fallback_count; ++i) {
|
||||
cfg.s3FallbackEndpoints.push_back(cstr(config->s3_fallback_endpoints[i]));
|
||||
}
|
||||
|
||||
cfg.isDevEnvironment = config->is_dev_environment != 0;
|
||||
if (config->request_timeout_msecs > 0)
|
||||
cfg.requestTimeoutMsecs = config->request_timeout_msecs;
|
||||
if (config->proxy_health_timeout_msecs > 0)
|
||||
cfg.proxyHealthTimeoutMsecs = config->proxy_health_timeout_msecs;
|
||||
if (config->proxy_storage_timeout_msecs > 0)
|
||||
cfg.proxyStorageTimeoutMsecs = config->proxy_storage_timeout_msecs;
|
||||
if (config->thread_pool_size > 0)
|
||||
cfg.threadPoolSize = config->thread_pool_size;
|
||||
|
||||
if (config->on_before_request != nullptr) {
|
||||
agw_before_request_fn fn = config->on_before_request;
|
||||
void *ud = config->on_before_request_user_data;
|
||||
cfg.onBeforeRequest = [fn, ud](const std::string &host) { fn(host.c_str(), ud); };
|
||||
}
|
||||
if (config->log != nullptr) {
|
||||
agw_log_fn fn = config->log;
|
||||
void *ud = config->log_user_data;
|
||||
cfg.log = [fn, ud](agw::LogLevel level, const std::string &msg) { fn(static_cast<int>(level), msg.c_str(), ud); };
|
||||
}
|
||||
|
||||
if (auto http = agw::detail::takeNextTestHttpClient()) {
|
||||
cfg.httpClient = http;
|
||||
}
|
||||
|
||||
try {
|
||||
return new agw_client(std::move(cfg));
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void agw_client_destroy(agw_client *client)
|
||||
{
|
||||
delete client;
|
||||
}
|
||||
|
||||
agw_response agw_client_post(agw_client *client, const char *endpoint, const char *payload, const char *service_type,
|
||||
const char *user_country_code, agw_cancel_token *cancel_token)
|
||||
{
|
||||
if (client == nullptr) {
|
||||
agw_response out;
|
||||
out.error = static_cast<int>(agw::ErrorCode::ApiConfigDownloadError);
|
||||
out.body = nullptr;
|
||||
out.body_len = 0;
|
||||
return out;
|
||||
}
|
||||
|
||||
agw::FailoverContext ctx { cstr(service_type), cstr(user_country_code) };
|
||||
agw::CancellationToken *tk = cancel_token ? &cancel_token->token : nullptr;
|
||||
agw::Response r = client->client.post(cstr(endpoint), cstr(payload), ctx, tk);
|
||||
return toCResponse(r);
|
||||
}
|
||||
|
||||
void agw_client_post_async(agw_client *client, const char *endpoint, const char *payload, const char *service_type,
|
||||
const char *user_country_code, agw_post_callback callback, void *user_data,
|
||||
agw_cancel_token *cancel_token)
|
||||
{
|
||||
if (client == nullptr || callback == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
agw::FailoverContext ctx { cstr(service_type), cstr(user_country_code) };
|
||||
agw::CancellationToken *tk = cancel_token ? &cancel_token->token : nullptr;
|
||||
|
||||
client->client.postAsync(
|
||||
cstr(endpoint), cstr(payload),
|
||||
[callback, user_data](agw::Response r) {
|
||||
agw_response cr = toCResponse(r);
|
||||
callback(cr, user_data);
|
||||
},
|
||||
ctx, tk);
|
||||
}
|
||||
|
||||
void agw_response_free(agw_response *response)
|
||||
{
|
||||
if (response == nullptr) {
|
||||
return;
|
||||
}
|
||||
std::free(response->body);
|
||||
response->body = nullptr;
|
||||
response->body_len = 0;
|
||||
}
|
||||
|
||||
agw_cancel_token *agw_cancel_token_create(void)
|
||||
{
|
||||
try {
|
||||
return new agw_cancel_token();
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void agw_cancel_token_cancel(agw_cancel_token *token)
|
||||
{
|
||||
if (token != nullptr) {
|
||||
token->token.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void agw_cancel_token_destroy(agw_cancel_token *token)
|
||||
{
|
||||
delete token;
|
||||
}
|
||||
}
|
||||
87
agw-sdk/src/crypto/aes.cpp
Normal file
87
agw-sdk/src/crypto/aes.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "aes.h"
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
|
||||
namespace agw::crypto
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using CtxPtr = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>;
|
||||
|
||||
constexpr int kAes256KeyLen = 32;
|
||||
constexpr int kAesBlock = 16;
|
||||
|
||||
void checkKeyIv(const std::vector<std::uint8_t> &key, const std::vector<std::uint8_t> &iv)
|
||||
{
|
||||
if (key.size() != static_cast<std::size_t>(kAes256KeyLen)) {
|
||||
throw std::runtime_error("agw::crypto::aes: key must be 32 bytes (AES-256)");
|
||||
}
|
||||
if (iv.size() < static_cast<std::size_t>(kAesBlock)) {
|
||||
throw std::runtime_error("agw::crypto::aes: iv must be at least 16 bytes");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> aesEncryptCbc(const std::vector<std::uint8_t> &data, const std::vector<std::uint8_t> &key,
|
||||
const std::vector<std::uint8_t> &iv)
|
||||
{
|
||||
checkKeyIv(key, iv);
|
||||
|
||||
CtxPtr ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
|
||||
if (!ctx) {
|
||||
throw std::runtime_error("agw::crypto::aes: EVP_CIPHER_CTX_new failed");
|
||||
}
|
||||
|
||||
if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_cbc(), nullptr, key.data(), iv.data()) != 1) {
|
||||
throw std::runtime_error("agw::crypto::aes: EVP_EncryptInit_ex failed");
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> out(data.size() + kAesBlock);
|
||||
int len = 0;
|
||||
if (EVP_EncryptUpdate(ctx.get(), out.data(), &len, data.data(), static_cast<int>(data.size())) != 1) {
|
||||
throw std::runtime_error("agw::crypto::aes: EVP_EncryptUpdate failed");
|
||||
}
|
||||
int total = len;
|
||||
|
||||
if (EVP_EncryptFinal_ex(ctx.get(), out.data() + total, &len) != 1) {
|
||||
throw std::runtime_error("agw::crypto::aes: EVP_EncryptFinal_ex failed");
|
||||
}
|
||||
total += len;
|
||||
|
||||
out.resize(static_cast<std::size_t>(total));
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> aesDecryptCbc(const std::vector<std::uint8_t> &data, const std::vector<std::uint8_t> &key,
|
||||
const std::vector<std::uint8_t> &iv)
|
||||
{
|
||||
checkKeyIv(key, iv);
|
||||
|
||||
CtxPtr ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
|
||||
if (!ctx) {
|
||||
throw std::runtime_error("agw::crypto::aes: EVP_CIPHER_CTX_new failed");
|
||||
}
|
||||
|
||||
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_256_cbc(), nullptr, key.data(), iv.data()) != 1) {
|
||||
throw std::runtime_error("agw::crypto::aes: EVP_DecryptInit_ex failed");
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> out(data.size() + kAesBlock);
|
||||
int len = 0;
|
||||
if (EVP_DecryptUpdate(ctx.get(), out.data(), &len, data.data(), static_cast<int>(data.size())) != 1) {
|
||||
throw std::runtime_error("agw::crypto::aes: EVP_DecryptUpdate failed");
|
||||
}
|
||||
int total = len;
|
||||
|
||||
if (EVP_DecryptFinal_ex(ctx.get(), out.data() + total, &len) != 1) {
|
||||
throw std::runtime_error("agw::crypto::aes: EVP_DecryptFinal_ex failed (bad key/iv/padding)");
|
||||
}
|
||||
total += len;
|
||||
|
||||
out.resize(static_cast<std::size_t>(total));
|
||||
return out;
|
||||
}
|
||||
}
|
||||
16
agw-sdk/src/crypto/aes.h
Normal file
16
agw-sdk/src/crypto/aes.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef AGW_CRYPTO_AES_H
|
||||
#define AGW_CRYPTO_AES_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace agw::crypto
|
||||
{
|
||||
std::vector<std::uint8_t> aesEncryptCbc(const std::vector<std::uint8_t> &data, const std::vector<std::uint8_t> &key,
|
||||
const std::vector<std::uint8_t> &iv);
|
||||
|
||||
std::vector<std::uint8_t> aesDecryptCbc(const std::vector<std::uint8_t> &data, const std::vector<std::uint8_t> &key,
|
||||
const std::vector<std::uint8_t> &iv);
|
||||
}
|
||||
|
||||
#endif
|
||||
59
agw-sdk/src/crypto/hash.cpp
Normal file
59
agw-sdk/src/crypto/hash.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "hash.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <openssl/sha.h>
|
||||
|
||||
namespace agw::crypto
|
||||
{
|
||||
namespace
|
||||
{
|
||||
int hexNibble(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> sha512(const std::vector<std::uint8_t> &data)
|
||||
{
|
||||
std::vector<std::uint8_t> out(SHA512_DIGEST_LENGTH);
|
||||
SHA512(data.data(), data.size(), out.data());
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string toHex(const std::vector<std::uint8_t> &data)
|
||||
{
|
||||
static const char *digits = "0123456789abcdef";
|
||||
std::string out;
|
||||
out.reserve(data.size() * 2);
|
||||
for (std::uint8_t b : data) {
|
||||
out.push_back(digits[b >> 4]);
|
||||
out.push_back(digits[b & 0x0F]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> fromHex(const std::string &hex)
|
||||
{
|
||||
if (hex.size() % 2 != 0) {
|
||||
throw std::runtime_error("agw::crypto::fromHex: odd-length input");
|
||||
}
|
||||
std::vector<std::uint8_t> out;
|
||||
out.reserve(hex.size() / 2);
|
||||
for (std::size_t i = 0; i < hex.size(); i += 2) {
|
||||
const int hi = hexNibble(hex[i]);
|
||||
const int lo = hexNibble(hex[i + 1]);
|
||||
if (hi < 0 || lo < 0) {
|
||||
throw std::runtime_error("agw::crypto::fromHex: invalid hex character");
|
||||
}
|
||||
out.push_back(static_cast<std::uint8_t>((hi << 4) | lo));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
17
agw-sdk/src/crypto/hash.h
Normal file
17
agw-sdk/src/crypto/hash.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef AGW_CRYPTO_HASH_H
|
||||
#define AGW_CRYPTO_HASH_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace agw::crypto
|
||||
{
|
||||
std::vector<std::uint8_t> sha512(const std::vector<std::uint8_t> &data);
|
||||
|
||||
std::string toHex(const std::vector<std::uint8_t> &data);
|
||||
|
||||
std::vector<std::uint8_t> fromHex(const std::string &hex);
|
||||
}
|
||||
|
||||
#endif
|
||||
20
agw-sdk/src/crypto/rng.cpp
Normal file
20
agw-sdk/src/crypto/rng.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include "rng.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <openssl/rand.h>
|
||||
|
||||
namespace agw::crypto
|
||||
{
|
||||
std::vector<std::uint8_t> DefaultRng::bytes(std::size_t n)
|
||||
{
|
||||
std::vector<std::uint8_t> out(n);
|
||||
if (n == 0) {
|
||||
return out;
|
||||
}
|
||||
if (RAND_priv_bytes(out.data(), static_cast<int>(n)) != 1) {
|
||||
throw std::runtime_error("agw::crypto::DefaultRng: RAND_priv_bytes failed");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
24
agw-sdk/src/crypto/rng.h
Normal file
24
agw-sdk/src/crypto/rng.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef AGW_CRYPTO_RNG_H
|
||||
#define AGW_CRYPTO_RNG_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace agw::crypto
|
||||
{
|
||||
class IRng
|
||||
{
|
||||
public:
|
||||
virtual ~IRng() = default;
|
||||
virtual std::vector<std::uint8_t> bytes(std::size_t n) = 0;
|
||||
};
|
||||
|
||||
class DefaultRng : public IRng
|
||||
{
|
||||
public:
|
||||
std::vector<std::uint8_t> bytes(std::size_t n) override;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
111
agw-sdk/src/crypto/rsa.cpp
Normal file
111
agw-sdk/src/crypto/rsa.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "rsa.h"
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
namespace agw::crypto
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using BioPtr = std::unique_ptr<BIO, decltype(&BIO_free)>;
|
||||
using PkeyPtr = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>;
|
||||
using PkeyCtxPtr = std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)>;
|
||||
|
||||
PkeyPtr loadPublicKey(const std::string &pem)
|
||||
{
|
||||
BioPtr bio(BIO_new_mem_buf(pem.data(), static_cast<int>(pem.size())), BIO_free);
|
||||
if (!bio) {
|
||||
throw std::runtime_error("agw::crypto::rsa: BIO_new_mem_buf failed");
|
||||
}
|
||||
EVP_PKEY *raw = nullptr;
|
||||
if (!PEM_read_bio_PUBKEY(bio.get(), &raw, nullptr, nullptr)) {
|
||||
throw std::runtime_error("agw::crypto::rsa: PEM_read_bio_PUBKEY failed");
|
||||
}
|
||||
return PkeyPtr(raw, EVP_PKEY_free);
|
||||
}
|
||||
|
||||
PkeyPtr loadPrivateKey(const std::string &pem)
|
||||
{
|
||||
BioPtr bio(BIO_new_mem_buf(pem.data(), static_cast<int>(pem.size())), BIO_free);
|
||||
if (!bio) {
|
||||
throw std::runtime_error("agw::crypto::rsa: BIO_new_mem_buf failed");
|
||||
}
|
||||
EVP_PKEY *raw = nullptr;
|
||||
if (!PEM_read_bio_PrivateKey(bio.get(), &raw, nullptr, nullptr)) {
|
||||
throw std::runtime_error("agw::crypto::rsa: PEM_read_bio_PrivateKey failed");
|
||||
}
|
||||
return PkeyPtr(raw, EVP_PKEY_free);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> rsaEncryptPublicPkcs1(const std::vector<std::uint8_t> &plaintext,
|
||||
const std::string &publicKeyPem)
|
||||
{
|
||||
PkeyPtr key = loadPublicKey(publicKeyPem);
|
||||
|
||||
PkeyCtxPtr ctx(EVP_PKEY_CTX_new(key.get(), nullptr), EVP_PKEY_CTX_free);
|
||||
if (!ctx) {
|
||||
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_CTX_new failed");
|
||||
}
|
||||
if (EVP_PKEY_encrypt_init(ctx.get()) != 1) {
|
||||
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_encrypt_init failed");
|
||||
}
|
||||
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_PADDING) != 1) {
|
||||
throw std::runtime_error("agw::crypto::rsa: set_rsa_padding failed");
|
||||
}
|
||||
|
||||
std::size_t outLen = 0;
|
||||
if (EVP_PKEY_encrypt(ctx.get(), nullptr, &outLen, plaintext.data(), plaintext.size()) != 1) {
|
||||
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_encrypt (size) failed");
|
||||
}
|
||||
std::vector<std::uint8_t> out(outLen);
|
||||
if (EVP_PKEY_encrypt(ctx.get(), out.data(), &outLen, plaintext.data(), plaintext.size()) != 1) {
|
||||
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_encrypt failed");
|
||||
}
|
||||
out.resize(outLen);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool rsaPublicKeyValid(const std::string &publicKeyPem)
|
||||
{
|
||||
try {
|
||||
loadPublicKey(publicKeyPem);
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> rsaDecryptPrivatePkcs1(const std::vector<std::uint8_t> &ciphertext,
|
||||
const std::string &privateKeyPem)
|
||||
{
|
||||
PkeyPtr key = loadPrivateKey(privateKeyPem);
|
||||
|
||||
PkeyCtxPtr ctx(EVP_PKEY_CTX_new(key.get(), nullptr), EVP_PKEY_CTX_free);
|
||||
if (!ctx) {
|
||||
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_CTX_new failed");
|
||||
}
|
||||
if (EVP_PKEY_decrypt_init(ctx.get()) != 1) {
|
||||
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_decrypt_init failed");
|
||||
}
|
||||
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_PADDING) != 1) {
|
||||
throw std::runtime_error("agw::crypto::rsa: set_rsa_padding failed");
|
||||
}
|
||||
|
||||
std::size_t outLen = 0;
|
||||
if (EVP_PKEY_decrypt(ctx.get(), nullptr, &outLen, ciphertext.data(), ciphertext.size()) != 1) {
|
||||
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_decrypt (size) failed");
|
||||
}
|
||||
std::vector<std::uint8_t> out(outLen);
|
||||
if (EVP_PKEY_decrypt(ctx.get(), out.data(), &outLen, ciphertext.data(), ciphertext.size()) != 1) {
|
||||
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_decrypt failed");
|
||||
}
|
||||
out.resize(outLen);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
19
agw-sdk/src/crypto/rsa.h
Normal file
19
agw-sdk/src/crypto/rsa.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef AGW_CRYPTO_RSA_H
|
||||
#define AGW_CRYPTO_RSA_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace agw::crypto
|
||||
{
|
||||
std::vector<std::uint8_t> rsaEncryptPublicPkcs1(const std::vector<std::uint8_t> &plaintext,
|
||||
const std::string &publicKeyPem);
|
||||
|
||||
std::vector<std::uint8_t> rsaDecryptPrivatePkcs1(const std::vector<std::uint8_t> &ciphertext,
|
||||
const std::string &privateKeyPem);
|
||||
|
||||
bool rsaPublicKeyValid(const std::string &publicKeyPem);
|
||||
}
|
||||
|
||||
#endif
|
||||
14
agw-sdk/src/detail/test_hooks.h
Normal file
14
agw-sdk/src/detail/test_hooks.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef AGW_DETAIL_TEST_HOOKS_H
|
||||
#define AGW_DETAIL_TEST_HOOKS_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "agw/http.h"
|
||||
|
||||
namespace agw::detail
|
||||
{
|
||||
void setNextTestHttpClient(std::shared_ptr<IHttpClient> http);
|
||||
std::shared_ptr<IHttpClient> takeNextTestHttpClient();
|
||||
}
|
||||
|
||||
#endif
|
||||
100
agw-sdk/src/failover/bypass_policy.cpp
Normal file
100
agw-sdk/src/failover/bypass_policy.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include "failover/bypass_policy.h"
|
||||
|
||||
#include "protocol/keys.h"
|
||||
#include "util/json.h"
|
||||
|
||||
namespace agw::failover
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr const char *kPattern1 = "No active configuration found for";
|
||||
constexpr const char *kPattern2 = "No non-revoked public key found for";
|
||||
constexpr const char *kPattern3 = "Account not found.";
|
||||
constexpr const char *kPatternQrSessionNotFound = "QR session not found";
|
||||
constexpr const char *kPatternSessionNotFound = "Session not found";
|
||||
constexpr const char *kUpdateRequestPattern = "client version update is required";
|
||||
constexpr const char *kUnprocessableSubscriptionMessage =
|
||||
"Failed to retrieve subscription information. Is it activated?";
|
||||
|
||||
constexpr int kNotFound = 404;
|
||||
constexpr int kNotImplemented = 501;
|
||||
constexpr int kPaymentRequired = 402;
|
||||
constexpr int kConflict = 409;
|
||||
constexpr int kRequestTimeout = 408;
|
||||
constexpr int kUnprocessableEntity = 422;
|
||||
|
||||
bool contains(const std::string &body, const char *needle)
|
||||
{
|
||||
return body.find(needle) != std::string::npos;
|
||||
}
|
||||
|
||||
std::string trim(const std::string &s)
|
||||
{
|
||||
std::size_t b = 0, e = s.size();
|
||||
while (b < e && (s[b] == ' ' || s[b] == '\t' || s[b] == '\n' || s[b] == '\r'))
|
||||
++b;
|
||||
while (e > b && (s[e - 1] == ' ' || s[e - 1] == '\t' || s[e - 1] == '\n' || s[e - 1] == '\r'))
|
||||
--e;
|
||||
return s.substr(b, e - b);
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldBypassProxy(TransportError transportError, const std::string &decryptedBody, bool decryptionSuccessful)
|
||||
{
|
||||
if (!decryptionSuccessful) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int apiHttpStatus = -1;
|
||||
std::string apiErrorMessage;
|
||||
try {
|
||||
util::Json obj = util::Json::parse(decryptedBody);
|
||||
if (obj.is_object()) {
|
||||
if (auto it = obj.find(protocol::keys::httpStatus); it != obj.end() && it->is_number_integer()) {
|
||||
apiHttpStatus = it->get<int>();
|
||||
}
|
||||
if (auto it = obj.find(protocol::keys::message); it != obj.end() && it->is_string()) {
|
||||
apiErrorMessage = trim(it->get<std::string>());
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
if (transportError == TransportError::Canceled || transportError == TransportError::Timeout) {
|
||||
return true;
|
||||
}
|
||||
if (contains(decryptedBody, "html")) {
|
||||
return true;
|
||||
}
|
||||
if (apiHttpStatus == kRequestTimeout) {
|
||||
return false;
|
||||
}
|
||||
if (apiHttpStatus == kNotFound) {
|
||||
if (contains(decryptedBody, kPattern1) || contains(decryptedBody, kPattern2)
|
||||
|| contains(decryptedBody, kPattern3) || contains(decryptedBody, kPatternQrSessionNotFound)
|
||||
|| contains(decryptedBody, kPatternSessionNotFound)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (apiHttpStatus == kNotImplemented) {
|
||||
if (contains(decryptedBody, kUpdateRequestPattern)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (apiHttpStatus == kConflict) {
|
||||
return false;
|
||||
}
|
||||
if (apiHttpStatus == kPaymentRequired) {
|
||||
return false;
|
||||
}
|
||||
if (apiHttpStatus == kUnprocessableEntity) {
|
||||
return apiErrorMessage != kUnprocessableSubscriptionMessage;
|
||||
}
|
||||
if (transportError != TransportError::None) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
13
agw-sdk/src/failover/bypass_policy.h
Normal file
13
agw-sdk/src/failover/bypass_policy.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef AGW_FAILOVER_BYPASS_POLICY_H
|
||||
#define AGW_FAILOVER_BYPASS_POLICY_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "agw/http.h"
|
||||
|
||||
namespace agw::failover
|
||||
{
|
||||
bool shouldBypassProxy(TransportError transportError, const std::string &decryptedBody, bool decryptionSuccessful);
|
||||
}
|
||||
|
||||
#endif
|
||||
71
agw-sdk/src/failover/proxy_list.cpp
Normal file
71
agw-sdk/src/failover/proxy_list.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "failover/proxy_list.h"
|
||||
|
||||
#include "crypto/aes.h"
|
||||
#include "crypto/hash.h"
|
||||
#include "util/base64.h"
|
||||
#include "util/json.h"
|
||||
|
||||
namespace agw::failover
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void appendStorageUrls(const std::vector<std::string> &baseUrls, const FailoverContext &ctx,
|
||||
std::vector<std::string> &target)
|
||||
{
|
||||
if (!ctx.serviceType.empty()) {
|
||||
const std::string token = "endpoints-" + ctx.serviceType + "-" + ctx.userCountryCode;
|
||||
const std::string encoded =
|
||||
util::base64UrlEncodeNoPad(std::vector<std::uint8_t>(token.begin(), token.end()));
|
||||
for (const auto &base : baseUrls) {
|
||||
target.push_back(base + encoded + ".json");
|
||||
}
|
||||
}
|
||||
for (const auto &base : baseUrls) {
|
||||
target.push_back(base + "endpoints.json");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> parseEndpointsArray(const std::string &json)
|
||||
{
|
||||
std::vector<std::string> out;
|
||||
try {
|
||||
util::Json doc = util::Json::parse(json);
|
||||
if (doc.is_array()) {
|
||||
for (const auto &el : doc) {
|
||||
if (el.is_string()) {
|
||||
out.push_back(el.get<std::string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> buildStorageUrls(const std::vector<std::string> &primaryBaseUrls,
|
||||
const std::vector<std::string> &fallbackBaseUrls,
|
||||
const FailoverContext &ctx)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
appendStorageUrls(primaryBaseUrls, ctx, result);
|
||||
appendStorageUrls(fallbackBaseUrls, ctx, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> decodeProxyList(const std::string &body, bool isDevEnvironment, const std::string &pubKeyPem)
|
||||
{
|
||||
if (isDevEnvironment) {
|
||||
return parseEndpointsArray(body);
|
||||
}
|
||||
|
||||
const std::vector<std::uint8_t> pubBytes(pubKeyPem.begin(), pubKeyPem.end());
|
||||
const std::string h = crypto::toHex(crypto::sha512(pubBytes));
|
||||
const std::vector<std::uint8_t> key = crypto::fromHex(h.substr(0, 64));
|
||||
const std::vector<std::uint8_t> iv = crypto::fromHex(h.substr(64, 32));
|
||||
|
||||
const std::vector<std::uint8_t> cipher = util::base64Decode(body);
|
||||
const std::vector<std::uint8_t> plain = crypto::aesDecryptCbc(cipher, key, iv);
|
||||
return parseEndpointsArray(std::string(plain.begin(), plain.end()));
|
||||
}
|
||||
}
|
||||
19
agw-sdk/src/failover/proxy_list.h
Normal file
19
agw-sdk/src/failover/proxy_list.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef AGW_FAILOVER_PROXY_LIST_H
|
||||
#define AGW_FAILOVER_PROXY_LIST_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "agw/types.h"
|
||||
|
||||
namespace agw::failover
|
||||
{
|
||||
std::vector<std::string> buildStorageUrls(const std::vector<std::string> &primaryBaseUrls,
|
||||
const std::vector<std::string> &fallbackBaseUrls,
|
||||
const FailoverContext &ctx);
|
||||
|
||||
std::vector<std::string> decodeProxyList(const std::string &body, bool isDevEnvironment,
|
||||
const std::string &pubKeyPem);
|
||||
}
|
||||
|
||||
#endif
|
||||
21
agw-sdk/src/failover/proxy_picker.cpp
Normal file
21
agw-sdk/src/failover/proxy_picker.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "failover/proxy_picker.h"
|
||||
|
||||
namespace agw::failover
|
||||
{
|
||||
std::string pickHealthyProxy(IHttpClient &http, const std::vector<std::string> &proxyUrls, int timeoutMsecs)
|
||||
{
|
||||
for (const auto &proxy : proxyUrls) {
|
||||
HttpRequest req;
|
||||
req.url = proxy + "lmbd-health";
|
||||
req.method = "GET";
|
||||
req.headers = { { "Content-Type", "application/json" } };
|
||||
req.timeoutMsecs = timeoutMsecs;
|
||||
|
||||
const HttpResponse resp = http.send(req);
|
||||
if (resp.error == TransportError::None && !resp.sslError) {
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
return { };
|
||||
}
|
||||
}
|
||||
14
agw-sdk/src/failover/proxy_picker.h
Normal file
14
agw-sdk/src/failover/proxy_picker.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef AGW_FAILOVER_PROXY_PICKER_H
|
||||
#define AGW_FAILOVER_PROXY_PICKER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "agw/http.h"
|
||||
|
||||
namespace agw::failover
|
||||
{
|
||||
std::string pickHealthyProxy(IHttpClient &http, const std::vector<std::string> &proxyUrls, int timeoutMsecs);
|
||||
}
|
||||
|
||||
#endif
|
||||
364
agw-sdk/src/gateway_controller.cpp
Normal file
364
agw-sdk/src/gateway_controller.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
#include "agw/gateway_controller.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "crypto/rng.h"
|
||||
#include "failover/bypass_policy.h"
|
||||
#include "failover/proxy_list.h"
|
||||
#include "failover/proxy_picker.h"
|
||||
#include "protocol/error_mapping.h"
|
||||
#include "protocol/request_builder.h"
|
||||
#include "protocol/response.h"
|
||||
#include "util/thread_pool.h"
|
||||
#include "util/url.h"
|
||||
#include "util/uuid.h"
|
||||
|
||||
namespace agw {
|
||||
namespace {
|
||||
bool isCancelled(const CancellationToken *cancel)
|
||||
{
|
||||
return cancel != nullptr && cancel->isCancelled();
|
||||
}
|
||||
|
||||
std::function<bool()> makeCancelCheck(CancellationToken *cancel)
|
||||
{
|
||||
if (cancel == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return [cancel] { return cancel->isCancelled(); };
|
||||
}
|
||||
|
||||
std::string threadIdStr()
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << std::this_thread::get_id();
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
const char *transportErrorName(TransportError e)
|
||||
{
|
||||
switch (e) {
|
||||
case TransportError::None: return "None";
|
||||
case TransportError::Timeout: return "Timeout";
|
||||
case TransportError::Canceled: return "Canceled";
|
||||
case TransportError::OperationNotImplemented: return "OperationNotImplemented";
|
||||
case TransportError::ConnectionError: return "ConnectionError";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
struct GatewayController::Impl {
|
||||
Config config;
|
||||
std::shared_ptr<IHttpClient> http;
|
||||
std::unique_ptr<crypto::IRng> rng;
|
||||
|
||||
std::mutex proxyMutex;
|
||||
std::string cachedProxy;
|
||||
|
||||
util::ThreadPool pool;
|
||||
|
||||
explicit Impl(Config cfg)
|
||||
: config(std::move(cfg)),
|
||||
rng(std::make_unique<crypto::DefaultRng>()),
|
||||
pool(static_cast<std::size_t>(config.threadPoolSize))
|
||||
{
|
||||
http = config.httpClient ? config.httpClient
|
||||
: std::shared_ptr<IHttpClient>(makeDefaultHttpClient());
|
||||
log(LogLevel::Info,
|
||||
"client created: dev=" + std::string(config.isDevEnvironment ? "1" : "0")
|
||||
+ " timeout=" + std::to_string(config.requestTimeoutMsecs) + "ms"
|
||||
+ " pool=" + std::to_string(config.threadPoolSize)
|
||||
+ " s3primary=" + std::to_string(config.s3PrimaryEndpoints.size())
|
||||
+ " s3fallback=" + std::to_string(config.s3FallbackEndpoints.size())
|
||||
+ " customHttp=" + std::string(config.httpClient ? "1" : "0"));
|
||||
}
|
||||
|
||||
void log(LogLevel level, const std::string &message) const
|
||||
{
|
||||
if (config.log) {
|
||||
config.log(level, message);
|
||||
}
|
||||
}
|
||||
void dbg(const std::string &message) const { log(LogLevel::Debug, message); }
|
||||
|
||||
std::string getCachedProxy()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(proxyMutex);
|
||||
return cachedProxy;
|
||||
}
|
||||
|
||||
void setCachedProxy(const std::string &proxy)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(proxyMutex);
|
||||
cachedProxy = proxy;
|
||||
}
|
||||
|
||||
bool attempt(const std::string &endpoint, const std::string &host, const HttpRequest &baseReq,
|
||||
const std::vector<std::uint8_t> &key, const std::vector<std::uint8_t> &iv,
|
||||
HttpResponse &resp, protocol::DecryptResult &dec)
|
||||
{
|
||||
HttpRequest req = baseReq;
|
||||
req.url = util::formatEndpoint(endpoint, host);
|
||||
dbg(" proxy attempt: POST " + req.url);
|
||||
|
||||
const auto t0 = std::chrono::steady_clock::now();
|
||||
resp = http->send(req);
|
||||
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - t0).count();
|
||||
|
||||
dec = protocol::tryDecryptResponse(resp.body, key, iv);
|
||||
const bool bypass = resp.sslError || failover::shouldBypassProxy(resp.error, dec.decryptedBody, dec.ok);
|
||||
dbg(" proxy attempt result: transport=" + std::string(transportErrorName(resp.error))
|
||||
+ " ssl=" + std::string(resp.sslError ? "1" : "0") + " http=" + std::to_string(resp.httpStatusCode)
|
||||
+ " bodyLen=" + std::to_string(resp.body.size()) + " decryptOk=" + std::string(dec.ok ? "1" : "0")
|
||||
+ " bypassAgain=" + std::string(bypass ? "1" : "0") + " (" + std::to_string(ms) + "ms)");
|
||||
return !bypass;
|
||||
}
|
||||
|
||||
void runFailover(const std::string &endpoint, const HttpRequest &baseReq, const FailoverContext &ctx,
|
||||
const std::vector<std::uint8_t> &key, const std::vector<std::uint8_t> &iv,
|
||||
HttpResponse &resp, protocol::DecryptResult &dec, CancellationToken *cancel)
|
||||
{
|
||||
if (isCancelled(cancel)) {
|
||||
dbg("failover: cancelled before start");
|
||||
return;
|
||||
}
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
|
||||
std::vector<std::string> primary = config.s3PrimaryEndpoints;
|
||||
std::vector<std::string> fallback = config.s3FallbackEndpoints;
|
||||
std::shuffle(primary.begin(), primary.end(), gen);
|
||||
std::shuffle(fallback.begin(), fallback.end(), gen);
|
||||
|
||||
const std::vector<std::string> storageUrls = failover::buildStorageUrls(primary, fallback, ctx);
|
||||
dbg("failover: storage urls=" + std::to_string(storageUrls.size())
|
||||
+ " service='" + ctx.serviceType + "' country='" + ctx.userCountryCode + "'");
|
||||
|
||||
std::vector<std::string> proxyUrls;
|
||||
for (const auto &storageUrl : storageUrls) {
|
||||
if (isCancelled(cancel)) {
|
||||
dbg("failover: cancelled during storage fetch");
|
||||
return;
|
||||
}
|
||||
HttpRequest g;
|
||||
g.url = storageUrl;
|
||||
g.method = "GET";
|
||||
g.headers = {{"Content-Type", "application/json"}};
|
||||
g.timeoutMsecs = config.proxyStorageTimeoutMsecs;
|
||||
g.cancelCheck = makeCancelCheck(cancel);
|
||||
|
||||
const HttpResponse gr = http->send(g);
|
||||
dbg(" storage GET " + storageUrl + " → transport=" + std::string(transportErrorName(gr.error))
|
||||
+ " ssl=" + std::string(gr.sslError ? "1" : "0") + " http=" + std::to_string(gr.httpStatusCode)
|
||||
+ " bodyLen=" + std::to_string(gr.body.size()));
|
||||
if (gr.error != TransportError::None || gr.sslError) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
proxyUrls = failover::decodeProxyList(gr.body, config.isDevEnvironment, config.agwPublicKeyPem);
|
||||
dbg(" decoded proxy list: " + std::to_string(proxyUrls.size()) + " proxies");
|
||||
break;
|
||||
} catch (...) {
|
||||
dbg(" proxy list decode failed → next storage");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
std::shuffle(proxyUrls.begin(), proxyUrls.end(), gen);
|
||||
|
||||
std::string proxy = getCachedProxy();
|
||||
if (proxy.empty()) {
|
||||
if (isCancelled(cancel)) {
|
||||
dbg("failover: cancelled before health-check");
|
||||
return;
|
||||
}
|
||||
dbg("failover: no cached proxy → health-check of " + std::to_string(proxyUrls.size()) + " proxies");
|
||||
proxy = failover::pickHealthyProxy(*http, proxyUrls, config.proxyHealthTimeoutMsecs);
|
||||
if (!proxy.empty()) {
|
||||
dbg("failover: healthy proxy = " + proxy + " (cached)");
|
||||
setCachedProxy(proxy);
|
||||
} else {
|
||||
dbg("failover: no healthy proxy found");
|
||||
}
|
||||
} else {
|
||||
dbg("failover: using cached proxy = " + proxy);
|
||||
}
|
||||
|
||||
if (!proxy.empty()) {
|
||||
if (isCancelled(cancel)) {
|
||||
return;
|
||||
}
|
||||
if (attempt(endpoint, proxy, baseReq, key, iv, resp, dec)) {
|
||||
dbg("failover: succeeded via cached/first proxy");
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const auto &p : proxyUrls) {
|
||||
if (isCancelled(cancel)) {
|
||||
return;
|
||||
}
|
||||
if (attempt(endpoint, p, baseReq, key, iv, resp, dec)) {
|
||||
dbg("failover: succeeded via proxy " + p + " (cached)");
|
||||
setCachedProxy(p);
|
||||
return;
|
||||
}
|
||||
}
|
||||
dbg("failover: exhausted all proxies (using last attempt result)");
|
||||
}
|
||||
|
||||
Response executePost(const std::string &endpoint, const std::string &payload,
|
||||
const FailoverContext &ctx, CancellationToken *cancel)
|
||||
{
|
||||
const auto tStart = std::chrono::steady_clock::now();
|
||||
log(LogLevel::Info, "post START endpoint='" + endpoint + "' service='" + ctx.serviceType
|
||||
+ "' country='" + ctx.userCountryCode + "' payloadLen=" + std::to_string(payload.size())
|
||||
+ " thread=" + threadIdStr());
|
||||
|
||||
if (isCancelled(cancel)) {
|
||||
log(LogLevel::Info, "post: cancelled before start");
|
||||
return Response{ErrorCode::Cancelled, std::string()};
|
||||
}
|
||||
|
||||
protocol::EncryptedRequest enc =
|
||||
protocol::buildEncryptedRequest(payload, config.agwPublicKeyPem, *rng);
|
||||
if (enc.error != ErrorCode::NoError) {
|
||||
log(LogLevel::Warning, "post: request build failed error="
|
||||
+ std::to_string(static_cast<int>(enc.error)));
|
||||
return Response{enc.error, std::string()};
|
||||
}
|
||||
dbg("request built: bodyLen=" + std::to_string(enc.body.size()) + " (key/iv/salt generated)");
|
||||
if (isCancelled(cancel)) {
|
||||
return Response{ErrorCode::Cancelled, std::string()};
|
||||
}
|
||||
|
||||
const std::string requestId = util::makeUuidV4(*rng);
|
||||
const std::string cached = getCachedProxy();
|
||||
const std::string directHost = cached.empty() ? config.gatewayEndpoint : cached;
|
||||
const std::string url = util::formatEndpoint(endpoint, directHost);
|
||||
dbg("direct request: url=" + url + " reqId=" + requestId
|
||||
+ " viaCachedProxy=" + std::string(cached.empty() ? "0" : "1"));
|
||||
|
||||
if (config.onBeforeRequest) {
|
||||
const std::string host = util::extractHost(url);
|
||||
dbg("onBeforeRequest(host=" + host + ")");
|
||||
config.onBeforeRequest(host);
|
||||
}
|
||||
|
||||
HttpRequest req;
|
||||
req.url = url;
|
||||
req.method = "POST";
|
||||
req.body = enc.body;
|
||||
req.headers = {
|
||||
{"Content-Type", "application/json"},
|
||||
{"X-Client-Request-ID", requestId},
|
||||
};
|
||||
req.timeoutMsecs = config.requestTimeoutMsecs;
|
||||
req.cancelCheck = makeCancelCheck(cancel);
|
||||
|
||||
const auto t0 = std::chrono::steady_clock::now();
|
||||
HttpResponse resp = http->send(req);
|
||||
const auto httpMs = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - t0).count();
|
||||
if (isCancelled(cancel)) {
|
||||
log(LogLevel::Info, "post: cancelled after direct send");
|
||||
return Response{ErrorCode::Cancelled, std::string()};
|
||||
}
|
||||
|
||||
protocol::DecryptResult dec = protocol::tryDecryptResponse(resp.body, enc.key, enc.iv);
|
||||
dbg("direct response: transport=" + std::string(transportErrorName(resp.error))
|
||||
+ " ssl=" + std::string(resp.sslError ? "1" : "0") + " http=" + std::to_string(resp.httpStatusCode)
|
||||
+ " bodyLen=" + std::to_string(resp.body.size()) + " decryptOk=" + std::string(dec.ok ? "1" : "0")
|
||||
+ " (" + std::to_string(httpMs) + "ms)");
|
||||
|
||||
const bool bypass = !resp.sslError
|
||||
&& failover::shouldBypassProxy(resp.error, dec.decryptedBody, dec.ok);
|
||||
if (bypass) {
|
||||
log(LogLevel::Info, "direct response suspicious — running failover");
|
||||
runFailover(endpoint, req, ctx, enc.key, enc.iv, resp, dec, cancel);
|
||||
if (isCancelled(cancel)) {
|
||||
log(LogLevel::Info, "post: cancelled during failover");
|
||||
return Response{ErrorCode::Cancelled, std::string()};
|
||||
}
|
||||
} else {
|
||||
dbg("direct response accepted (no failover)");
|
||||
}
|
||||
|
||||
Response out;
|
||||
out.body = dec.decryptedBody;
|
||||
|
||||
const ErrorCode mapped = protocol::mapResponseError(resp.sslError, resp.error, dec.decryptedBody);
|
||||
const auto totalMs = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - tStart).count();
|
||||
if (mapped != ErrorCode::NoError) {
|
||||
out.error = mapped;
|
||||
log(LogLevel::Warning, "post DONE error=" + std::to_string(static_cast<int>(mapped))
|
||||
+ " bodyLen=" + std::to_string(out.body.size()) + " (" + std::to_string(totalMs) + "ms)");
|
||||
return out;
|
||||
}
|
||||
if (!dec.ok) {
|
||||
out.error = ErrorCode::ApiConfigDecryptionError;
|
||||
log(LogLevel::Error, "post DONE: response decryption failed (1106) ("
|
||||
+ std::to_string(totalMs) + "ms)");
|
||||
return out;
|
||||
}
|
||||
out.error = ErrorCode::NoError;
|
||||
log(LogLevel::Info, "post DONE ok bodyLen=" + std::to_string(out.body.size())
|
||||
+ " (" + std::to_string(totalMs) + "ms)");
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
GatewayController::GatewayController(Config config) : m_impl(std::make_unique<Impl>(std::move(config))) {}
|
||||
GatewayController::~GatewayController() = default;
|
||||
GatewayController::GatewayController(GatewayController &&) noexcept = default;
|
||||
GatewayController &GatewayController::operator=(GatewayController &&) noexcept = default;
|
||||
|
||||
Response GatewayController::post(const std::string &endpoint, const std::string &payload,
|
||||
const FailoverContext &ctx, CancellationToken *cancel)
|
||||
{
|
||||
return m_impl->executePost(endpoint, payload, ctx, cancel);
|
||||
}
|
||||
|
||||
void GatewayController::postAsync(const std::string &endpoint, const std::string &payload,
|
||||
std::function<void(Response)> onResult, const FailoverContext &ctx,
|
||||
CancellationToken *cancel)
|
||||
{
|
||||
Impl *impl = m_impl.get();
|
||||
impl->dbg("postAsync: submitting to pool (caller thread=" + threadIdStr() + ")");
|
||||
impl->pool.submit([impl, endpoint, payload, onResult = std::move(onResult), ctx, cancel]() {
|
||||
impl->dbg("postAsync: running on pool thread=" + threadIdStr());
|
||||
Response r = impl->executePost(endpoint, payload, ctx, cancel);
|
||||
if (onResult) {
|
||||
onResult(std::move(r));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::future<Response> GatewayController::postFuture(const std::string &endpoint, const std::string &payload,
|
||||
const FailoverContext &ctx, CancellationToken *cancel)
|
||||
{
|
||||
auto promise = std::make_shared<std::promise<Response>>();
|
||||
std::future<Response> fut = promise->get_future();
|
||||
Impl *impl = m_impl.get();
|
||||
impl->dbg("postFuture: submitting to pool (caller thread=" + threadIdStr() + ")");
|
||||
impl->pool.submit([impl, endpoint, payload, ctx, cancel, promise]() {
|
||||
impl->dbg("postFuture: running on pool thread=" + threadIdStr());
|
||||
promise->set_value(impl->executePost(endpoint, payload, ctx, cancel));
|
||||
});
|
||||
return fut;
|
||||
}
|
||||
}
|
||||
132
agw-sdk/src/http/curl_client.cpp
Normal file
132
agw-sdk/src/http/curl_client.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
#ifdef AGW_HAVE_CURL
|
||||
|
||||
#include "http/curl_client.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
namespace agw
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::once_flag g_curlInitOnce;
|
||||
|
||||
void ensureCurlGlobalInit()
|
||||
{
|
||||
std::call_once(g_curlInitOnce, []() { curl_global_init(CURL_GLOBAL_DEFAULT); });
|
||||
}
|
||||
|
||||
std::size_t writeCallback(char *ptr, std::size_t size, std::size_t nmemb, void *userdata)
|
||||
{
|
||||
const std::size_t total = size * nmemb;
|
||||
auto *buf = static_cast<std::string *>(userdata);
|
||||
buf->append(ptr, total);
|
||||
return total;
|
||||
}
|
||||
|
||||
int xferCallback(void *clientp, curl_off_t, curl_off_t, curl_off_t, curl_off_t)
|
||||
{
|
||||
auto *check = static_cast<const std::function<bool()> *>(clientp);
|
||||
if (check && *check && (*check)()) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
TransportError mapCurlError(CURLcode code, bool &sslError)
|
||||
{
|
||||
sslError = false;
|
||||
switch (code) {
|
||||
case CURLE_OK: return TransportError::None;
|
||||
case CURLE_OPERATION_TIMEDOUT: return TransportError::Timeout;
|
||||
case CURLE_ABORTED_BY_CALLBACK: return TransportError::Canceled;
|
||||
case CURLE_SSL_CONNECT_ERROR:
|
||||
case CURLE_PEER_FAILED_VERIFICATION:
|
||||
case CURLE_SSL_CERTPROBLEM:
|
||||
case CURLE_SSL_CIPHER:
|
||||
case CURLE_SSL_CACERT_BADFILE:
|
||||
case CURLE_SSL_ISSUER_ERROR: sslError = true; return TransportError::ConnectionError;
|
||||
default: return TransportError::ConnectionError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CurlHttpClient::CurlHttpClient()
|
||||
{
|
||||
ensureCurlGlobalInit();
|
||||
}
|
||||
|
||||
CurlHttpClient::~CurlHttpClient() = default;
|
||||
|
||||
HttpResponse CurlHttpClient::send(const HttpRequest &request)
|
||||
{
|
||||
HttpResponse response;
|
||||
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
response.error = TransportError::ConnectionError;
|
||||
response.errorString = "curl_easy_init failed";
|
||||
return response;
|
||||
}
|
||||
|
||||
struct curl_slist *headers = nullptr;
|
||||
for (const auto &h : request.headers) {
|
||||
const std::string line = h.first + ": " + h.second;
|
||||
headers = curl_slist_append(headers, line.c_str());
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str());
|
||||
if (headers) {
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
}
|
||||
if (request.timeoutMsecs > 0) {
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, static_cast<long>(request.timeoutMsecs));
|
||||
}
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response.body);
|
||||
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
|
||||
|
||||
if (request.cancelCheck) {
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &request.cancelCheck);
|
||||
}
|
||||
|
||||
if (request.method == "POST") {
|
||||
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.data());
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(request.body.size()));
|
||||
} else {
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
|
||||
}
|
||||
|
||||
const CURLcode code = curl_easy_perform(curl);
|
||||
bool sslError = false;
|
||||
response.error = mapCurlError(code, sslError);
|
||||
response.sslError = sslError;
|
||||
if (code != CURLE_OK) {
|
||||
response.errorString = curl_easy_strerror(code);
|
||||
}
|
||||
|
||||
long httpCode = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
|
||||
response.httpStatusCode = static_cast<int>(httpCode);
|
||||
|
||||
if (headers) {
|
||||
curl_slist_free_all(headers);
|
||||
}
|
||||
curl_easy_cleanup(curl);
|
||||
return response;
|
||||
}
|
||||
|
||||
std::unique_ptr<IHttpClient> makeDefaultHttpClient()
|
||||
{
|
||||
return std::make_unique<CurlHttpClient>();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
17
agw-sdk/src/http/curl_client.h
Normal file
17
agw-sdk/src/http/curl_client.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef AGW_HTTP_CURL_CLIENT_H
|
||||
#define AGW_HTTP_CURL_CLIENT_H
|
||||
|
||||
#include "agw/http.h"
|
||||
|
||||
namespace agw
|
||||
{
|
||||
class CurlHttpClient : public IHttpClient
|
||||
{
|
||||
public:
|
||||
CurlHttpClient();
|
||||
~CurlHttpClient() override;
|
||||
HttpResponse send(const HttpRequest &request) override;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
15
agw-sdk/src/http/default_client_fallback.cpp
Normal file
15
agw-sdk/src/http/default_client_fallback.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef AGW_HAVE_CURL
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "agw/http.h"
|
||||
|
||||
namespace agw
|
||||
{
|
||||
std::unique_ptr<IHttpClient> makeDefaultHttpClient()
|
||||
{
|
||||
throw std::runtime_error("agw: SDK built without libcurl; provide Config::httpClient (your own IHttpClient)");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
133
agw-sdk/src/protocol/error_mapping.cpp
Normal file
133
agw-sdk/src/protocol/error_mapping.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#include "protocol/error_mapping.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
#include "protocol/keys.h"
|
||||
#include "util/json.h"
|
||||
|
||||
namespace agw::protocol
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr int kConflict = 409;
|
||||
constexpr int kNotFound = 404;
|
||||
constexpr int kNotImplemented = 501;
|
||||
constexpr int kPaymentRequired = 402;
|
||||
constexpr int kTooManyRequests = 429;
|
||||
constexpr int kRequestTimeout = 408;
|
||||
constexpr int kUnprocessableEntity = 422;
|
||||
|
||||
constexpr const char *kUnprocessableSubscriptionMessage =
|
||||
"Failed to retrieve subscription information. Is it activated?";
|
||||
constexpr const char *kTrialAlreadyUsedMessage = "trial subscription already used";
|
||||
|
||||
std::string trim(const std::string &s)
|
||||
{
|
||||
std::size_t b = 0;
|
||||
std::size_t e = s.size();
|
||||
while (b < e && std::isspace(static_cast<unsigned char>(s[b])))
|
||||
++b;
|
||||
while (e > b && std::isspace(static_cast<unsigned char>(s[e - 1])))
|
||||
--e;
|
||||
return s.substr(b, e - b);
|
||||
}
|
||||
|
||||
std::string toLower(std::string s)
|
||||
{
|
||||
std::transform(s.begin(), s.end(), s.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return s;
|
||||
}
|
||||
|
||||
bool containsCI(const std::string &haystack, const std::string &needle)
|
||||
{
|
||||
return toLower(haystack).find(toLower(needle)) != std::string::npos;
|
||||
}
|
||||
|
||||
std::string messageFrom(const util::Json &obj)
|
||||
{
|
||||
auto it = obj.find(keys::message);
|
||||
if (it != obj.end() && it->is_string()) {
|
||||
return trim(it->get<std::string>());
|
||||
}
|
||||
return { };
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode mapResponseError(bool sslError, TransportError transportError, const std::string &decryptedBody)
|
||||
{
|
||||
if (sslError) {
|
||||
return ErrorCode::ApiConfigSslError;
|
||||
}
|
||||
if (transportError == TransportError::Timeout || transportError == TransportError::Canceled) {
|
||||
return ErrorCode::ApiConfigTimeoutError;
|
||||
}
|
||||
if (transportError == TransportError::OperationNotImplemented) {
|
||||
return ErrorCode::ApiUpdateRequestError;
|
||||
}
|
||||
|
||||
util::Json obj;
|
||||
bool isObject = false;
|
||||
try {
|
||||
obj = util::Json::parse(decryptedBody);
|
||||
isObject = obj.is_object();
|
||||
} catch (...) {
|
||||
isObject = false;
|
||||
}
|
||||
|
||||
if (isObject) {
|
||||
int httpStatus = -1;
|
||||
if (auto it = obj.find(keys::httpStatus); it != obj.end() && it->is_number_integer()) {
|
||||
httpStatus = it->get<int>();
|
||||
}
|
||||
const std::string message = messageFrom(obj);
|
||||
|
||||
if (httpStatus == kTooManyRequests) {
|
||||
return ErrorCode::ApiRateLimitError;
|
||||
}
|
||||
if (httpStatus == kConflict) {
|
||||
if (containsCI(message, kTrialAlreadyUsedMessage)) {
|
||||
return ErrorCode::ApiTrialAlreadyUsedError;
|
||||
}
|
||||
return ErrorCode::ApiConfigLimitError;
|
||||
}
|
||||
if (httpStatus == kNotFound) {
|
||||
return ErrorCode::ApiNotFoundError;
|
||||
}
|
||||
if (httpStatus == kRequestTimeout) {
|
||||
return ErrorCode::ApiConfigTimeoutError;
|
||||
}
|
||||
if (httpStatus == kNotImplemented) {
|
||||
return ErrorCode::ApiUpdateRequestError;
|
||||
}
|
||||
if (httpStatus == kUnprocessableEntity) {
|
||||
if (message == kUnprocessableSubscriptionMessage) {
|
||||
return ErrorCode::ApiSubscriptionExpiredError;
|
||||
}
|
||||
return ErrorCode::ApiConfigDownloadError;
|
||||
}
|
||||
if (httpStatus == kPaymentRequired) {
|
||||
if (containsCI(message, "refresh_captcha")) {
|
||||
return ErrorCode::ApiCaptchaRefreshError;
|
||||
}
|
||||
if (containsCI(message, "invalid_captcha")) {
|
||||
return ErrorCode::ApiCaptchaInvalidError;
|
||||
}
|
||||
if (obj.contains("captcha_id") || obj.contains("captcha_image")
|
||||
|| containsCI(message, "rate_limit_exceeded")) {
|
||||
return ErrorCode::ApiCaptchaRequiredError;
|
||||
}
|
||||
return ErrorCode::ApiSubscriptionNotActiveError;
|
||||
}
|
||||
if (httpStatus >= 300) {
|
||||
return ErrorCode::ApiConfigDownloadError;
|
||||
}
|
||||
}
|
||||
|
||||
if (transportError == TransportError::None) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
return ErrorCode::ApiConfigDownloadError;
|
||||
}
|
||||
}
|
||||
14
agw-sdk/src/protocol/error_mapping.h
Normal file
14
agw-sdk/src/protocol/error_mapping.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef AGW_PROTOCOL_ERROR_MAPPING_H
|
||||
#define AGW_PROTOCOL_ERROR_MAPPING_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "agw/http.h"
|
||||
#include "agw/types.h"
|
||||
|
||||
namespace agw::protocol
|
||||
{
|
||||
ErrorCode mapResponseError(bool sslError, TransportError transportError, const std::string &decryptedBody);
|
||||
}
|
||||
|
||||
#endif
|
||||
18
agw-sdk/src/protocol/keys.h
Normal file
18
agw-sdk/src/protocol/keys.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef AGW_PROTOCOL_KEYS_H
|
||||
#define AGW_PROTOCOL_KEYS_H
|
||||
|
||||
namespace agw::protocol::keys {
|
||||
inline constexpr const char *aesKey = "aes_key";
|
||||
inline constexpr const char *aesIv = "aes_iv";
|
||||
inline constexpr const char *aesSalt = "aes_salt";
|
||||
inline constexpr const char *apiPayload = "api_payload";
|
||||
inline constexpr const char *keyPayload = "key_payload";
|
||||
|
||||
inline constexpr const char *serviceType = "service_type";
|
||||
inline constexpr const char *userCountryCode = "user_country_code";
|
||||
|
||||
inline constexpr const char *httpStatus = "http_status";
|
||||
inline constexpr const char *message = "message";
|
||||
}
|
||||
|
||||
#endif
|
||||
55
agw-sdk/src/protocol/request_builder.cpp
Normal file
55
agw-sdk/src/protocol/request_builder.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "protocol/request_builder.h"
|
||||
|
||||
#include "crypto/aes.h"
|
||||
#include "crypto/rsa.h"
|
||||
#include "protocol/keys.h"
|
||||
#include "util/base64.h"
|
||||
#include "util/json.h"
|
||||
|
||||
namespace agw::protocol
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::vector<std::uint8_t> bytesOf(const std::string &s)
|
||||
{
|
||||
return std::vector<std::uint8_t>(s.begin(), s.end());
|
||||
}
|
||||
}
|
||||
|
||||
EncryptedRequest buildEncryptedRequest(const std::string &payload, const std::string &publicKeyPem, crypto::IRng &rng)
|
||||
{
|
||||
namespace k = keys;
|
||||
|
||||
EncryptedRequest out;
|
||||
out.key = rng.bytes(32);
|
||||
out.iv = rng.bytes(32);
|
||||
out.salt = rng.bytes(8);
|
||||
|
||||
if (!crypto::rsaPublicKeyValid(publicKeyPem)) {
|
||||
out.error = ErrorCode::ApiMissingAgwPublicKey;
|
||||
return out;
|
||||
}
|
||||
|
||||
util::Json keysJson;
|
||||
keysJson[k::aesKey] = util::base64Encode(out.key);
|
||||
keysJson[k::aesIv] = util::base64Encode(out.iv);
|
||||
keysJson[k::aesSalt] = util::base64Encode(out.salt);
|
||||
const std::string keysSerialized = util::qtIndentedDump(keysJson);
|
||||
|
||||
std::string keyPayloadB64;
|
||||
std::string apiPayloadB64;
|
||||
try {
|
||||
keyPayloadB64 = util::base64Encode(crypto::rsaEncryptPublicPkcs1(bytesOf(keysSerialized), publicKeyPem));
|
||||
apiPayloadB64 = util::base64Encode(crypto::aesEncryptCbc(bytesOf(payload), out.key, out.iv));
|
||||
} catch (...) {
|
||||
out.error = ErrorCode::ApiConfigDecryptionError;
|
||||
return out;
|
||||
}
|
||||
|
||||
util::Json body;
|
||||
body[k::keyPayload] = keyPayloadB64;
|
||||
body[k::apiPayload] = apiPayloadB64;
|
||||
out.body = util::qtIndentedDump(body);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
26
agw-sdk/src/protocol/request_builder.h
Normal file
26
agw-sdk/src/protocol/request_builder.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef AGW_PROTOCOL_REQUEST_BUILDER_H
|
||||
#define AGW_PROTOCOL_REQUEST_BUILDER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "agw/types.h"
|
||||
#include "crypto/rng.h"
|
||||
|
||||
namespace agw::protocol
|
||||
{
|
||||
struct EncryptedRequest
|
||||
{
|
||||
std::string body;
|
||||
std::vector<std::uint8_t> key;
|
||||
std::vector<std::uint8_t> iv;
|
||||
std::vector<std::uint8_t> salt;
|
||||
ErrorCode error = ErrorCode::NoError;
|
||||
};
|
||||
|
||||
EncryptedRequest buildEncryptedRequest(const std::string &payload, const std::string &publicKeyPem,
|
||||
crypto::IRng &rng);
|
||||
}
|
||||
|
||||
#endif
|
||||
24
agw-sdk/src/protocol/response.cpp
Normal file
24
agw-sdk/src/protocol/response.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "protocol/response.h"
|
||||
|
||||
#include "crypto/aes.h"
|
||||
|
||||
namespace agw::protocol
|
||||
{
|
||||
DecryptResult tryDecryptResponse(const std::string &encrypted, const std::vector<std::uint8_t> &key,
|
||||
const std::vector<std::uint8_t> &iv)
|
||||
{
|
||||
DecryptResult result;
|
||||
result.decryptedBody = encrypted;
|
||||
result.ok = false;
|
||||
try {
|
||||
const std::vector<std::uint8_t> in(encrypted.begin(), encrypted.end());
|
||||
const std::vector<std::uint8_t> out = crypto::aesDecryptCbc(in, key, iv);
|
||||
result.decryptedBody.assign(out.begin(), out.end());
|
||||
result.ok = true;
|
||||
} catch (...) {
|
||||
result.decryptedBody = encrypted;
|
||||
result.ok = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
20
agw-sdk/src/protocol/response.h
Normal file
20
agw-sdk/src/protocol/response.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef AGW_PROTOCOL_RESPONSE_H
|
||||
#define AGW_PROTOCOL_RESPONSE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace agw::protocol
|
||||
{
|
||||
struct DecryptResult
|
||||
{
|
||||
std::string decryptedBody;
|
||||
bool ok = false;
|
||||
};
|
||||
|
||||
DecryptResult tryDecryptResponse(const std::string &encrypted, const std::vector<std::uint8_t> &key,
|
||||
const std::vector<std::uint8_t> &iv);
|
||||
}
|
||||
|
||||
#endif
|
||||
108
agw-sdk/src/util/base64.cpp
Normal file
108
agw-sdk/src/util/base64.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "base64.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace agw::util
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const char *kStd = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
const char *kUrl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
|
||||
std::string encode(const std::vector<std::uint8_t> &data, const char *alphabet, bool pad)
|
||||
{
|
||||
std::string out;
|
||||
out.reserve((data.size() + 2) / 3 * 4);
|
||||
|
||||
std::size_t i = 0;
|
||||
const std::size_t n = data.size();
|
||||
while (i + 3 <= n) {
|
||||
const std::uint32_t v = (std::uint32_t(data[i]) << 16) | (std::uint32_t(data[i + 1]) << 8) | data[i + 2];
|
||||
out.push_back(alphabet[(v >> 18) & 0x3F]);
|
||||
out.push_back(alphabet[(v >> 12) & 0x3F]);
|
||||
out.push_back(alphabet[(v >> 6) & 0x3F]);
|
||||
out.push_back(alphabet[v & 0x3F]);
|
||||
i += 3;
|
||||
}
|
||||
|
||||
const std::size_t rem = n - i;
|
||||
if (rem == 1) {
|
||||
const std::uint32_t v = std::uint32_t(data[i]) << 16;
|
||||
out.push_back(alphabet[(v >> 18) & 0x3F]);
|
||||
out.push_back(alphabet[(v >> 12) & 0x3F]);
|
||||
if (pad) {
|
||||
out.push_back('=');
|
||||
out.push_back('=');
|
||||
}
|
||||
} else if (rem == 2) {
|
||||
const std::uint32_t v = (std::uint32_t(data[i]) << 16) | (std::uint32_t(data[i + 1]) << 8);
|
||||
out.push_back(alphabet[(v >> 18) & 0x3F]);
|
||||
out.push_back(alphabet[(v >> 12) & 0x3F]);
|
||||
out.push_back(alphabet[(v >> 6) & 0x3F]);
|
||||
if (pad) {
|
||||
out.push_back('=');
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
int decodeChar(char c)
|
||||
{
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return c - 'A';
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return c - 'a' + 26;
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0' + 52;
|
||||
if (c == '+' || c == '-')
|
||||
return 62;
|
||||
if (c == '/' || c == '_')
|
||||
return 63;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::string base64Encode(const std::vector<std::uint8_t> &data)
|
||||
{
|
||||
return encode(data, kStd, true);
|
||||
}
|
||||
|
||||
std::string base64UrlEncodeNoPad(const std::vector<std::uint8_t> &data)
|
||||
{
|
||||
return encode(data, kUrl, false);
|
||||
}
|
||||
|
||||
std::string base64Encode(const std::string &data)
|
||||
{
|
||||
return base64Encode(std::vector<std::uint8_t>(data.begin(), data.end()));
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> base64Decode(const std::string &text)
|
||||
{
|
||||
std::vector<std::uint8_t> out;
|
||||
out.reserve(text.size() / 4 * 3 + 3);
|
||||
|
||||
std::array<int, 4> quad { };
|
||||
int count = 0;
|
||||
for (char c : text) {
|
||||
const int v = decodeChar(c);
|
||||
if (v < 0) {
|
||||
continue;
|
||||
}
|
||||
quad[count++] = v;
|
||||
if (count == 4) {
|
||||
out.push_back(static_cast<std::uint8_t>((quad[0] << 2) | (quad[1] >> 4)));
|
||||
out.push_back(static_cast<std::uint8_t>((quad[1] << 4) | (quad[2] >> 2)));
|
||||
out.push_back(static_cast<std::uint8_t>((quad[2] << 6) | quad[3]));
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
if (count == 2) {
|
||||
out.push_back(static_cast<std::uint8_t>((quad[0] << 2) | (quad[1] >> 4)));
|
||||
} else if (count == 3) {
|
||||
out.push_back(static_cast<std::uint8_t>((quad[0] << 2) | (quad[1] >> 4)));
|
||||
out.push_back(static_cast<std::uint8_t>((quad[1] << 4) | (quad[2] >> 2)));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
19
agw-sdk/src/util/base64.h
Normal file
19
agw-sdk/src/util/base64.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef AGW_UTIL_BASE64_H
|
||||
#define AGW_UTIL_BASE64_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace agw::util
|
||||
{
|
||||
std::string base64Encode(const std::vector<std::uint8_t> &data);
|
||||
|
||||
std::string base64UrlEncodeNoPad(const std::vector<std::uint8_t> &data);
|
||||
|
||||
std::vector<std::uint8_t> base64Decode(const std::string &text);
|
||||
|
||||
std::string base64Encode(const std::string &data);
|
||||
}
|
||||
|
||||
#endif
|
||||
108
agw-sdk/src/util/json.cpp
Normal file
108
agw-sdk/src/util/json.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "json.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace agw::util
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const char *kHex = "0123456789abcdef";
|
||||
|
||||
void appendEscaped(std::string &out, const std::string &s)
|
||||
{
|
||||
out.push_back('"');
|
||||
for (unsigned char c : s) {
|
||||
switch (c) {
|
||||
case '"': out += "\\\""; break;
|
||||
case '\\': out += "\\\\"; break;
|
||||
case '\b': out += "\\b"; break;
|
||||
case '\f': out += "\\f"; break;
|
||||
case '\n': out += "\\n"; break;
|
||||
case '\r': out += "\\r"; break;
|
||||
case '\t': out += "\\t"; break;
|
||||
default:
|
||||
if (c < 0x20) {
|
||||
out += "\\u00";
|
||||
out.push_back(kHex[c >> 4]);
|
||||
out.push_back(kHex[c & 0x0F]);
|
||||
} else {
|
||||
out.push_back(static_cast<char>(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
out.push_back('"');
|
||||
}
|
||||
|
||||
void appendIndent(std::string &out, int level)
|
||||
{
|
||||
out.append(static_cast<std::size_t>(level) * 4, ' ');
|
||||
}
|
||||
|
||||
void dumpValue(std::string &out, const Json &j, int indent);
|
||||
|
||||
void dumpObject(std::string &out, const Json &j, int indent)
|
||||
{
|
||||
out += "{\n";
|
||||
const int inner = indent + 1;
|
||||
std::size_t i = 0;
|
||||
const std::size_t n = j.size();
|
||||
for (auto it = j.begin(); it != j.end(); ++it, ++i) {
|
||||
appendIndent(out, inner);
|
||||
appendEscaped(out, it.key());
|
||||
out += ": ";
|
||||
dumpValue(out, it.value(), inner);
|
||||
if (i + 1 < n) {
|
||||
out.push_back(',');
|
||||
}
|
||||
out.push_back('\n');
|
||||
}
|
||||
appendIndent(out, indent);
|
||||
out.push_back('}');
|
||||
}
|
||||
|
||||
void dumpArray(std::string &out, const Json &j, int indent)
|
||||
{
|
||||
out += "[\n";
|
||||
const int inner = indent + 1;
|
||||
std::size_t i = 0;
|
||||
const std::size_t n = j.size();
|
||||
for (const auto &el : j) {
|
||||
appendIndent(out, inner);
|
||||
dumpValue(out, el, inner);
|
||||
if (i + 1 < n) {
|
||||
out.push_back(',');
|
||||
}
|
||||
out.push_back('\n');
|
||||
++i;
|
||||
}
|
||||
appendIndent(out, indent);
|
||||
out.push_back(']');
|
||||
}
|
||||
|
||||
void dumpValue(std::string &out, const Json &j, int indent)
|
||||
{
|
||||
switch (j.type()) {
|
||||
case Json::value_t::object: dumpObject(out, j, indent); break;
|
||||
case Json::value_t::array: dumpArray(out, j, indent); break;
|
||||
case Json::value_t::string: appendEscaped(out, j.get<std::string>()); break;
|
||||
case Json::value_t::boolean: out += j.get<bool>() ? "true" : "false"; break;
|
||||
case Json::value_t::null: out += "null"; break;
|
||||
case Json::value_t::number_integer:
|
||||
case Json::value_t::number_unsigned:
|
||||
case Json::value_t::number_float:
|
||||
default:
|
||||
|
||||
out += j.dump();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string qtIndentedDump(const Json &j)
|
||||
{
|
||||
std::string out;
|
||||
dumpValue(out, j, 0);
|
||||
out.push_back('\n');
|
||||
return out;
|
||||
}
|
||||
}
|
||||
15
agw-sdk/src/util/json.h
Normal file
15
agw-sdk/src/util/json.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef AGW_UTIL_JSON_H
|
||||
#define AGW_UTIL_JSON_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace agw::util
|
||||
{
|
||||
using Json = nlohmann::json;
|
||||
|
||||
std::string qtIndentedDump(const Json &j);
|
||||
}
|
||||
|
||||
#endif
|
||||
59
agw-sdk/src/util/thread_pool.cpp
Normal file
59
agw-sdk/src/util/thread_pool.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "util/thread_pool.h"
|
||||
|
||||
namespace agw::util
|
||||
{
|
||||
ThreadPool::ThreadPool(std::size_t threadCount)
|
||||
{
|
||||
if (threadCount == 0) {
|
||||
threadCount = 1;
|
||||
}
|
||||
m_workers.reserve(threadCount);
|
||||
for (std::size_t i = 0; i < threadCount; ++i) {
|
||||
m_workers.emplace_back([this] { workerLoop(); });
|
||||
}
|
||||
}
|
||||
|
||||
ThreadPool::~ThreadPool()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_stopping = true;
|
||||
}
|
||||
m_cv.notify_all();
|
||||
for (auto &w : m_workers) {
|
||||
if (w.joinable()) {
|
||||
w.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadPool::submit(std::function<void()> task)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_tasks.push(std::move(task));
|
||||
}
|
||||
m_cv.notify_one();
|
||||
}
|
||||
|
||||
void ThreadPool::workerLoop()
|
||||
{
|
||||
for (;;) {
|
||||
std::function<void()> task;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_cv.wait(lock, [this] { return m_stopping || !m_tasks.empty(); });
|
||||
|
||||
if (m_tasks.empty()) {
|
||||
if (m_stopping) {
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
task = std::move(m_tasks.front());
|
||||
m_tasks.pop();
|
||||
}
|
||||
task();
|
||||
}
|
||||
}
|
||||
}
|
||||
36
agw-sdk/src/util/thread_pool.h
Normal file
36
agw-sdk/src/util/thread_pool.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef AGW_UTIL_THREAD_POOL_H
|
||||
#define AGW_UTIL_THREAD_POOL_H
|
||||
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace agw::util
|
||||
{
|
||||
class ThreadPool
|
||||
{
|
||||
public:
|
||||
explicit ThreadPool(std::size_t threadCount);
|
||||
~ThreadPool();
|
||||
|
||||
ThreadPool(const ThreadPool &) = delete;
|
||||
ThreadPool &operator=(const ThreadPool &) = delete;
|
||||
|
||||
void submit(std::function<void()> task);
|
||||
|
||||
private:
|
||||
void workerLoop();
|
||||
|
||||
std::vector<std::thread> m_workers;
|
||||
std::queue<std::function<void()>> m_tasks;
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_cv;
|
||||
bool m_stopping = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
48
agw-sdk/src/util/url.cpp
Normal file
48
agw-sdk/src/util/url.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "util/url.h"
|
||||
|
||||
namespace agw::util
|
||||
{
|
||||
std::string formatEndpoint(const std::string &endpoint, const std::string &host)
|
||||
{
|
||||
const std::string token = "%1";
|
||||
const std::size_t pos = endpoint.find(token);
|
||||
if (pos == std::string::npos) {
|
||||
return endpoint;
|
||||
}
|
||||
std::string out = endpoint;
|
||||
out.replace(pos, token.size(), host);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string extractHost(const std::string &url)
|
||||
{
|
||||
std::size_t start = 0;
|
||||
const std::size_t scheme = url.find("://");
|
||||
if (scheme != std::string::npos) {
|
||||
start = scheme + 3;
|
||||
}
|
||||
|
||||
std::size_t end = url.size();
|
||||
for (std::size_t i = start; i < url.size(); ++i) {
|
||||
const char c = url[i];
|
||||
if (c == '/' || c == '?' || c == '#') {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string authority = url.substr(start, end - start);
|
||||
|
||||
const std::size_t at = authority.find('@');
|
||||
if (at != std::string::npos) {
|
||||
authority = authority.substr(at + 1);
|
||||
}
|
||||
|
||||
const std::size_t colon = authority.find(':');
|
||||
if (colon != std::string::npos) {
|
||||
authority = authority.substr(0, colon);
|
||||
}
|
||||
|
||||
return authority;
|
||||
}
|
||||
}
|
||||
13
agw-sdk/src/util/url.h
Normal file
13
agw-sdk/src/util/url.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef AGW_UTIL_URL_H
|
||||
#define AGW_UTIL_URL_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace agw::util
|
||||
{
|
||||
std::string formatEndpoint(const std::string &endpoint, const std::string &host);
|
||||
|
||||
std::string extractHost(const std::string &url);
|
||||
}
|
||||
|
||||
#endif
|
||||
36
agw-sdk/src/util/uuid.cpp
Normal file
36
agw-sdk/src/util/uuid.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "uuid.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace agw::util
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const char *kHex = "0123456789abcdef";
|
||||
|
||||
void appendHex(std::string &out, std::uint8_t b)
|
||||
{
|
||||
out.push_back(kHex[b >> 4]);
|
||||
out.push_back(kHex[b & 0x0F]);
|
||||
}
|
||||
}
|
||||
|
||||
std::string makeUuidV4(crypto::IRng &rng)
|
||||
{
|
||||
std::vector<std::uint8_t> b = rng.bytes(16);
|
||||
|
||||
b[6] = static_cast<std::uint8_t>((b[6] & 0x0F) | 0x40);
|
||||
b[8] = static_cast<std::uint8_t>((b[8] & 0x3F) | 0x80);
|
||||
|
||||
std::string out;
|
||||
out.reserve(36);
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (i == 4 || i == 6 || i == 8 || i == 10) {
|
||||
out.push_back('-');
|
||||
}
|
||||
appendHex(out, b[i]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
13
agw-sdk/src/util/uuid.h
Normal file
13
agw-sdk/src/util/uuid.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef AGW_UTIL_UUID_H
|
||||
#define AGW_UTIL_UUID_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "crypto/rng.h"
|
||||
|
||||
namespace agw::util
|
||||
{
|
||||
std::string makeUuidV4(crypto::IRng &rng);
|
||||
}
|
||||
|
||||
#endif
|
||||
27
agw-sdk/tests/CMakeLists.txt
Normal file
27
agw-sdk/tests/CMakeLists.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
# Тесты agw-sdk. Локально — на встроенном harness (agw_test.h), без внешнего фреймворка.
|
||||
|
||||
set(AGW_FIXTURES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/golden/fixtures")
|
||||
|
||||
function(agw_add_test name src)
|
||||
add_executable(${name} ${src})
|
||||
target_include_directories(${name} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
)
|
||||
target_link_libraries(${name} PRIVATE agw OpenSSL::Crypto nlohmann_json::nlohmann_json Threads::Threads)
|
||||
target_compile_definitions(${name} PRIVATE AGW_FIXTURES_DIR="${AGW_FIXTURES_DIR}")
|
||||
add_test(NAME ${name} COMMAND ${name})
|
||||
endfunction()
|
||||
|
||||
agw_add_test(test_crypto unit/test_crypto.cpp)
|
||||
agw_add_test(test_json unit/test_json.cpp)
|
||||
agw_add_test(test_golden golden/test_golden.cpp)
|
||||
agw_add_test(test_error_mapping unit/test_error_mapping.cpp)
|
||||
agw_add_test(test_bypass_policy unit/test_bypass_policy.cpp)
|
||||
agw_add_test(test_proxy_list unit/test_proxy_list.cpp)
|
||||
agw_add_test(test_thread_pool unit/test_thread_pool.cpp)
|
||||
agw_add_test(test_post integration/test_post.cpp)
|
||||
agw_add_test(test_failover integration/test_failover.cpp)
|
||||
agw_add_test(test_async integration/test_async.cpp)
|
||||
agw_add_test(test_c_abi integration/test_c_abi.cpp)
|
||||
39
agw-sdk/tests/agw_test.h
Normal file
39
agw-sdk/tests/agw_test.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef AGW_TEST_H
|
||||
#define AGW_TEST_H
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
namespace agw_test {
|
||||
inline int &failCount()
|
||||
{
|
||||
static int n = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
inline void report(bool ok, const char *expr, const char *file, int line)
|
||||
{
|
||||
if (!ok) {
|
||||
std::fprintf(stderr, "FAIL: %s\n at %s:%d\n", expr, file, line);
|
||||
++failCount();
|
||||
}
|
||||
}
|
||||
|
||||
inline void reportEq(const std::string &a, const std::string &b, const char *expr, const char *file, int line)
|
||||
{
|
||||
if (a != b) {
|
||||
std::fprintf(stderr, "FAIL: %s\n at %s:%d\n lhs=[%s]\n rhs=[%s]\n",
|
||||
expr, file, line, a.c_str(), b.c_str());
|
||||
++failCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define CHECK(expr) ::agw_test::report((expr), #expr, __FILE__, __LINE__)
|
||||
#define CHECK_EQ(a, b) ::agw_test::reportEq((a), (b), #a " == " #b, __FILE__, __LINE__)
|
||||
|
||||
#define AGW_TEST_MAIN_RETURN() \
|
||||
(::agw_test::failCount() == 0 ? (std::printf("OK\n"), 0) : (std::fprintf(stderr, "%d check(s) failed\n", ::agw_test::failCount()), 1))
|
||||
|
||||
#endif
|
||||
1
agw-sdk/tests/golden/fixtures/aes_cipher.bin
Normal file
1
agw-sdk/tests/golden/fixtures/aes_cipher.bin
Normal file
@@ -0,0 +1 @@
|
||||
ыaГцЖ7И~▐>ъl╛▌Ц╗ЦaJ╩┤ъ%фTхO╒╢^
|
||||
1
agw-sdk/tests/golden/fixtures/aes_plaintext.bin
Normal file
1
agw-sdk/tests/golden/fixtures/aes_plaintext.bin
Normal file
@@ -0,0 +1 @@
|
||||
{"hello":"world"}
|
||||
4
agw-sdk/tests/golden/fixtures/aes_vector.txt
Normal file
4
agw-sdk/tests/golden/fixtures/aes_vector.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
AES_KEY_HEX=000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
|
||||
AES_IV_HEX=101112131415161718191a1b1c1d1e1f
|
||||
AES_PLAINTEXT={"hello":"world"}
|
||||
AES_CIPHER_B64=2WHnAcP2N+l+jz7fbKyO46jjYUq7h98lxlTIT6K0Xg8=
|
||||
28
agw-sdk/tests/golden/fixtures/test_rsa_priv.pem
Normal file
28
agw-sdk/tests/golden/fixtures/test_rsa_priv.pem
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDL3pxE7uI3RacQ
|
||||
jvyFz5tbkL87aqpFBvAVFS0OFzVLbApaJ6nv2jZNfidbPCN5SMeNoa2kNTC+MZNQ
|
||||
qORgr77TgaRuFap5dSun9qci0ll5Y/zBQHb8/Xihah8YpkbO/8SV1aFWLtWKQiQL
|
||||
xrFTD9ShXC7S6IQrdGcngUhLShinWmjveZJ//B7no3wUP3xPF+EkTXTq5QyD/SfQ
|
||||
/w0BUosy55sCn5OyP6iSJ4cqujA7WCEd48XpS5zgceqdnE98mvjhriMDfORXsSnx
|
||||
ymsDnPyX/wkrLOynpN5KPoCMwXS0knTSGADUPMm/EQXa5fjEDxg0OnJQcJV/mZJk
|
||||
4Rgi+gZJAgMBAAECggEAXWW2ob3u1POL/gIDninmOqStd0L+jnEHPCFfar0nJU5x
|
||||
z6usJr4JcqcA0MNUXRQCl9gh/MCBfCCqJKG7PrBE9BDIi8ZROyN6xJAzMbi8VOiB
|
||||
uucVnAFjak97v4ctmVeDcEFWkG0UVyrF6L82LZ9rAiGBMg5jvqStPWP1AskHUmM+
|
||||
drJZYsrvqqqZLVbB34I6logBcD0IKEib8uBM3brLrO1t86XpLvOIJEjCsD+GRtmJ
|
||||
vnjjVcIqHFM+VyA+RvpgMnfbTcG3D4YZhDtdgsbnOHs4mydM/I6C7a5pLftKsdoC
|
||||
lKgb6CIqJoXvW2goljHfQiBre56hwhBjRgzmdo+BoQKBgQD+aM+P1+nAsIUxWmXf
|
||||
jexL8LIQ+POxNztz/d9EIYLXOztiS3epwt8e/Xqffu4B5+D1j6u/gtnK90SNcMi2
|
||||
IhetBwkTGdz6s/JHTt92f7okRbSnzwZwj03Ppkgg3VAPp3LL4nP/a1kcHi+9aqBB
|
||||
kPPkQ0k9BS/JOmEVPOKpOx0ABwKBgQDNJOjDyhKesjjLK33xJdD6sdhwxFJrSMs3
|
||||
TtMd0KvPu26Z+gn+5ybE/rzOoO7YZIUmB8AXvNKLu8U/5FRE6eOeaumz2LvFNCzE
|
||||
IC6J2Oixt/lVxxuR0mSRTJI/hR0CtrofXRke8YjeU3VjtkMW6k2QrPAAMMTPieRW
|
||||
fkfb8oWTLwKBgQCt0B/W77XFLxSgplkphgYlz/loPR4JOmoFEjLCkn6Y29/zhQnp
|
||||
UrkrrBRl+ctUQ/7e5lx5yEVSNOOCGscWIG66iS76/NWL9vsVGt7zT8p106XcbEXD
|
||||
CzUnJDztLybuuwFkKIAFxmqoGjuVls6MXSM0FYBpDy0Ztyfy4Zkd88QZawKBgHB8
|
||||
rKWvSEZ8s2e0kXqJoe3VVzl+bTMm10eckWbn5U4jGKKV2KVNWpTqmd0zocRGWjxg
|
||||
Q5TAlTLJ438FVK/1EDrtpPhY/51C3sksXFh5+B57It1GMHflRf/mXMs30pCKYcSQ
|
||||
6BVvm/1NBjGG34LRN3b9XRy9oS2sDujelcilU1lBAoGBAMHx0oFSlNUHGofigo3p
|
||||
26kWJw7BE/o8HVIrfI2vekXOgTVgJWGiubjp3qUKgNf1lbRy/Ur9F4ElW7hEINjC
|
||||
aMJUZWc/xYwAmKHe/7ITZByfRXdGMwlvo8QudbzDC6vvCqn7wQW56kyTTEEHF54k
|
||||
21jK19EBX4JWibzJotv8ShkU
|
||||
-----END PRIVATE KEY-----
|
||||
9
agw-sdk/tests/golden/fixtures/test_rsa_pub.pem
Normal file
9
agw-sdk/tests/golden/fixtures/test_rsa_pub.pem
Normal file
@@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy96cRO7iN0WnEI78hc+b
|
||||
W5C/O2qqRQbwFRUtDhc1S2wKWiep79o2TX4nWzwjeUjHjaGtpDUwvjGTUKjkYK++
|
||||
04GkbhWqeXUrp/anItJZeWP8wUB2/P14oWofGKZGzv/EldWhVi7VikIkC8axUw/U
|
||||
oVwu0uiEK3RnJ4FIS0oYp1po73mSf/we56N8FD98TxfhJE106uUMg/0n0P8NAVKL
|
||||
MuebAp+Tsj+okieHKrowO1ghHePF6Uuc4HHqnZxPfJr44a4jA3zkV7Ep8cprA5z8
|
||||
l/8JKyzsp6TeSj6AjMF0tJJ00hgA1DzJvxEF2uX4xA8YNDpyUHCVf5mSZOEYIvoG
|
||||
SQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
97
agw-sdk/tests/golden/test_golden.cpp
Normal file
97
agw-sdk/tests/golden/test_golden.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#include "agw_test.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "crypto/aes.h"
|
||||
#include "crypto/hash.h"
|
||||
#include "crypto/rsa.h"
|
||||
#include "protocol/keys.h"
|
||||
#include "util/base64.h"
|
||||
#include "util/json.h"
|
||||
|
||||
using namespace agw;
|
||||
|
||||
namespace {
|
||||
std::vector<std::uint8_t> bytesOf(const std::string &s)
|
||||
{
|
||||
return std::vector<std::uint8_t>(s.begin(), s.end());
|
||||
}
|
||||
|
||||
std::string toStr(const std::vector<std::uint8_t> &v)
|
||||
{
|
||||
return std::string(v.begin(), v.end());
|
||||
}
|
||||
|
||||
std::string readFile(const std::string &path)
|
||||
{
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
std::ostringstream ss;
|
||||
ss << f.rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
namespace k = protocol::keys;
|
||||
|
||||
const auto key = crypto::fromHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
|
||||
const auto iv = crypto::fromHex("101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f");
|
||||
const auto salt = crypto::fromHex("a0a1a2a3a4a5a6a7");
|
||||
const std::string payload = "{\"hello\":\"world\"}";
|
||||
|
||||
util::Json keysJson;
|
||||
keysJson[k::aesKey] = util::base64Encode(key);
|
||||
keysJson[k::aesIv] = util::base64Encode(iv);
|
||||
keysJson[k::aesSalt] = util::base64Encode(salt);
|
||||
const std::string keysSerialized = util::qtIndentedDump(keysJson);
|
||||
|
||||
const std::string expectedKeysJson =
|
||||
"{\n"
|
||||
" \"aes_iv\": \"EBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8=\",\n"
|
||||
" \"aes_key\": \"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=\",\n"
|
||||
" \"aes_salt\": \"oKGio6Slpqc=\"\n"
|
||||
"}\n";
|
||||
CHECK_EQ(keysSerialized, expectedKeysJson);
|
||||
|
||||
const auto apiCipher = crypto::aesEncryptCbc(bytesOf(payload), key, iv);
|
||||
const std::string apiPayloadB64 = util::base64Encode(apiCipher);
|
||||
CHECK_EQ(apiPayloadB64, std::string("2WHnAcP2N+l+jz7fbKyO46jjYUq7h98lxlTIT6K0Xg8="));
|
||||
|
||||
const std::string pub = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_pub.pem");
|
||||
const std::string priv = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_priv.pem");
|
||||
CHECK(!pub.empty());
|
||||
CHECK(!priv.empty());
|
||||
|
||||
const auto keyCipher = crypto::rsaEncryptPublicPkcs1(bytesOf(keysSerialized), pub);
|
||||
const std::string keyPayloadB64 = util::base64Encode(keyCipher);
|
||||
|
||||
const auto keyCipherBack = util::base64Decode(keyPayloadB64);
|
||||
const auto recovered = crypto::rsaDecryptPrivatePkcs1(keyCipherBack, priv);
|
||||
CHECK_EQ(toStr(recovered), keysSerialized);
|
||||
|
||||
util::Json body;
|
||||
body[k::keyPayload] = keyPayloadB64;
|
||||
body[k::apiPayload] = apiPayloadB64;
|
||||
const std::string bodySerialized = util::qtIndentedDump(body);
|
||||
|
||||
util::Json parsed = util::Json::parse(bodySerialized);
|
||||
CHECK_EQ(parsed[k::apiPayload].get<std::string>(), apiPayloadB64);
|
||||
{
|
||||
const auto cBack = util::base64Decode(parsed[k::keyPayload].get<std::string>());
|
||||
const auto rec = crypto::rsaDecryptPrivatePkcs1(cBack, priv);
|
||||
CHECK_EQ(toStr(rec), keysSerialized);
|
||||
}
|
||||
|
||||
{
|
||||
const auto respPlain = bytesOf("{\"ok\":true}");
|
||||
const auto respCipher = crypto::aesEncryptCbc(respPlain, key, iv);
|
||||
const auto back = crypto::aesDecryptCbc(respCipher, key, iv);
|
||||
CHECK(back == respPlain);
|
||||
}
|
||||
|
||||
return AGW_TEST_MAIN_RETURN();
|
||||
}
|
||||
174
agw-sdk/tests/integration/test_async.cpp
Normal file
174
agw-sdk/tests/integration/test_async.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "agw_test.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "agw/cancellation.h"
|
||||
#include "agw/gateway_controller.h"
|
||||
#include "agw/config.h"
|
||||
#include "crypto/aes.h"
|
||||
#include "crypto/rsa.h"
|
||||
#include "mock_gateway/mock_gateway.h"
|
||||
#include "protocol/keys.h"
|
||||
#include "util/base64.h"
|
||||
#include "util/json.h"
|
||||
|
||||
using namespace agw;
|
||||
|
||||
namespace {
|
||||
std::string readFile(const std::string &path)
|
||||
{
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
std::ostringstream ss;
|
||||
ss << f.rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
Config baseConfig(std::shared_ptr<IHttpClient> http, const std::string &pub)
|
||||
{
|
||||
Config c;
|
||||
c.gatewayEndpoint = "gw.example.test";
|
||||
c.agwPublicKeyPem = pub;
|
||||
c.requestTimeoutMsecs = 5000;
|
||||
c.httpClient = std::move(http);
|
||||
return c;
|
||||
}
|
||||
|
||||
class BlockingUntilCancelMock : public IHttpClient {
|
||||
public:
|
||||
std::atomic<int> entered{0};
|
||||
HttpResponse send(const HttpRequest &req) override
|
||||
{
|
||||
entered.fetch_add(1);
|
||||
while (!(req.cancelCheck && req.cancelCheck())) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||
}
|
||||
HttpResponse r;
|
||||
r.error = TransportError::Canceled;
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
class StatelessMock : public IHttpClient {
|
||||
public:
|
||||
explicit StatelessMock(std::string priv) : m_priv(std::move(priv)) {}
|
||||
std::atomic<int> count{0};
|
||||
HttpResponse send(const HttpRequest &req) override
|
||||
{
|
||||
count.fetch_add(1);
|
||||
namespace k = protocol::keys;
|
||||
util::Json body = util::Json::parse(req.body);
|
||||
const auto keyCipher = util::base64Decode(body[k::keyPayload].get<std::string>());
|
||||
const auto keysBytes = crypto::rsaDecryptPrivatePkcs1(keyCipher, m_priv);
|
||||
util::Json keysJson = util::Json::parse(std::string(keysBytes.begin(), keysBytes.end()));
|
||||
const auto aesKey = util::base64Decode(keysJson[k::aesKey].get<std::string>());
|
||||
const auto aesIv = util::base64Decode(keysJson[k::aesIv].get<std::string>());
|
||||
|
||||
const std::string plain = R"({"ok":true})";
|
||||
const std::vector<std::uint8_t> pv(plain.begin(), plain.end());
|
||||
const auto cipher = crypto::aesEncryptCbc(pv, aesKey, aesIv);
|
||||
HttpResponse r;
|
||||
r.httpStatusCode = 200;
|
||||
r.body.assign(cipher.begin(), cipher.end());
|
||||
return r;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_priv;
|
||||
};
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
const std::string pub = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_pub.pem");
|
||||
const std::string priv = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_priv.pem");
|
||||
const std::string endpoint = "https://%1/api/v1/test";
|
||||
const FailoverContext ctx{"prem", "US"};
|
||||
const std::string payload = R"({"hello":"world"})";
|
||||
|
||||
{
|
||||
auto mock = std::make_shared<agw_test::MockGateway>(priv);
|
||||
mock->responsePlain = R"({"ok":true,"v":1})";
|
||||
GatewayController client(baseConfig(mock, pub));
|
||||
std::future<Response> f = client.postFuture(endpoint, payload, ctx);
|
||||
Response r = f.get();
|
||||
CHECK(r.error == ErrorCode::NoError);
|
||||
CHECK_EQ(r.body, std::string(R"({"ok":true,"v":1})"));
|
||||
CHECK_EQ(mock->lastDecryptedPayload, payload);
|
||||
}
|
||||
|
||||
{
|
||||
auto mock = std::make_shared<agw_test::MockGateway>(priv);
|
||||
mock->responsePlain = R"({"async":true})";
|
||||
GatewayController client(baseConfig(mock, pub));
|
||||
|
||||
std::promise<Response> p;
|
||||
std::future<Response> f = p.get_future();
|
||||
client.postAsync(
|
||||
endpoint, payload, [&p](Response r) { p.set_value(std::move(r)); }, ctx);
|
||||
Response r = f.get();
|
||||
CHECK(r.error == ErrorCode::NoError);
|
||||
CHECK_EQ(r.body, std::string(R"({"async":true})"));
|
||||
}
|
||||
|
||||
{
|
||||
auto mock = std::make_shared<agw_test::MockGateway>(priv);
|
||||
GatewayController client(baseConfig(mock, pub));
|
||||
CancellationToken token;
|
||||
token.cancel();
|
||||
std::future<Response> f = client.postFuture(endpoint, payload, ctx, &token);
|
||||
Response r = f.get();
|
||||
CHECK(r.error == ErrorCode::Cancelled);
|
||||
CHECK(mock->requestCount == 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto mock = std::make_shared<BlockingUntilCancelMock>();
|
||||
GatewayController client(baseConfig(mock, pub));
|
||||
CancellationToken token;
|
||||
|
||||
std::promise<Response> p;
|
||||
std::future<Response> f = p.get_future();
|
||||
client.postAsync(
|
||||
endpoint, payload, [&p](Response r) { p.set_value(std::move(r)); }, ctx, &token);
|
||||
|
||||
while (mock->entered.load() == 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||
}
|
||||
token.cancel();
|
||||
Response r = f.get();
|
||||
CHECK(r.error == ErrorCode::Cancelled);
|
||||
}
|
||||
|
||||
{
|
||||
auto mock = std::make_shared<StatelessMock>(priv);
|
||||
Config cfg = baseConfig(mock, pub);
|
||||
cfg.threadPoolSize = 8;
|
||||
GatewayController client(std::move(cfg));
|
||||
|
||||
constexpr int N = 64;
|
||||
std::vector<std::future<Response>> futs;
|
||||
futs.reserve(N);
|
||||
for (int i = 0; i < N; ++i) {
|
||||
futs.push_back(client.postFuture(endpoint, payload, ctx));
|
||||
}
|
||||
int ok = 0;
|
||||
for (auto &fut : futs) {
|
||||
Response r = fut.get();
|
||||
if (r.error == ErrorCode::NoError && r.body == R"({"ok":true})") {
|
||||
++ok;
|
||||
}
|
||||
}
|
||||
CHECK(ok == N);
|
||||
CHECK(mock->count.load() == N);
|
||||
}
|
||||
|
||||
return AGW_TEST_MAIN_RETURN();
|
||||
}
|
||||
118
agw-sdk/tests/integration/test_c_abi.cpp
Normal file
118
agw-sdk/tests/integration/test_c_abi.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#include "agw_test.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "agw/c_abi.h"
|
||||
#include "agw/types.h"
|
||||
#include "detail/test_hooks.h"
|
||||
#include "mock_gateway/mock_gateway.h"
|
||||
|
||||
namespace {
|
||||
std::string readFile(const std::string &path)
|
||||
{
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
std::ostringstream ss;
|
||||
ss << f.rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
agw_config makeConfig(const char *gateway, const char *pem)
|
||||
{
|
||||
agw_config c{};
|
||||
c.gateway_endpoint = gateway;
|
||||
c.agw_public_key_pem = pem;
|
||||
c.request_timeout_msecs = 5000;
|
||||
return c;
|
||||
}
|
||||
|
||||
struct AsyncSink {
|
||||
std::promise<std::pair<int, std::string>> promise;
|
||||
};
|
||||
|
||||
void asyncCallback(agw_response r, void *ud)
|
||||
{
|
||||
auto *sink = static_cast<AsyncSink *>(ud);
|
||||
sink->promise.set_value({r.error, r.body ? std::string(r.body, r.body_len) : std::string()});
|
||||
agw_response_free(&r);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
const std::string pub = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_pub.pem");
|
||||
const std::string priv = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_priv.pem");
|
||||
const std::string payload = R"({"hello":"world"})";
|
||||
|
||||
{
|
||||
auto mock = std::make_shared<agw_test::MockGateway>(priv);
|
||||
mock->responsePlain = R"({"ok":true,"c":1})";
|
||||
agw::detail::setNextTestHttpClient(mock);
|
||||
|
||||
agw_config cfg = makeConfig("gw.example.test", pub.c_str());
|
||||
agw_client *client = agw_client_create(&cfg);
|
||||
CHECK(client != nullptr);
|
||||
|
||||
agw_response r = agw_client_post(client, "https://%1/api/v1/test", payload.c_str(), "prem", "US", nullptr);
|
||||
CHECK(r.error == 0);
|
||||
CHECK(r.body != nullptr);
|
||||
CHECK_EQ(std::string(r.body, r.body_len), std::string(R"({"ok":true,"c":1})"));
|
||||
CHECK_EQ(mock->lastDecryptedPayload, payload);
|
||||
agw_response_free(&r);
|
||||
CHECK(r.body == nullptr);
|
||||
|
||||
mock->responsePlain = R"({"async":1})";
|
||||
AsyncSink sink;
|
||||
auto fut = sink.promise.get_future();
|
||||
agw_client_post_async(client, "https://%1/api/v1/test", payload.c_str(), "prem", "US",
|
||||
&asyncCallback, &sink, nullptr);
|
||||
auto [err, body] = fut.get();
|
||||
CHECK(err == 0);
|
||||
CHECK_EQ(body, std::string(R"({"async":1})"));
|
||||
|
||||
agw_client_destroy(client);
|
||||
}
|
||||
|
||||
{
|
||||
auto mock = std::make_shared<agw_test::MockGateway>(priv);
|
||||
agw::detail::setNextTestHttpClient(mock);
|
||||
agw_config cfg = makeConfig("gw.example.test", pub.c_str());
|
||||
agw_client *client = agw_client_create(&cfg);
|
||||
|
||||
agw_cancel_token *token = agw_cancel_token_create();
|
||||
CHECK(token != nullptr);
|
||||
agw_cancel_token_cancel(token);
|
||||
|
||||
agw_response r = agw_client_post(client, "https://%1/api/v1/test", payload.c_str(), "", "", token);
|
||||
CHECK(r.error == static_cast<int>(agw::ErrorCode::Cancelled));
|
||||
CHECK(mock->requestCount == 0);
|
||||
agw_response_free(&r);
|
||||
|
||||
agw_cancel_token_destroy(token);
|
||||
agw_client_destroy(client);
|
||||
}
|
||||
|
||||
{
|
||||
agw_config cfg = makeConfig("gw.example.test", "not a pem");
|
||||
agw_client *client = agw_client_create(&cfg);
|
||||
CHECK(client != nullptr);
|
||||
agw_response r = agw_client_post(client, "https://%1/x", payload.c_str(), "", "", nullptr);
|
||||
CHECK(r.error == static_cast<int>(agw::ErrorCode::ApiMissingAgwPublicKey));
|
||||
agw_response_free(&r);
|
||||
agw_client_destroy(client);
|
||||
}
|
||||
|
||||
{
|
||||
CHECK(agw_client_create(nullptr) == nullptr);
|
||||
agw_response r = agw_client_post(nullptr, "e", "p", "", "", nullptr);
|
||||
CHECK(r.error != 0);
|
||||
agw_response_free(&r);
|
||||
agw_client_destroy(nullptr);
|
||||
}
|
||||
|
||||
return AGW_TEST_MAIN_RETURN();
|
||||
}
|
||||
125
agw-sdk/tests/integration/test_failover.cpp
Normal file
125
agw-sdk/tests/integration/test_failover.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "agw_test.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "agw/gateway_controller.h"
|
||||
#include "agw/config.h"
|
||||
#include "crypto/aes.h"
|
||||
#include "crypto/rsa.h"
|
||||
#include "protocol/keys.h"
|
||||
#include "util/base64.h"
|
||||
#include "util/json.h"
|
||||
|
||||
using namespace agw;
|
||||
|
||||
namespace {
|
||||
std::string readFile(const std::string &path)
|
||||
{
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
std::ostringstream ss;
|
||||
ss << f.rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool contains(const std::string &h, const std::string &n) { return h.find(n) != std::string::npos; }
|
||||
|
||||
class FailoverMock : public IHttpClient {
|
||||
public:
|
||||
explicit FailoverMock(std::string priv) : m_priv(std::move(priv)) {}
|
||||
|
||||
int directPosts = 0, proxyPosts = 0, storageGets = 0, healthGets = 0;
|
||||
|
||||
HttpResponse send(const HttpRequest &req) override
|
||||
{
|
||||
HttpResponse resp;
|
||||
resp.httpStatusCode = 200;
|
||||
|
||||
if (req.method == "GET") {
|
||||
if (contains(req.url, "lmbd-health")) {
|
||||
++healthGets;
|
||||
if (!contains(req.url, "proxy.good.test")) {
|
||||
resp.error = TransportError::ConnectionError;
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
++storageGets;
|
||||
resp.body = R"(["https://proxy.good.test/"])";
|
||||
return resp;
|
||||
}
|
||||
|
||||
namespace k = protocol::keys;
|
||||
util::Json body = util::Json::parse(req.body);
|
||||
const auto keyCipher = util::base64Decode(body[k::keyPayload].get<std::string>());
|
||||
const auto keysBytes = crypto::rsaDecryptPrivatePkcs1(keyCipher, m_priv);
|
||||
util::Json keysJson = util::Json::parse(std::string(keysBytes.begin(), keysBytes.end()));
|
||||
const auto aesKey = util::base64Decode(keysJson[k::aesKey].get<std::string>());
|
||||
const auto aesIv = util::base64Decode(keysJson[k::aesIv].get<std::string>());
|
||||
|
||||
std::string plain;
|
||||
if (contains(req.url, "proxy.good.test")) {
|
||||
++proxyPosts;
|
||||
plain = R"({"ok":true,"via":"proxy"})";
|
||||
} else {
|
||||
++directPosts;
|
||||
plain = R"({"http_status":404,"message":"blocked"})";
|
||||
}
|
||||
const std::vector<std::uint8_t> pv(plain.begin(), plain.end());
|
||||
const auto cipher = crypto::aesEncryptCbc(pv, aesKey, aesIv);
|
||||
resp.body.assign(cipher.begin(), cipher.end());
|
||||
return resp;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_priv;
|
||||
};
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
const std::string pub = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_pub.pem");
|
||||
const std::string priv = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_priv.pem");
|
||||
|
||||
auto mock = std::make_shared<FailoverMock>(priv);
|
||||
|
||||
Config cfg;
|
||||
cfg.gatewayEndpoint = "https://gw.example.test/";
|
||||
cfg.agwPublicKeyPem = pub;
|
||||
cfg.isDevEnvironment = true;
|
||||
cfg.s3PrimaryEndpoints = {"https://s3.example.test/"};
|
||||
cfg.requestTimeoutMsecs = 5000;
|
||||
cfg.httpClient = mock;
|
||||
|
||||
GatewayController client(std::move(cfg));
|
||||
const std::string endpoint = "%1api/v1/test";
|
||||
const FailoverContext ctx{"prem", "US"};
|
||||
const std::string payload = R"({"hello":"world"})";
|
||||
|
||||
{
|
||||
Response r = client.post(endpoint, payload, ctx);
|
||||
CHECK(r.error == ErrorCode::NoError);
|
||||
CHECK_EQ(r.body, std::string(R"({"ok":true,"via":"proxy"})"));
|
||||
CHECK(mock->directPosts == 1);
|
||||
CHECK(mock->storageGets >= 1);
|
||||
CHECK(mock->healthGets >= 1);
|
||||
CHECK(mock->proxyPosts == 1);
|
||||
}
|
||||
|
||||
{
|
||||
const int storageBefore = mock->storageGets;
|
||||
const int healthBefore = mock->healthGets;
|
||||
Response r = client.post(endpoint, payload, ctx);
|
||||
CHECK(r.error == ErrorCode::NoError);
|
||||
CHECK_EQ(r.body, std::string(R"({"ok":true,"via":"proxy"})"));
|
||||
|
||||
CHECK(mock->storageGets == storageBefore);
|
||||
CHECK(mock->healthGets == healthBefore);
|
||||
CHECK(mock->directPosts == 1);
|
||||
}
|
||||
|
||||
return AGW_TEST_MAIN_RETURN();
|
||||
}
|
||||
103
agw-sdk/tests/integration/test_post.cpp
Normal file
103
agw-sdk/tests/integration/test_post.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "agw_test.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "agw/gateway_controller.h"
|
||||
#include "agw/config.h"
|
||||
#include "mock_gateway/mock_gateway.h"
|
||||
|
||||
using namespace agw;
|
||||
|
||||
namespace {
|
||||
std::string readFile(const std::string &path)
|
||||
{
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
std::ostringstream ss;
|
||||
ss << f.rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
Config baseConfig(std::shared_ptr<IHttpClient> http, const std::string &pubPem)
|
||||
{
|
||||
Config c;
|
||||
c.gatewayEndpoint = "gw.example.test";
|
||||
c.agwPublicKeyPem = pubPem;
|
||||
c.requestTimeoutMsecs = 5000;
|
||||
c.httpClient = std::move(http);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
const std::string pub = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_pub.pem");
|
||||
const std::string priv = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_priv.pem");
|
||||
const std::string endpoint = "https://%1/api/v1/test";
|
||||
const FailoverContext ctx{"premium", "US"};
|
||||
const std::string payload = R"({"hello":"world","n":42})";
|
||||
|
||||
{
|
||||
auto mock = std::make_shared<agw_test::MockGateway>(priv);
|
||||
mock->responsePlain = R"({"ok":true,"data":"hi"})";
|
||||
|
||||
std::string seenHost;
|
||||
Config cfg = baseConfig(mock, pub);
|
||||
cfg.onBeforeRequest = [&](const std::string &h) { seenHost = h; };
|
||||
|
||||
GatewayController client(std::move(cfg));
|
||||
Response r = client.post(endpoint, payload, ctx);
|
||||
|
||||
CHECK(r.error == ErrorCode::NoError);
|
||||
CHECK_EQ(r.body, std::string(R"({"ok":true,"data":"hi"})"));
|
||||
|
||||
CHECK_EQ(mock->lastDecryptedPayload, payload);
|
||||
|
||||
CHECK_EQ(mock->lastUrl, std::string("https://gw.example.test/api/v1/test"));
|
||||
CHECK_EQ(seenHost, std::string("gw.example.test"));
|
||||
CHECK(mock->requestCount == 1);
|
||||
|
||||
CHECK(mock->lastRequestId.size() == 36);
|
||||
CHECK(mock->lastRequestId[14] == '4');
|
||||
}
|
||||
|
||||
{
|
||||
auto mock = std::make_shared<agw_test::MockGateway>(priv);
|
||||
mock->responsePlain = R"({"http_status":409,"message":"limit"})";
|
||||
GatewayController client(baseConfig(mock, pub));
|
||||
Response r = client.post(endpoint, payload, ctx);
|
||||
CHECK(r.error == ErrorCode::ApiConfigLimitError);
|
||||
|
||||
CHECK_EQ(r.body, std::string(R"({"http_status":409,"message":"limit"})"));
|
||||
}
|
||||
|
||||
{
|
||||
auto mock = std::make_shared<agw_test::MockGateway>(priv);
|
||||
mock->simulateSsl = true;
|
||||
GatewayController client(baseConfig(mock, pub));
|
||||
Response r = client.post(endpoint, payload, ctx);
|
||||
CHECK(r.error == ErrorCode::ApiConfigSslError);
|
||||
}
|
||||
|
||||
{
|
||||
auto mock = std::make_shared<agw_test::MockGateway>(priv);
|
||||
mock->simulateTransport = TransportError::Timeout;
|
||||
GatewayController client(baseConfig(mock, pub));
|
||||
Response r = client.post(endpoint, payload, ctx);
|
||||
CHECK(r.error == ErrorCode::ApiConfigTimeoutError);
|
||||
}
|
||||
|
||||
{
|
||||
auto mock = std::make_shared<agw_test::MockGateway>(priv);
|
||||
Config cfg = baseConfig(mock, "not a pem key");
|
||||
GatewayController client(std::move(cfg));
|
||||
Response r = client.post(endpoint, payload, ctx);
|
||||
CHECK(r.error == ErrorCode::ApiMissingAgwPublicKey);
|
||||
|
||||
CHECK(mock->requestCount == 0);
|
||||
}
|
||||
|
||||
return AGW_TEST_MAIN_RETURN();
|
||||
}
|
||||
77
agw-sdk/tests/mock_gateway/mock_gateway.h
Normal file
77
agw-sdk/tests/mock_gateway/mock_gateway.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#ifndef AGW_TEST_MOCK_GATEWAY_H
|
||||
#define AGW_TEST_MOCK_GATEWAY_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "agw/http.h"
|
||||
#include "crypto/aes.h"
|
||||
#include "crypto/rsa.h"
|
||||
#include "protocol/keys.h"
|
||||
#include "util/base64.h"
|
||||
#include "util/json.h"
|
||||
|
||||
namespace agw_test {
|
||||
class MockGateway : public agw::IHttpClient {
|
||||
public:
|
||||
explicit MockGateway(std::string privateKeyPem) : m_priv(std::move(privateKeyPem)) {}
|
||||
|
||||
std::string responsePlain = "{\"ok\":true}";
|
||||
bool simulateSsl = false;
|
||||
agw::TransportError simulateTransport = agw::TransportError::None;
|
||||
int httpStatusCode = 200;
|
||||
|
||||
std::string lastUrl;
|
||||
std::string lastRequestId;
|
||||
std::string lastDecryptedPayload;
|
||||
int requestCount = 0;
|
||||
|
||||
agw::HttpResponse send(const agw::HttpRequest &req) override
|
||||
{
|
||||
++requestCount;
|
||||
lastUrl = req.url;
|
||||
for (const auto &h : req.headers) {
|
||||
if (h.first == "X-Client-Request-ID") {
|
||||
lastRequestId = h.second;
|
||||
}
|
||||
}
|
||||
|
||||
agw::HttpResponse resp;
|
||||
resp.httpStatusCode = httpStatusCode;
|
||||
|
||||
if (simulateSsl) {
|
||||
resp.sslError = true;
|
||||
resp.error = agw::TransportError::ConnectionError;
|
||||
return resp;
|
||||
}
|
||||
if (simulateTransport != agw::TransportError::None) {
|
||||
resp.error = simulateTransport;
|
||||
return resp;
|
||||
}
|
||||
|
||||
namespace k = agw::protocol::keys;
|
||||
agw::util::Json body = agw::util::Json::parse(req.body);
|
||||
|
||||
const auto keyCipher = agw::util::base64Decode(body[k::keyPayload].get<std::string>());
|
||||
const auto keysBytes = agw::crypto::rsaDecryptPrivatePkcs1(keyCipher, m_priv);
|
||||
agw::util::Json keysJson = agw::util::Json::parse(std::string(keysBytes.begin(), keysBytes.end()));
|
||||
const auto aesKey = agw::util::base64Decode(keysJson[k::aesKey].get<std::string>());
|
||||
const auto aesIv = agw::util::base64Decode(keysJson[k::aesIv].get<std::string>());
|
||||
|
||||
const auto apiCipher = agw::util::base64Decode(body[k::apiPayload].get<std::string>());
|
||||
const auto payloadBytes = agw::crypto::aesDecryptCbc(apiCipher, aesKey, aesIv);
|
||||
lastDecryptedPayload.assign(payloadBytes.begin(), payloadBytes.end());
|
||||
|
||||
const std::vector<std::uint8_t> respPlain(responsePlain.begin(), responsePlain.end());
|
||||
const auto respCipher = agw::crypto::aesEncryptCbc(respPlain, aesKey, aesIv);
|
||||
resp.body.assign(respCipher.begin(), respCipher.end());
|
||||
resp.error = agw::TransportError::None;
|
||||
return resp;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_priv;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
24765
agw-sdk/tests/third_party/nlohmann/json.hpp
vendored
Normal file
24765
agw-sdk/tests/third_party/nlohmann/json.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
48
agw-sdk/tests/unit/test_bypass_policy.cpp
Normal file
48
agw-sdk/tests/unit/test_bypass_policy.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "agw_test.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "failover/bypass_policy.h"
|
||||
|
||||
using namespace agw;
|
||||
using agw::failover::shouldBypassProxy;
|
||||
|
||||
namespace {
|
||||
bool bypassBody(const std::string &body)
|
||||
{
|
||||
return shouldBypassProxy(TransportError::None, body, true);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
CHECK(shouldBypassProxy(TransportError::None, "garbage", false) == true);
|
||||
|
||||
CHECK(shouldBypassProxy(TransportError::Timeout, R"({"http_status":200})", true) == true);
|
||||
CHECK(shouldBypassProxy(TransportError::Canceled, R"({"http_status":200})", true) == true);
|
||||
|
||||
CHECK(bypassBody("<html><body>blocked</body></html>") == true);
|
||||
|
||||
CHECK(bypassBody(R"({"http_status":408})") == false);
|
||||
CHECK(bypassBody(R"({"http_status":409})") == false);
|
||||
CHECK(bypassBody(R"({"http_status":402})") == false);
|
||||
|
||||
CHECK(bypassBody(R"({"http_status":404,"message":"whatever"})") == true);
|
||||
CHECK(bypassBody(R"({"http_status":404,"message":"No active configuration found for x"})") == false);
|
||||
CHECK(bypassBody(R"({"http_status":404,"detail":"Account not found."})") == false);
|
||||
CHECK(bypassBody(R"({"http_status":404,"message":"Session not found"})") == false);
|
||||
|
||||
CHECK(bypassBody(R"({"http_status":501})") == true);
|
||||
CHECK(bypassBody(R"({"http_status":501,"message":"client version update is required"})") == false);
|
||||
|
||||
CHECK(bypassBody(R"({"http_status":422,"message":"Failed to retrieve subscription information. Is it activated?"})") == false);
|
||||
CHECK(bypassBody(R"({"http_status":422,"message":"other"})") == true);
|
||||
|
||||
CHECK(bypassBody(R"({"http_status":200})") == false);
|
||||
|
||||
CHECK(bypassBody("plain ok") == false);
|
||||
|
||||
CHECK(shouldBypassProxy(TransportError::ConnectionError, R"({"http_status":200})", true) == true);
|
||||
|
||||
return AGW_TEST_MAIN_RETURN();
|
||||
}
|
||||
123
agw-sdk/tests/unit/test_crypto.cpp
Normal file
123
agw-sdk/tests/unit/test_crypto.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#include "agw_test.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "crypto/aes.h"
|
||||
#include "crypto/hash.h"
|
||||
#include "crypto/rng.h"
|
||||
#include "crypto/rsa.h"
|
||||
#include "util/base64.h"
|
||||
#include "util/uuid.h"
|
||||
|
||||
using namespace agw;
|
||||
|
||||
namespace {
|
||||
std::vector<std::uint8_t> bytesOf(const std::string &s)
|
||||
{
|
||||
return std::vector<std::uint8_t>(s.begin(), s.end());
|
||||
}
|
||||
|
||||
std::string readFile(const std::string &path)
|
||||
{
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
std::ostringstream ss;
|
||||
ss << f.rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
class FixedRng : public crypto::IRng {
|
||||
public:
|
||||
explicit FixedRng(std::vector<std::uint8_t> data) : m_data(std::move(data)) {}
|
||||
std::vector<std::uint8_t> bytes(std::size_t n) override
|
||||
{
|
||||
std::vector<std::uint8_t> out(n);
|
||||
for (std::size_t i = 0; i < n; ++i) {
|
||||
out[i] = m_data[(m_pos + i) % m_data.size()];
|
||||
}
|
||||
m_pos += n;
|
||||
return out;
|
||||
}
|
||||
private:
|
||||
std::vector<std::uint8_t> m_data;
|
||||
std::size_t m_pos = 0;
|
||||
};
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
CHECK_EQ(util::base64Encode(std::string("")), std::string(""));
|
||||
CHECK_EQ(util::base64Encode(std::string("f")), std::string("Zg=="));
|
||||
CHECK_EQ(util::base64Encode(std::string("fo")), std::string("Zm8="));
|
||||
CHECK_EQ(util::base64Encode(std::string("foo")), std::string("Zm9v"));
|
||||
CHECK_EQ(util::base64Encode(std::string("foob")), std::string("Zm9vYg=="));
|
||||
CHECK_EQ(util::base64Encode(std::string("fooba")), std::string("Zm9vYmE="));
|
||||
CHECK_EQ(util::base64Encode(std::string("foobar")), std::string("Zm9vYmFy"));
|
||||
|
||||
{
|
||||
std::vector<std::uint8_t> v{0xfb, 0xff, 0xbf};
|
||||
CHECK_EQ(util::base64UrlEncodeNoPad(v), std::string("-_-_"));
|
||||
CHECK_EQ(util::base64Encode(v), std::string("+/+/"));
|
||||
}
|
||||
|
||||
{
|
||||
auto v = bytesOf("any carnal pleasure.");
|
||||
CHECK(util::base64Decode(util::base64Encode(v)) == v);
|
||||
CHECK(util::base64Decode(util::base64UrlEncodeNoPad(v)) == v);
|
||||
}
|
||||
|
||||
CHECK_EQ(crypto::toHex(crypto::sha512(bytesOf("abc"))),
|
||||
std::string("ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a"
|
||||
"2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"));
|
||||
|
||||
{
|
||||
std::vector<std::uint8_t> v{0x00, 0x01, 0xab, 0xff};
|
||||
CHECK_EQ(crypto::toHex(v), std::string("0001abff"));
|
||||
CHECK(crypto::fromHex("0001abff") == v);
|
||||
}
|
||||
|
||||
{
|
||||
auto key = crypto::fromHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
|
||||
auto iv = crypto::fromHex("101112131415161718191a1b1c1d1e1f");
|
||||
auto pt = bytesOf("{\"hello\":\"world\"}");
|
||||
auto ct = crypto::aesEncryptCbc(pt, key, iv);
|
||||
CHECK_EQ(util::base64Encode(ct), std::string("2WHnAcP2N+l+jz7fbKyO46jjYUq7h98lxlTIT6K0Xg8="));
|
||||
|
||||
CHECK(crypto::aesDecryptCbc(ct, key, iv) == pt);
|
||||
}
|
||||
|
||||
{
|
||||
auto key = crypto::fromHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
|
||||
auto iv16 = crypto::fromHex("101112131415161718191a1b1c1d1e1f");
|
||||
auto iv32 = crypto::fromHex("101112131415161718191a1b1c1d1e1fdeadbeefdeadbeefdeadbeefdeadbeef");
|
||||
auto pt = bytesOf("{\"hello\":\"world\"}");
|
||||
CHECK(crypto::aesEncryptCbc(pt, key, iv16) == crypto::aesEncryptCbc(pt, key, iv32));
|
||||
}
|
||||
|
||||
{
|
||||
std::string pub = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_pub.pem");
|
||||
std::string priv = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_priv.pem");
|
||||
CHECK(!pub.empty());
|
||||
CHECK(!priv.empty());
|
||||
auto msg = bytesOf("{\"aes_key\":\"...\",\"aes_iv\":\"...\",\"aes_salt\":\"...\"}");
|
||||
auto ct = crypto::rsaEncryptPublicPkcs1(msg, pub);
|
||||
auto rt = crypto::rsaDecryptPrivatePkcs1(ct, priv);
|
||||
CHECK(rt == msg);
|
||||
|
||||
auto ct2 = crypto::rsaEncryptPublicPkcs1(msg, pub);
|
||||
CHECK(ct != ct2);
|
||||
}
|
||||
|
||||
{
|
||||
FixedRng rng(std::vector<std::uint8_t>(16, 0xFF));
|
||||
std::string u = util::makeUuidV4(rng);
|
||||
CHECK_EQ(u, std::string("ffffffff-ffff-4fff-bfff-ffffffffffff"));
|
||||
CHECK(u.size() == 36);
|
||||
CHECK(u[14] == '4');
|
||||
CHECK(u[19] == '8' || u[19] == '9' || u[19] == 'a' || u[19] == 'b');
|
||||
}
|
||||
|
||||
return AGW_TEST_MAIN_RETURN();
|
||||
}
|
||||
54
agw-sdk/tests/unit/test_error_mapping.cpp
Normal file
54
agw-sdk/tests/unit/test_error_mapping.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "agw_test.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "protocol/error_mapping.h"
|
||||
|
||||
using namespace agw;
|
||||
using agw::protocol::mapResponseError;
|
||||
|
||||
namespace {
|
||||
int code(ErrorCode e) { return static_cast<int>(e); }
|
||||
|
||||
ErrorCode mapBody(const std::string &body)
|
||||
{
|
||||
return mapResponseError(false, TransportError::None, body);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
CHECK(mapResponseError(true, TransportError::None, "") == ErrorCode::ApiConfigSslError);
|
||||
CHECK(mapResponseError(false, TransportError::Timeout, "") == ErrorCode::ApiConfigTimeoutError);
|
||||
CHECK(mapResponseError(false, TransportError::Canceled, "") == ErrorCode::ApiConfigTimeoutError);
|
||||
CHECK(mapResponseError(false, TransportError::OperationNotImplemented, "") == ErrorCode::ApiUpdateRequestError);
|
||||
CHECK(mapResponseError(false, TransportError::ConnectionError, "") == ErrorCode::ApiConfigDownloadError);
|
||||
CHECK(mapResponseError(false, TransportError::None, "") == ErrorCode::NoError);
|
||||
|
||||
CHECK(mapBody("not a json") == ErrorCode::NoError);
|
||||
|
||||
CHECK(mapBody(R"({"http_status":429})") == ErrorCode::ApiRateLimitError);
|
||||
CHECK(mapBody(R"({"http_status":409})") == ErrorCode::ApiConfigLimitError);
|
||||
CHECK(mapBody(R"({"http_status":409,"message":"Trial Subscription Already Used"})") == ErrorCode::ApiTrialAlreadyUsedError);
|
||||
CHECK(mapBody(R"({"http_status":404})") == ErrorCode::ApiNotFoundError);
|
||||
CHECK(mapBody(R"({"http_status":408})") == ErrorCode::ApiConfigTimeoutError);
|
||||
CHECK(mapBody(R"({"http_status":501})") == ErrorCode::ApiUpdateRequestError);
|
||||
|
||||
CHECK(mapBody(R"({"http_status":422,"message":"Failed to retrieve subscription information. Is it activated?"})")
|
||||
== ErrorCode::ApiSubscriptionExpiredError);
|
||||
CHECK(mapBody(R"({"http_status":422,"message":"something else"})") == ErrorCode::ApiConfigDownloadError);
|
||||
|
||||
CHECK(mapBody(R"({"http_status":402,"message":"refresh_captcha"})") == ErrorCode::ApiCaptchaRefreshError);
|
||||
CHECK(mapBody(R"({"http_status":402,"message":"invalid_captcha"})") == ErrorCode::ApiCaptchaInvalidError);
|
||||
CHECK(mapBody(R"({"http_status":402,"captcha_id":"x"})") == ErrorCode::ApiCaptchaRequiredError);
|
||||
CHECK(mapBody(R"({"http_status":402,"captcha_image":"x"})") == ErrorCode::ApiCaptchaRequiredError);
|
||||
CHECK(mapBody(R"({"http_status":402,"message":"rate_limit_exceeded"})") == ErrorCode::ApiCaptchaRequiredError);
|
||||
CHECK(mapBody(R"({"http_status":402,"message":"nope"})") == ErrorCode::ApiSubscriptionNotActiveError);
|
||||
|
||||
CHECK(mapBody(R"({"http_status":500})") == ErrorCode::ApiConfigDownloadError);
|
||||
|
||||
CHECK(mapBody(R"({"http_status":200})") == ErrorCode::NoError);
|
||||
|
||||
(void)code;
|
||||
return AGW_TEST_MAIN_RETURN();
|
||||
}
|
||||
61
agw-sdk/tests/unit/test_json.cpp
Normal file
61
agw-sdk/tests/unit/test_json.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "agw_test.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "util/json.h"
|
||||
|
||||
using namespace agw;
|
||||
|
||||
int main()
|
||||
{
|
||||
{
|
||||
util::Json j;
|
||||
j["aes_key"] = "KEY";
|
||||
j["aes_iv"] = "IV";
|
||||
j["aes_salt"] = "SALT";
|
||||
|
||||
const std::string expected =
|
||||
"{\n"
|
||||
" \"aes_iv\": \"IV\",\n"
|
||||
" \"aes_key\": \"KEY\",\n"
|
||||
" \"aes_salt\": \"SALT\"\n"
|
||||
"}\n";
|
||||
CHECK_EQ(util::qtIndentedDump(j), expected);
|
||||
}
|
||||
|
||||
{
|
||||
util::Json j;
|
||||
j["key_payload"] = "K";
|
||||
j["api_payload"] = "A";
|
||||
const std::string expected =
|
||||
"{\n"
|
||||
" \"api_payload\": \"A\",\n"
|
||||
" \"key_payload\": \"K\"\n"
|
||||
"}\n";
|
||||
CHECK_EQ(util::qtIndentedDump(j), expected);
|
||||
}
|
||||
|
||||
{
|
||||
util::Json j;
|
||||
j["s"] = std::string("a\"b\\c\nd\te\x01");
|
||||
const std::string expected =
|
||||
"{\n"
|
||||
" \"s\": \"a\\\"b\\\\c\\nd\\te\\u0001\"\n"
|
||||
"}\n";
|
||||
CHECK_EQ(util::qtIndentedDump(j), expected);
|
||||
}
|
||||
|
||||
{
|
||||
util::Json j;
|
||||
j["outer"]["inner"] = "v";
|
||||
const std::string expected =
|
||||
"{\n"
|
||||
" \"outer\": {\n"
|
||||
" \"inner\": \"v\"\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
CHECK_EQ(util::qtIndentedDump(j), expected);
|
||||
}
|
||||
|
||||
return AGW_TEST_MAIN_RETURN();
|
||||
}
|
||||
87
agw-sdk/tests/unit/test_proxy_list.cpp
Normal file
87
agw-sdk/tests/unit/test_proxy_list.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "agw_test.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "crypto/aes.h"
|
||||
#include "crypto/hash.h"
|
||||
#include "failover/proxy_list.h"
|
||||
#include "util/base64.h"
|
||||
|
||||
using namespace agw;
|
||||
|
||||
namespace {
|
||||
std::vector<std::uint8_t> bytesOf(const std::string &s)
|
||||
{
|
||||
return std::vector<std::uint8_t>(s.begin(), s.end());
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
{
|
||||
const std::vector<std::string> primary{"https://a/", "https://b/"};
|
||||
const std::vector<std::string> fallback{"https://f/"};
|
||||
const FailoverContext ctx{"prem", "US"};
|
||||
|
||||
const std::string enc =
|
||||
util::base64UrlEncodeNoPad(bytesOf("endpoints-prem-US"));
|
||||
|
||||
const auto urls = failover::buildStorageUrls(primary, fallback, ctx);
|
||||
const std::vector<std::string> expected{
|
||||
"https://a/" + enc + ".json",
|
||||
"https://b/" + enc + ".json",
|
||||
"https://a/endpoints.json",
|
||||
"https://b/endpoints.json",
|
||||
"https://f/" + enc + ".json",
|
||||
"https://f/endpoints.json",
|
||||
};
|
||||
CHECK(urls == expected);
|
||||
}
|
||||
|
||||
{
|
||||
const std::vector<std::string> primary{"https://a/", "https://b/"};
|
||||
const std::vector<std::string> fallback{"https://f/"};
|
||||
const FailoverContext ctx{"", ""};
|
||||
const auto urls = failover::buildStorageUrls(primary, fallback, ctx);
|
||||
const std::vector<std::string> expected{
|
||||
"https://a/endpoints.json",
|
||||
"https://b/endpoints.json",
|
||||
"https://f/endpoints.json",
|
||||
};
|
||||
CHECK(urls == expected);
|
||||
}
|
||||
|
||||
{
|
||||
const auto list = failover::decodeProxyList(R"(["https://p1/","https://p2/"])", true, "");
|
||||
const std::vector<std::string> expected{"https://p1/", "https://p2/"};
|
||||
CHECK(list == expected);
|
||||
|
||||
CHECK(failover::decodeProxyList(R"({"x":1})", true, "").empty());
|
||||
}
|
||||
|
||||
{
|
||||
const std::string pub = "PUBKEYDATA-pem-like";
|
||||
const std::string h = crypto::toHex(crypto::sha512(bytesOf(pub)));
|
||||
const auto key = crypto::fromHex(h.substr(0, 64));
|
||||
const auto iv = crypto::fromHex(h.substr(64, 32));
|
||||
|
||||
const std::string arr = R"(["https://prod1/","https://prod2/"])";
|
||||
const auto cipher = crypto::aesEncryptCbc(bytesOf(arr), key, iv);
|
||||
const std::string b64 = util::base64Encode(cipher);
|
||||
|
||||
const auto list = failover::decodeProxyList(b64, false, pub);
|
||||
const std::vector<std::string> expected{"https://prod1/", "https://prod2/"};
|
||||
CHECK(list == expected);
|
||||
|
||||
bool threw = false;
|
||||
try {
|
||||
failover::decodeProxyList("###not base64 cipher###", false, pub);
|
||||
} catch (...) {
|
||||
threw = true;
|
||||
}
|
||||
CHECK(threw);
|
||||
}
|
||||
|
||||
return AGW_TEST_MAIN_RETURN();
|
||||
}
|
||||
33
agw-sdk/tests/unit/test_thread_pool.cpp
Normal file
33
agw-sdk/tests/unit/test_thread_pool.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "agw_test.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#include "util/thread_pool.h"
|
||||
|
||||
using namespace agw;
|
||||
|
||||
int main()
|
||||
{
|
||||
{
|
||||
std::atomic<int> counter{0};
|
||||
{
|
||||
util::ThreadPool pool(4);
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
pool.submit([&counter] { counter.fetch_add(1, std::memory_order_relaxed); });
|
||||
}
|
||||
}
|
||||
CHECK(counter.load() == 1000);
|
||||
}
|
||||
|
||||
{
|
||||
std::atomic<bool> ran{false};
|
||||
{
|
||||
util::ThreadPool pool(0);
|
||||
pool.submit([&ran] { ran.store(true); });
|
||||
}
|
||||
CHECK(ran.load());
|
||||
}
|
||||
|
||||
return AGW_TEST_MAIN_RETURN();
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is only used by HP C/C++ on VMS, and is included automatically
|
||||
* after each header file from this directory
|
||||
*/
|
||||
|
||||
/*
|
||||
* The C++ compiler doesn't understand these pragmas, even though it
|
||||
* understands the corresponding command line qualifier.
|
||||
*/
|
||||
#ifndef __cplusplus
|
||||
/* restore state. Must correspond to the save in __decc_include_prologue.h */
|
||||
# pragma names restore
|
||||
#endif
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is only used by HP C/C++ on VMS, and is included automatically
|
||||
* after each header file from this directory
|
||||
*/
|
||||
|
||||
/*
|
||||
* The C++ compiler doesn't understand these pragmas, even though it
|
||||
* understands the corresponding command line qualifier.
|
||||
*/
|
||||
#ifndef __cplusplus
|
||||
/* save state */
|
||||
# pragma names save
|
||||
/* have the compiler shorten symbols larger than 31 chars to 23 chars
|
||||
* followed by a 8 hex char CRC
|
||||
*/
|
||||
# pragma names as_is,shortened
|
||||
#endif
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2016 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#ifndef HEADER_AES_H
|
||||
# define HEADER_AES_H
|
||||
|
||||
# include <openssl/opensslconf.h>
|
||||
|
||||
# include <stddef.h>
|
||||
# ifdef __cplusplus
|
||||
extern "C" {
|
||||
# endif
|
||||
|
||||
# define AES_ENCRYPT 1
|
||||
# define AES_DECRYPT 0
|
||||
|
||||
/*
|
||||
* Because array size can't be a const in C, the following two are macros.
|
||||
* Both sizes are in bytes.
|
||||
*/
|
||||
# define AES_MAXNR 14
|
||||
# define AES_BLOCK_SIZE 16
|
||||
|
||||
/* This should be a hidden type, but EVP requires that the size be known */
|
||||
struct aes_key_st {
|
||||
# ifdef AES_LONG
|
||||
unsigned long rd_key[4 * (AES_MAXNR + 1)];
|
||||
# else
|
||||
unsigned int rd_key[4 * (AES_MAXNR + 1)];
|
||||
# endif
|
||||
int rounds;
|
||||
};
|
||||
typedef struct aes_key_st AES_KEY;
|
||||
|
||||
const char *AES_options(void);
|
||||
|
||||
int AES_set_encrypt_key(const unsigned char *userKey, const int bits,
|
||||
AES_KEY *key);
|
||||
int AES_set_decrypt_key(const unsigned char *userKey, const int bits,
|
||||
AES_KEY *key);
|
||||
|
||||
void AES_encrypt(const unsigned char *in, unsigned char *out,
|
||||
const AES_KEY *key);
|
||||
void AES_decrypt(const unsigned char *in, unsigned char *out,
|
||||
const AES_KEY *key);
|
||||
|
||||
void AES_ecb_encrypt(const unsigned char *in, unsigned char *out,
|
||||
const AES_KEY *key, const int enc);
|
||||
void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
unsigned char *ivec, const int enc);
|
||||
void AES_cfb128_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
unsigned char *ivec, int *num, const int enc);
|
||||
void AES_cfb1_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
unsigned char *ivec, int *num, const int enc);
|
||||
void AES_cfb8_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
unsigned char *ivec, int *num, const int enc);
|
||||
void AES_ofb128_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
unsigned char *ivec, int *num);
|
||||
/* NB: the IV is _two_ blocks long */
|
||||
void AES_ige_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
unsigned char *ivec, const int enc);
|
||||
/* NB: the IV is _four_ blocks long */
|
||||
void AES_bi_ige_encrypt(const unsigned char *in, unsigned char *out,
|
||||
size_t length, const AES_KEY *key,
|
||||
const AES_KEY *key2, const unsigned char *ivec,
|
||||
const int enc);
|
||||
|
||||
int AES_wrap_key(AES_KEY *key, const unsigned char *iv,
|
||||
unsigned char *out,
|
||||
const unsigned char *in, unsigned int inlen);
|
||||
int AES_unwrap_key(AES_KEY *key, const unsigned char *iv,
|
||||
unsigned char *out,
|
||||
const unsigned char *in, unsigned int inlen);
|
||||
|
||||
|
||||
# ifdef __cplusplus
|
||||
}
|
||||
# endif
|
||||
|
||||
#endif
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-2016 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#define APPLINK_STDIN 1
|
||||
#define APPLINK_STDOUT 2
|
||||
#define APPLINK_STDERR 3
|
||||
#define APPLINK_FPRINTF 4
|
||||
#define APPLINK_FGETS 5
|
||||
#define APPLINK_FREAD 6
|
||||
#define APPLINK_FWRITE 7
|
||||
#define APPLINK_FSETMOD 8
|
||||
#define APPLINK_FEOF 9
|
||||
#define APPLINK_FCLOSE 10 /* should not be used */
|
||||
|
||||
#define APPLINK_FOPEN 11 /* solely for completeness */
|
||||
#define APPLINK_FSEEK 12
|
||||
#define APPLINK_FTELL 13
|
||||
#define APPLINK_FFLUSH 14
|
||||
#define APPLINK_FERROR 15
|
||||
#define APPLINK_CLEARERR 16
|
||||
#define APPLINK_FILENO 17 /* to be used with below */
|
||||
|
||||
#define APPLINK_OPEN 18 /* formally can't be used, as flags can vary */
|
||||
#define APPLINK_READ 19
|
||||
#define APPLINK_WRITE 20
|
||||
#define APPLINK_LSEEK 21
|
||||
#define APPLINK_CLOSE 22
|
||||
#define APPLINK_MAX 22 /* always same as last macro */
|
||||
|
||||
#ifndef APPMACROS_ONLY
|
||||
# include <stdio.h>
|
||||
# include <io.h>
|
||||
# include <fcntl.h>
|
||||
|
||||
static void *app_stdin(void)
|
||||
{
|
||||
return stdin;
|
||||
}
|
||||
|
||||
static void *app_stdout(void)
|
||||
{
|
||||
return stdout;
|
||||
}
|
||||
|
||||
static void *app_stderr(void)
|
||||
{
|
||||
return stderr;
|
||||
}
|
||||
|
||||
static int app_feof(FILE *fp)
|
||||
{
|
||||
return feof(fp);
|
||||
}
|
||||
|
||||
static int app_ferror(FILE *fp)
|
||||
{
|
||||
return ferror(fp);
|
||||
}
|
||||
|
||||
static void app_clearerr(FILE *fp)
|
||||
{
|
||||
clearerr(fp);
|
||||
}
|
||||
|
||||
static int app_fileno(FILE *fp)
|
||||
{
|
||||
return _fileno(fp);
|
||||
}
|
||||
|
||||
static int app_fsetmod(FILE *fp, char mod)
|
||||
{
|
||||
return _setmode(_fileno(fp), mod == 'b' ? _O_BINARY : _O_TEXT);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
__declspec(dllexport)
|
||||
void **
|
||||
# if defined(__BORLANDC__)
|
||||
/*
|
||||
* __stdcall appears to be the only way to get the name
|
||||
* decoration right with Borland C. Otherwise it works
|
||||
* purely incidentally, as we pass no parameters.
|
||||
*/
|
||||
__stdcall
|
||||
# else
|
||||
__cdecl
|
||||
# endif
|
||||
OPENSSL_Applink(void)
|
||||
{
|
||||
static int once = 1;
|
||||
static void *OPENSSL_ApplinkTable[APPLINK_MAX + 1] =
|
||||
{ (void *)APPLINK_MAX };
|
||||
|
||||
if (once) {
|
||||
OPENSSL_ApplinkTable[APPLINK_STDIN] = app_stdin;
|
||||
OPENSSL_ApplinkTable[APPLINK_STDOUT] = app_stdout;
|
||||
OPENSSL_ApplinkTable[APPLINK_STDERR] = app_stderr;
|
||||
OPENSSL_ApplinkTable[APPLINK_FPRINTF] = fprintf;
|
||||
OPENSSL_ApplinkTable[APPLINK_FGETS] = fgets;
|
||||
OPENSSL_ApplinkTable[APPLINK_FREAD] = fread;
|
||||
OPENSSL_ApplinkTable[APPLINK_FWRITE] = fwrite;
|
||||
OPENSSL_ApplinkTable[APPLINK_FSETMOD] = app_fsetmod;
|
||||
OPENSSL_ApplinkTable[APPLINK_FEOF] = app_feof;
|
||||
OPENSSL_ApplinkTable[APPLINK_FCLOSE] = fclose;
|
||||
|
||||
OPENSSL_ApplinkTable[APPLINK_FOPEN] = fopen;
|
||||
OPENSSL_ApplinkTable[APPLINK_FSEEK] = fseek;
|
||||
OPENSSL_ApplinkTable[APPLINK_FTELL] = ftell;
|
||||
OPENSSL_ApplinkTable[APPLINK_FFLUSH] = fflush;
|
||||
OPENSSL_ApplinkTable[APPLINK_FERROR] = app_ferror;
|
||||
OPENSSL_ApplinkTable[APPLINK_CLEARERR] = app_clearerr;
|
||||
OPENSSL_ApplinkTable[APPLINK_FILENO] = app_fileno;
|
||||
|
||||
OPENSSL_ApplinkTable[APPLINK_OPEN] = _open;
|
||||
OPENSSL_ApplinkTable[APPLINK_READ] = _read;
|
||||
OPENSSL_ApplinkTable[APPLINK_WRITE] = _write;
|
||||
OPENSSL_ApplinkTable[APPLINK_LSEEK] = _lseek;
|
||||
OPENSSL_ApplinkTable[APPLINK_CLOSE] = _close;
|
||||
|
||||
once = 0;
|
||||
}
|
||||
|
||||
return OPENSSL_ApplinkTable;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,886 +0,0 @@
|
||||
/*
|
||||
* Copyright 1995-2017 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#ifndef HEADER_ASN1_H
|
||||
# define HEADER_ASN1_H
|
||||
|
||||
# include <time.h>
|
||||
# include <openssl/e_os2.h>
|
||||
# include <openssl/opensslconf.h>
|
||||
# include <openssl/bio.h>
|
||||
# include <openssl/safestack.h>
|
||||
# include <openssl/asn1err.h>
|
||||
# include <openssl/symhacks.h>
|
||||
|
||||
# include <openssl/ossl_typ.h>
|
||||
# if OPENSSL_API_COMPAT < 0x10100000L
|
||||
# include <openssl/bn.h>
|
||||
# endif
|
||||
|
||||
# ifdef OPENSSL_BUILD_SHLIBCRYPTO
|
||||
# undef OPENSSL_EXTERN
|
||||
# define OPENSSL_EXTERN OPENSSL_EXPORT
|
||||
# endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
# define V_ASN1_UNIVERSAL 0x00
|
||||
# define V_ASN1_APPLICATION 0x40
|
||||
# define V_ASN1_CONTEXT_SPECIFIC 0x80
|
||||
# define V_ASN1_PRIVATE 0xc0
|
||||
|
||||
# define V_ASN1_CONSTRUCTED 0x20
|
||||
# define V_ASN1_PRIMITIVE_TAG 0x1f
|
||||
# define V_ASN1_PRIMATIVE_TAG /*compat*/ V_ASN1_PRIMITIVE_TAG
|
||||
|
||||
# define V_ASN1_APP_CHOOSE -2/* let the recipient choose */
|
||||
# define V_ASN1_OTHER -3/* used in ASN1_TYPE */
|
||||
# define V_ASN1_ANY -4/* used in ASN1 template code */
|
||||
|
||||
# define V_ASN1_UNDEF -1
|
||||
/* ASN.1 tag values */
|
||||
# define V_ASN1_EOC 0
|
||||
# define V_ASN1_BOOLEAN 1 /**/
|
||||
# define V_ASN1_INTEGER 2
|
||||
# define V_ASN1_BIT_STRING 3
|
||||
# define V_ASN1_OCTET_STRING 4
|
||||
# define V_ASN1_NULL 5
|
||||
# define V_ASN1_OBJECT 6
|
||||
# define V_ASN1_OBJECT_DESCRIPTOR 7
|
||||
# define V_ASN1_EXTERNAL 8
|
||||
# define V_ASN1_REAL 9
|
||||
# define V_ASN1_ENUMERATED 10
|
||||
# define V_ASN1_UTF8STRING 12
|
||||
# define V_ASN1_SEQUENCE 16
|
||||
# define V_ASN1_SET 17
|
||||
# define V_ASN1_NUMERICSTRING 18 /**/
|
||||
# define V_ASN1_PRINTABLESTRING 19
|
||||
# define V_ASN1_T61STRING 20
|
||||
# define V_ASN1_TELETEXSTRING 20/* alias */
|
||||
# define V_ASN1_VIDEOTEXSTRING 21 /**/
|
||||
# define V_ASN1_IA5STRING 22
|
||||
# define V_ASN1_UTCTIME 23
|
||||
# define V_ASN1_GENERALIZEDTIME 24 /**/
|
||||
# define V_ASN1_GRAPHICSTRING 25 /**/
|
||||
# define V_ASN1_ISO64STRING 26 /**/
|
||||
# define V_ASN1_VISIBLESTRING 26/* alias */
|
||||
# define V_ASN1_GENERALSTRING 27 /**/
|
||||
# define V_ASN1_UNIVERSALSTRING 28 /**/
|
||||
# define V_ASN1_BMPSTRING 30
|
||||
|
||||
/*
|
||||
* NB the constants below are used internally by ASN1_INTEGER
|
||||
* and ASN1_ENUMERATED to indicate the sign. They are *not* on
|
||||
* the wire tag values.
|
||||
*/
|
||||
|
||||
# define V_ASN1_NEG 0x100
|
||||
# define V_ASN1_NEG_INTEGER (2 | V_ASN1_NEG)
|
||||
# define V_ASN1_NEG_ENUMERATED (10 | V_ASN1_NEG)
|
||||
|
||||
/* For use with d2i_ASN1_type_bytes() */
|
||||
# define B_ASN1_NUMERICSTRING 0x0001
|
||||
# define B_ASN1_PRINTABLESTRING 0x0002
|
||||
# define B_ASN1_T61STRING 0x0004
|
||||
# define B_ASN1_TELETEXSTRING 0x0004
|
||||
# define B_ASN1_VIDEOTEXSTRING 0x0008
|
||||
# define B_ASN1_IA5STRING 0x0010
|
||||
# define B_ASN1_GRAPHICSTRING 0x0020
|
||||
# define B_ASN1_ISO64STRING 0x0040
|
||||
# define B_ASN1_VISIBLESTRING 0x0040
|
||||
# define B_ASN1_GENERALSTRING 0x0080
|
||||
# define B_ASN1_UNIVERSALSTRING 0x0100
|
||||
# define B_ASN1_OCTET_STRING 0x0200
|
||||
# define B_ASN1_BIT_STRING 0x0400
|
||||
# define B_ASN1_BMPSTRING 0x0800
|
||||
# define B_ASN1_UNKNOWN 0x1000
|
||||
# define B_ASN1_UTF8STRING 0x2000
|
||||
# define B_ASN1_UTCTIME 0x4000
|
||||
# define B_ASN1_GENERALIZEDTIME 0x8000
|
||||
# define B_ASN1_SEQUENCE 0x10000
|
||||
/* For use with ASN1_mbstring_copy() */
|
||||
# define MBSTRING_FLAG 0x1000
|
||||
# define MBSTRING_UTF8 (MBSTRING_FLAG)
|
||||
# define MBSTRING_ASC (MBSTRING_FLAG|1)
|
||||
# define MBSTRING_BMP (MBSTRING_FLAG|2)
|
||||
# define MBSTRING_UNIV (MBSTRING_FLAG|4)
|
||||
# define SMIME_OLDMIME 0x400
|
||||
# define SMIME_CRLFEOL 0x800
|
||||
# define SMIME_STREAM 0x1000
|
||||
struct X509_algor_st;
|
||||
DEFINE_STACK_OF(X509_ALGOR)
|
||||
|
||||
# define ASN1_STRING_FLAG_BITS_LEFT 0x08/* Set if 0x07 has bits left value */
|
||||
/*
|
||||
* This indicates that the ASN1_STRING is not a real value but just a place
|
||||
* holder for the location where indefinite length constructed data should be
|
||||
* inserted in the memory buffer
|
||||
*/
|
||||
# define ASN1_STRING_FLAG_NDEF 0x010
|
||||
|
||||
/*
|
||||
* This flag is used by the CMS code to indicate that a string is not
|
||||
* complete and is a place holder for content when it had all been accessed.
|
||||
* The flag will be reset when content has been written to it.
|
||||
*/
|
||||
|
||||
# define ASN1_STRING_FLAG_CONT 0x020
|
||||
/*
|
||||
* This flag is used by ASN1 code to indicate an ASN1_STRING is an MSTRING
|
||||
* type.
|
||||
*/
|
||||
# define ASN1_STRING_FLAG_MSTRING 0x040
|
||||
/* String is embedded and only content should be freed */
|
||||
# define ASN1_STRING_FLAG_EMBED 0x080
|
||||
/* String should be parsed in RFC 5280's time format */
|
||||
# define ASN1_STRING_FLAG_X509_TIME 0x100
|
||||
/* This is the base type that holds just about everything :-) */
|
||||
struct asn1_string_st {
|
||||
int length;
|
||||
int type;
|
||||
unsigned char *data;
|
||||
/*
|
||||
* The value of the following field depends on the type being held. It
|
||||
* is mostly being used for BIT_STRING so if the input data has a
|
||||
* non-zero 'unused bits' value, it will be handled correctly
|
||||
*/
|
||||
long flags;
|
||||
};
|
||||
|
||||
/*
|
||||
* ASN1_ENCODING structure: this is used to save the received encoding of an
|
||||
* ASN1 type. This is useful to get round problems with invalid encodings
|
||||
* which can break signatures.
|
||||
*/
|
||||
|
||||
typedef struct ASN1_ENCODING_st {
|
||||
unsigned char *enc; /* DER encoding */
|
||||
long len; /* Length of encoding */
|
||||
int modified; /* set to 1 if 'enc' is invalid */
|
||||
} ASN1_ENCODING;
|
||||
|
||||
/* Used with ASN1 LONG type: if a long is set to this it is omitted */
|
||||
# define ASN1_LONG_UNDEF 0x7fffffffL
|
||||
|
||||
# define STABLE_FLAGS_MALLOC 0x01
|
||||
/*
|
||||
* A zero passed to ASN1_STRING_TABLE_new_add for the flags is interpreted
|
||||
* as "don't change" and STABLE_FLAGS_MALLOC is always set. By setting
|
||||
* STABLE_FLAGS_MALLOC only we can clear the existing value. Use the alias
|
||||
* STABLE_FLAGS_CLEAR to reflect this.
|
||||
*/
|
||||
# define STABLE_FLAGS_CLEAR STABLE_FLAGS_MALLOC
|
||||
# define STABLE_NO_MASK 0x02
|
||||
# define DIRSTRING_TYPE \
|
||||
(B_ASN1_PRINTABLESTRING|B_ASN1_T61STRING|B_ASN1_BMPSTRING|B_ASN1_UTF8STRING)
|
||||
# define PKCS9STRING_TYPE (DIRSTRING_TYPE|B_ASN1_IA5STRING)
|
||||
|
||||
typedef struct asn1_string_table_st {
|
||||
int nid;
|
||||
long minsize;
|
||||
long maxsize;
|
||||
unsigned long mask;
|
||||
unsigned long flags;
|
||||
} ASN1_STRING_TABLE;
|
||||
|
||||
DEFINE_STACK_OF(ASN1_STRING_TABLE)
|
||||
|
||||
/* size limits: this stuff is taken straight from RFC2459 */
|
||||
|
||||
# define ub_name 32768
|
||||
# define ub_common_name 64
|
||||
# define ub_locality_name 128
|
||||
# define ub_state_name 128
|
||||
# define ub_organization_name 64
|
||||
# define ub_organization_unit_name 64
|
||||
# define ub_title 64
|
||||
# define ub_email_address 128
|
||||
|
||||
/*
|
||||
* Declarations for template structures: for full definitions see asn1t.h
|
||||
*/
|
||||
typedef struct ASN1_TEMPLATE_st ASN1_TEMPLATE;
|
||||
typedef struct ASN1_TLC_st ASN1_TLC;
|
||||
/* This is just an opaque pointer */
|
||||
typedef struct ASN1_VALUE_st ASN1_VALUE;
|
||||
|
||||
/* Declare ASN1 functions: the implement macro in in asn1t.h */
|
||||
|
||||
# define DECLARE_ASN1_FUNCTIONS(type) DECLARE_ASN1_FUNCTIONS_name(type, type)
|
||||
|
||||
# define DECLARE_ASN1_ALLOC_FUNCTIONS(type) \
|
||||
DECLARE_ASN1_ALLOC_FUNCTIONS_name(type, type)
|
||||
|
||||
# define DECLARE_ASN1_FUNCTIONS_name(type, name) \
|
||||
DECLARE_ASN1_ALLOC_FUNCTIONS_name(type, name) \
|
||||
DECLARE_ASN1_ENCODE_FUNCTIONS(type, name, name)
|
||||
|
||||
# define DECLARE_ASN1_FUNCTIONS_fname(type, itname, name) \
|
||||
DECLARE_ASN1_ALLOC_FUNCTIONS_name(type, name) \
|
||||
DECLARE_ASN1_ENCODE_FUNCTIONS(type, itname, name)
|
||||
|
||||
# define DECLARE_ASN1_ENCODE_FUNCTIONS(type, itname, name) \
|
||||
type *d2i_##name(type **a, const unsigned char **in, long len); \
|
||||
int i2d_##name(type *a, unsigned char **out); \
|
||||
DECLARE_ASN1_ITEM(itname)
|
||||
|
||||
# define DECLARE_ASN1_ENCODE_FUNCTIONS_const(type, name) \
|
||||
type *d2i_##name(type **a, const unsigned char **in, long len); \
|
||||
int i2d_##name(const type *a, unsigned char **out); \
|
||||
DECLARE_ASN1_ITEM(name)
|
||||
|
||||
# define DECLARE_ASN1_NDEF_FUNCTION(name) \
|
||||
int i2d_##name##_NDEF(name *a, unsigned char **out);
|
||||
|
||||
# define DECLARE_ASN1_FUNCTIONS_const(name) \
|
||||
DECLARE_ASN1_ALLOC_FUNCTIONS(name) \
|
||||
DECLARE_ASN1_ENCODE_FUNCTIONS_const(name, name)
|
||||
|
||||
# define DECLARE_ASN1_ALLOC_FUNCTIONS_name(type, name) \
|
||||
type *name##_new(void); \
|
||||
void name##_free(type *a);
|
||||
|
||||
# define DECLARE_ASN1_PRINT_FUNCTION(stname) \
|
||||
DECLARE_ASN1_PRINT_FUNCTION_fname(stname, stname)
|
||||
|
||||
# define DECLARE_ASN1_PRINT_FUNCTION_fname(stname, fname) \
|
||||
int fname##_print_ctx(BIO *out, stname *x, int indent, \
|
||||
const ASN1_PCTX *pctx);
|
||||
|
||||
# define D2I_OF(type) type *(*)(type **,const unsigned char **,long)
|
||||
# define I2D_OF(type) int (*)(type *,unsigned char **)
|
||||
# define I2D_OF_const(type) int (*)(const type *,unsigned char **)
|
||||
|
||||
# define CHECKED_D2I_OF(type, d2i) \
|
||||
((d2i_of_void*) (1 ? d2i : ((D2I_OF(type))0)))
|
||||
# define CHECKED_I2D_OF(type, i2d) \
|
||||
((i2d_of_void*) (1 ? i2d : ((I2D_OF(type))0)))
|
||||
# define CHECKED_NEW_OF(type, xnew) \
|
||||
((void *(*)(void)) (1 ? xnew : ((type *(*)(void))0)))
|
||||
# define CHECKED_PTR_OF(type, p) \
|
||||
((void*) (1 ? p : (type*)0))
|
||||
# define CHECKED_PPTR_OF(type, p) \
|
||||
((void**) (1 ? p : (type**)0))
|
||||
|
||||
# define TYPEDEF_D2I_OF(type) typedef type *d2i_of_##type(type **,const unsigned char **,long)
|
||||
# define TYPEDEF_I2D_OF(type) typedef int i2d_of_##type(type *,unsigned char **)
|
||||
# define TYPEDEF_D2I2D_OF(type) TYPEDEF_D2I_OF(type); TYPEDEF_I2D_OF(type)
|
||||
|
||||
TYPEDEF_D2I2D_OF(void);
|
||||
|
||||
/*-
|
||||
* The following macros and typedefs allow an ASN1_ITEM
|
||||
* to be embedded in a structure and referenced. Since
|
||||
* the ASN1_ITEM pointers need to be globally accessible
|
||||
* (possibly from shared libraries) they may exist in
|
||||
* different forms. On platforms that support it the
|
||||
* ASN1_ITEM structure itself will be globally exported.
|
||||
* Other platforms will export a function that returns
|
||||
* an ASN1_ITEM pointer.
|
||||
*
|
||||
* To handle both cases transparently the macros below
|
||||
* should be used instead of hard coding an ASN1_ITEM
|
||||
* pointer in a structure.
|
||||
*
|
||||
* The structure will look like this:
|
||||
*
|
||||
* typedef struct SOMETHING_st {
|
||||
* ...
|
||||
* ASN1_ITEM_EXP *iptr;
|
||||
* ...
|
||||
* } SOMETHING;
|
||||
*
|
||||
* It would be initialised as e.g.:
|
||||
*
|
||||
* SOMETHING somevar = {...,ASN1_ITEM_ref(X509),...};
|
||||
*
|
||||
* and the actual pointer extracted with:
|
||||
*
|
||||
* const ASN1_ITEM *it = ASN1_ITEM_ptr(somevar.iptr);
|
||||
*
|
||||
* Finally an ASN1_ITEM pointer can be extracted from an
|
||||
* appropriate reference with: ASN1_ITEM_rptr(X509). This
|
||||
* would be used when a function takes an ASN1_ITEM * argument.
|
||||
*
|
||||
*/
|
||||
|
||||
# ifndef OPENSSL_EXPORT_VAR_AS_FUNCTION
|
||||
|
||||
/* ASN1_ITEM pointer exported type */
|
||||
typedef const ASN1_ITEM ASN1_ITEM_EXP;
|
||||
|
||||
/* Macro to obtain ASN1_ITEM pointer from exported type */
|
||||
# define ASN1_ITEM_ptr(iptr) (iptr)
|
||||
|
||||
/* Macro to include ASN1_ITEM pointer from base type */
|
||||
# define ASN1_ITEM_ref(iptr) (&(iptr##_it))
|
||||
|
||||
# define ASN1_ITEM_rptr(ref) (&(ref##_it))
|
||||
|
||||
# define DECLARE_ASN1_ITEM(name) \
|
||||
OPENSSL_EXTERN const ASN1_ITEM name##_it;
|
||||
|
||||
# else
|
||||
|
||||
/*
|
||||
* Platforms that can't easily handle shared global variables are declared as
|
||||
* functions returning ASN1_ITEM pointers.
|
||||
*/
|
||||
|
||||
/* ASN1_ITEM pointer exported type */
|
||||
typedef const ASN1_ITEM *ASN1_ITEM_EXP (void);
|
||||
|
||||
/* Macro to obtain ASN1_ITEM pointer from exported type */
|
||||
# define ASN1_ITEM_ptr(iptr) (iptr())
|
||||
|
||||
/* Macro to include ASN1_ITEM pointer from base type */
|
||||
# define ASN1_ITEM_ref(iptr) (iptr##_it)
|
||||
|
||||
# define ASN1_ITEM_rptr(ref) (ref##_it())
|
||||
|
||||
# define DECLARE_ASN1_ITEM(name) \
|
||||
const ASN1_ITEM * name##_it(void);
|
||||
|
||||
# endif
|
||||
|
||||
/* Parameters used by ASN1_STRING_print_ex() */
|
||||
|
||||
/*
|
||||
* These determine which characters to escape: RFC2253 special characters,
|
||||
* control characters and MSB set characters
|
||||
*/
|
||||
|
||||
# define ASN1_STRFLGS_ESC_2253 1
|
||||
# define ASN1_STRFLGS_ESC_CTRL 2
|
||||
# define ASN1_STRFLGS_ESC_MSB 4
|
||||
|
||||
/*
|
||||
* This flag determines how we do escaping: normally RC2253 backslash only,
|
||||
* set this to use backslash and quote.
|
||||
*/
|
||||
|
||||
# define ASN1_STRFLGS_ESC_QUOTE 8
|
||||
|
||||
/* These three flags are internal use only. */
|
||||
|
||||
/* Character is a valid PrintableString character */
|
||||
# define CHARTYPE_PRINTABLESTRING 0x10
|
||||
/* Character needs escaping if it is the first character */
|
||||
# define CHARTYPE_FIRST_ESC_2253 0x20
|
||||
/* Character needs escaping if it is the last character */
|
||||
# define CHARTYPE_LAST_ESC_2253 0x40
|
||||
|
||||
/*
|
||||
* NB the internal flags are safely reused below by flags handled at the top
|
||||
* level.
|
||||
*/
|
||||
|
||||
/*
|
||||
* If this is set we convert all character strings to UTF8 first
|
||||
*/
|
||||
|
||||
# define ASN1_STRFLGS_UTF8_CONVERT 0x10
|
||||
|
||||
/*
|
||||
* If this is set we don't attempt to interpret content: just assume all
|
||||
* strings are 1 byte per character. This will produce some pretty odd
|
||||
* looking output!
|
||||
*/
|
||||
|
||||
# define ASN1_STRFLGS_IGNORE_TYPE 0x20
|
||||
|
||||
/* If this is set we include the string type in the output */
|
||||
# define ASN1_STRFLGS_SHOW_TYPE 0x40
|
||||
|
||||
/*
|
||||
* This determines which strings to display and which to 'dump' (hex dump of
|
||||
* content octets or DER encoding). We can only dump non character strings or
|
||||
* everything. If we don't dump 'unknown' they are interpreted as character
|
||||
* strings with 1 octet per character and are subject to the usual escaping
|
||||
* options.
|
||||
*/
|
||||
|
||||
# define ASN1_STRFLGS_DUMP_ALL 0x80
|
||||
# define ASN1_STRFLGS_DUMP_UNKNOWN 0x100
|
||||
|
||||
/*
|
||||
* These determine what 'dumping' does, we can dump the content octets or the
|
||||
* DER encoding: both use the RFC2253 #XXXXX notation.
|
||||
*/
|
||||
|
||||
# define ASN1_STRFLGS_DUMP_DER 0x200
|
||||
|
||||
/*
|
||||
* This flag specifies that RC2254 escaping shall be performed.
|
||||
*/
|
||||
#define ASN1_STRFLGS_ESC_2254 0x400
|
||||
|
||||
/*
|
||||
* All the string flags consistent with RFC2253, escaping control characters
|
||||
* isn't essential in RFC2253 but it is advisable anyway.
|
||||
*/
|
||||
|
||||
# define ASN1_STRFLGS_RFC2253 (ASN1_STRFLGS_ESC_2253 | \
|
||||
ASN1_STRFLGS_ESC_CTRL | \
|
||||
ASN1_STRFLGS_ESC_MSB | \
|
||||
ASN1_STRFLGS_UTF8_CONVERT | \
|
||||
ASN1_STRFLGS_DUMP_UNKNOWN | \
|
||||
ASN1_STRFLGS_DUMP_DER)
|
||||
|
||||
DEFINE_STACK_OF(ASN1_INTEGER)
|
||||
|
||||
DEFINE_STACK_OF(ASN1_GENERALSTRING)
|
||||
|
||||
DEFINE_STACK_OF(ASN1_UTF8STRING)
|
||||
|
||||
typedef struct asn1_type_st {
|
||||
int type;
|
||||
union {
|
||||
char *ptr;
|
||||
ASN1_BOOLEAN boolean;
|
||||
ASN1_STRING *asn1_string;
|
||||
ASN1_OBJECT *object;
|
||||
ASN1_INTEGER *integer;
|
||||
ASN1_ENUMERATED *enumerated;
|
||||
ASN1_BIT_STRING *bit_string;
|
||||
ASN1_OCTET_STRING *octet_string;
|
||||
ASN1_PRINTABLESTRING *printablestring;
|
||||
ASN1_T61STRING *t61string;
|
||||
ASN1_IA5STRING *ia5string;
|
||||
ASN1_GENERALSTRING *generalstring;
|
||||
ASN1_BMPSTRING *bmpstring;
|
||||
ASN1_UNIVERSALSTRING *universalstring;
|
||||
ASN1_UTCTIME *utctime;
|
||||
ASN1_GENERALIZEDTIME *generalizedtime;
|
||||
ASN1_VISIBLESTRING *visiblestring;
|
||||
ASN1_UTF8STRING *utf8string;
|
||||
/*
|
||||
* set and sequence are left complete and still contain the set or
|
||||
* sequence bytes
|
||||
*/
|
||||
ASN1_STRING *set;
|
||||
ASN1_STRING *sequence;
|
||||
ASN1_VALUE *asn1_value;
|
||||
} value;
|
||||
} ASN1_TYPE;
|
||||
|
||||
DEFINE_STACK_OF(ASN1_TYPE)
|
||||
|
||||
typedef STACK_OF(ASN1_TYPE) ASN1_SEQUENCE_ANY;
|
||||
|
||||
DECLARE_ASN1_ENCODE_FUNCTIONS_const(ASN1_SEQUENCE_ANY, ASN1_SEQUENCE_ANY)
|
||||
DECLARE_ASN1_ENCODE_FUNCTIONS_const(ASN1_SEQUENCE_ANY, ASN1_SET_ANY)
|
||||
|
||||
/* This is used to contain a list of bit names */
|
||||
typedef struct BIT_STRING_BITNAME_st {
|
||||
int bitnum;
|
||||
const char *lname;
|
||||
const char *sname;
|
||||
} BIT_STRING_BITNAME;
|
||||
|
||||
# define B_ASN1_TIME \
|
||||
B_ASN1_UTCTIME | \
|
||||
B_ASN1_GENERALIZEDTIME
|
||||
|
||||
# define B_ASN1_PRINTABLE \
|
||||
B_ASN1_NUMERICSTRING| \
|
||||
B_ASN1_PRINTABLESTRING| \
|
||||
B_ASN1_T61STRING| \
|
||||
B_ASN1_IA5STRING| \
|
||||
B_ASN1_BIT_STRING| \
|
||||
B_ASN1_UNIVERSALSTRING|\
|
||||
B_ASN1_BMPSTRING|\
|
||||
B_ASN1_UTF8STRING|\
|
||||
B_ASN1_SEQUENCE|\
|
||||
B_ASN1_UNKNOWN
|
||||
|
||||
# define B_ASN1_DIRECTORYSTRING \
|
||||
B_ASN1_PRINTABLESTRING| \
|
||||
B_ASN1_TELETEXSTRING|\
|
||||
B_ASN1_BMPSTRING|\
|
||||
B_ASN1_UNIVERSALSTRING|\
|
||||
B_ASN1_UTF8STRING
|
||||
|
||||
# define B_ASN1_DISPLAYTEXT \
|
||||
B_ASN1_IA5STRING| \
|
||||
B_ASN1_VISIBLESTRING| \
|
||||
B_ASN1_BMPSTRING|\
|
||||
B_ASN1_UTF8STRING
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS_fname(ASN1_TYPE, ASN1_ANY, ASN1_TYPE)
|
||||
|
||||
int ASN1_TYPE_get(const ASN1_TYPE *a);
|
||||
void ASN1_TYPE_set(ASN1_TYPE *a, int type, void *value);
|
||||
int ASN1_TYPE_set1(ASN1_TYPE *a, int type, const void *value);
|
||||
int ASN1_TYPE_cmp(const ASN1_TYPE *a, const ASN1_TYPE *b);
|
||||
|
||||
ASN1_TYPE *ASN1_TYPE_pack_sequence(const ASN1_ITEM *it, void *s, ASN1_TYPE **t);
|
||||
void *ASN1_TYPE_unpack_sequence(const ASN1_ITEM *it, const ASN1_TYPE *t);
|
||||
|
||||
ASN1_OBJECT *ASN1_OBJECT_new(void);
|
||||
void ASN1_OBJECT_free(ASN1_OBJECT *a);
|
||||
int i2d_ASN1_OBJECT(const ASN1_OBJECT *a, unsigned char **pp);
|
||||
ASN1_OBJECT *d2i_ASN1_OBJECT(ASN1_OBJECT **a, const unsigned char **pp,
|
||||
long length);
|
||||
|
||||
DECLARE_ASN1_ITEM(ASN1_OBJECT)
|
||||
|
||||
DEFINE_STACK_OF(ASN1_OBJECT)
|
||||
|
||||
ASN1_STRING *ASN1_STRING_new(void);
|
||||
void ASN1_STRING_free(ASN1_STRING *a);
|
||||
void ASN1_STRING_clear_free(ASN1_STRING *a);
|
||||
int ASN1_STRING_copy(ASN1_STRING *dst, const ASN1_STRING *str);
|
||||
ASN1_STRING *ASN1_STRING_dup(const ASN1_STRING *a);
|
||||
ASN1_STRING *ASN1_STRING_type_new(int type);
|
||||
int ASN1_STRING_cmp(const ASN1_STRING *a, const ASN1_STRING *b);
|
||||
/*
|
||||
* Since this is used to store all sorts of things, via macros, for now,
|
||||
* make its data void *
|
||||
*/
|
||||
int ASN1_STRING_set(ASN1_STRING *str, const void *data, int len);
|
||||
void ASN1_STRING_set0(ASN1_STRING *str, void *data, int len);
|
||||
int ASN1_STRING_length(const ASN1_STRING *x);
|
||||
void ASN1_STRING_length_set(ASN1_STRING *x, int n);
|
||||
int ASN1_STRING_type(const ASN1_STRING *x);
|
||||
DEPRECATEDIN_1_1_0(unsigned char *ASN1_STRING_data(ASN1_STRING *x))
|
||||
const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *x);
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_BIT_STRING)
|
||||
int ASN1_BIT_STRING_set(ASN1_BIT_STRING *a, unsigned char *d, int length);
|
||||
int ASN1_BIT_STRING_set_bit(ASN1_BIT_STRING *a, int n, int value);
|
||||
int ASN1_BIT_STRING_get_bit(const ASN1_BIT_STRING *a, int n);
|
||||
int ASN1_BIT_STRING_check(const ASN1_BIT_STRING *a,
|
||||
const unsigned char *flags, int flags_len);
|
||||
|
||||
int ASN1_BIT_STRING_name_print(BIO *out, ASN1_BIT_STRING *bs,
|
||||
BIT_STRING_BITNAME *tbl, int indent);
|
||||
int ASN1_BIT_STRING_num_asc(const char *name, BIT_STRING_BITNAME *tbl);
|
||||
int ASN1_BIT_STRING_set_asc(ASN1_BIT_STRING *bs, const char *name, int value,
|
||||
BIT_STRING_BITNAME *tbl);
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_INTEGER)
|
||||
ASN1_INTEGER *d2i_ASN1_UINTEGER(ASN1_INTEGER **a, const unsigned char **pp,
|
||||
long length);
|
||||
ASN1_INTEGER *ASN1_INTEGER_dup(const ASN1_INTEGER *x);
|
||||
int ASN1_INTEGER_cmp(const ASN1_INTEGER *x, const ASN1_INTEGER *y);
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_ENUMERATED)
|
||||
|
||||
int ASN1_UTCTIME_check(const ASN1_UTCTIME *a);
|
||||
ASN1_UTCTIME *ASN1_UTCTIME_set(ASN1_UTCTIME *s, time_t t);
|
||||
ASN1_UTCTIME *ASN1_UTCTIME_adj(ASN1_UTCTIME *s, time_t t,
|
||||
int offset_day, long offset_sec);
|
||||
int ASN1_UTCTIME_set_string(ASN1_UTCTIME *s, const char *str);
|
||||
int ASN1_UTCTIME_cmp_time_t(const ASN1_UTCTIME *s, time_t t);
|
||||
|
||||
int ASN1_GENERALIZEDTIME_check(const ASN1_GENERALIZEDTIME *a);
|
||||
ASN1_GENERALIZEDTIME *ASN1_GENERALIZEDTIME_set(ASN1_GENERALIZEDTIME *s,
|
||||
time_t t);
|
||||
ASN1_GENERALIZEDTIME *ASN1_GENERALIZEDTIME_adj(ASN1_GENERALIZEDTIME *s,
|
||||
time_t t, int offset_day,
|
||||
long offset_sec);
|
||||
int ASN1_GENERALIZEDTIME_set_string(ASN1_GENERALIZEDTIME *s, const char *str);
|
||||
|
||||
int ASN1_TIME_diff(int *pday, int *psec,
|
||||
const ASN1_TIME *from, const ASN1_TIME *to);
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_OCTET_STRING)
|
||||
ASN1_OCTET_STRING *ASN1_OCTET_STRING_dup(const ASN1_OCTET_STRING *a);
|
||||
int ASN1_OCTET_STRING_cmp(const ASN1_OCTET_STRING *a,
|
||||
const ASN1_OCTET_STRING *b);
|
||||
int ASN1_OCTET_STRING_set(ASN1_OCTET_STRING *str, const unsigned char *data,
|
||||
int len);
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_VISIBLESTRING)
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_UNIVERSALSTRING)
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_UTF8STRING)
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_NULL)
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_BMPSTRING)
|
||||
|
||||
int UTF8_getc(const unsigned char *str, int len, unsigned long *val);
|
||||
int UTF8_putc(unsigned char *str, int len, unsigned long value);
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS_name(ASN1_STRING, ASN1_PRINTABLE)
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS_name(ASN1_STRING, DIRECTORYSTRING)
|
||||
DECLARE_ASN1_FUNCTIONS_name(ASN1_STRING, DISPLAYTEXT)
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_PRINTABLESTRING)
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_T61STRING)
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_IA5STRING)
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_GENERALSTRING)
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_UTCTIME)
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_GENERALIZEDTIME)
|
||||
DECLARE_ASN1_FUNCTIONS(ASN1_TIME)
|
||||
|
||||
DECLARE_ASN1_ITEM(ASN1_OCTET_STRING_NDEF)
|
||||
|
||||
ASN1_TIME *ASN1_TIME_set(ASN1_TIME *s, time_t t);
|
||||
ASN1_TIME *ASN1_TIME_adj(ASN1_TIME *s, time_t t,
|
||||
int offset_day, long offset_sec);
|
||||
int ASN1_TIME_check(const ASN1_TIME *t);
|
||||
ASN1_GENERALIZEDTIME *ASN1_TIME_to_generalizedtime(const ASN1_TIME *t,
|
||||
ASN1_GENERALIZEDTIME **out);
|
||||
int ASN1_TIME_set_string(ASN1_TIME *s, const char *str);
|
||||
int ASN1_TIME_set_string_X509(ASN1_TIME *s, const char *str);
|
||||
int ASN1_TIME_to_tm(const ASN1_TIME *s, struct tm *tm);
|
||||
int ASN1_TIME_normalize(ASN1_TIME *s);
|
||||
int ASN1_TIME_cmp_time_t(const ASN1_TIME *s, time_t t);
|
||||
int ASN1_TIME_compare(const ASN1_TIME *a, const ASN1_TIME *b);
|
||||
|
||||
int i2a_ASN1_INTEGER(BIO *bp, const ASN1_INTEGER *a);
|
||||
int a2i_ASN1_INTEGER(BIO *bp, ASN1_INTEGER *bs, char *buf, int size);
|
||||
int i2a_ASN1_ENUMERATED(BIO *bp, const ASN1_ENUMERATED *a);
|
||||
int a2i_ASN1_ENUMERATED(BIO *bp, ASN1_ENUMERATED *bs, char *buf, int size);
|
||||
int i2a_ASN1_OBJECT(BIO *bp, const ASN1_OBJECT *a);
|
||||
int a2i_ASN1_STRING(BIO *bp, ASN1_STRING *bs, char *buf, int size);
|
||||
int i2a_ASN1_STRING(BIO *bp, const ASN1_STRING *a, int type);
|
||||
int i2t_ASN1_OBJECT(char *buf, int buf_len, const ASN1_OBJECT *a);
|
||||
|
||||
int a2d_ASN1_OBJECT(unsigned char *out, int olen, const char *buf, int num);
|
||||
ASN1_OBJECT *ASN1_OBJECT_create(int nid, unsigned char *data, int len,
|
||||
const char *sn, const char *ln);
|
||||
|
||||
int ASN1_INTEGER_get_int64(int64_t *pr, const ASN1_INTEGER *a);
|
||||
int ASN1_INTEGER_set_int64(ASN1_INTEGER *a, int64_t r);
|
||||
int ASN1_INTEGER_get_uint64(uint64_t *pr, const ASN1_INTEGER *a);
|
||||
int ASN1_INTEGER_set_uint64(ASN1_INTEGER *a, uint64_t r);
|
||||
|
||||
int ASN1_INTEGER_set(ASN1_INTEGER *a, long v);
|
||||
long ASN1_INTEGER_get(const ASN1_INTEGER *a);
|
||||
ASN1_INTEGER *BN_to_ASN1_INTEGER(const BIGNUM *bn, ASN1_INTEGER *ai);
|
||||
BIGNUM *ASN1_INTEGER_to_BN(const ASN1_INTEGER *ai, BIGNUM *bn);
|
||||
|
||||
int ASN1_ENUMERATED_get_int64(int64_t *pr, const ASN1_ENUMERATED *a);
|
||||
int ASN1_ENUMERATED_set_int64(ASN1_ENUMERATED *a, int64_t r);
|
||||
|
||||
|
||||
int ASN1_ENUMERATED_set(ASN1_ENUMERATED *a, long v);
|
||||
long ASN1_ENUMERATED_get(const ASN1_ENUMERATED *a);
|
||||
ASN1_ENUMERATED *BN_to_ASN1_ENUMERATED(const BIGNUM *bn, ASN1_ENUMERATED *ai);
|
||||
BIGNUM *ASN1_ENUMERATED_to_BN(const ASN1_ENUMERATED *ai, BIGNUM *bn);
|
||||
|
||||
/* General */
|
||||
/* given a string, return the correct type, max is the maximum length */
|
||||
int ASN1_PRINTABLE_type(const unsigned char *s, int max);
|
||||
|
||||
unsigned long ASN1_tag2bit(int tag);
|
||||
|
||||
/* SPECIALS */
|
||||
int ASN1_get_object(const unsigned char **pp, long *plength, int *ptag,
|
||||
int *pclass, long omax);
|
||||
int ASN1_check_infinite_end(unsigned char **p, long len);
|
||||
int ASN1_const_check_infinite_end(const unsigned char **p, long len);
|
||||
void ASN1_put_object(unsigned char **pp, int constructed, int length,
|
||||
int tag, int xclass);
|
||||
int ASN1_put_eoc(unsigned char **pp);
|
||||
int ASN1_object_size(int constructed, int length, int tag);
|
||||
|
||||
/* Used to implement other functions */
|
||||
void *ASN1_dup(i2d_of_void *i2d, d2i_of_void *d2i, void *x);
|
||||
|
||||
# define ASN1_dup_of(type,i2d,d2i,x) \
|
||||
((type*)ASN1_dup(CHECKED_I2D_OF(type, i2d), \
|
||||
CHECKED_D2I_OF(type, d2i), \
|
||||
CHECKED_PTR_OF(type, x)))
|
||||
|
||||
# define ASN1_dup_of_const(type,i2d,d2i,x) \
|
||||
((type*)ASN1_dup(CHECKED_I2D_OF(const type, i2d), \
|
||||
CHECKED_D2I_OF(type, d2i), \
|
||||
CHECKED_PTR_OF(const type, x)))
|
||||
|
||||
void *ASN1_item_dup(const ASN1_ITEM *it, void *x);
|
||||
|
||||
/* ASN1 alloc/free macros for when a type is only used internally */
|
||||
|
||||
# define M_ASN1_new_of(type) (type *)ASN1_item_new(ASN1_ITEM_rptr(type))
|
||||
# define M_ASN1_free_of(x, type) \
|
||||
ASN1_item_free(CHECKED_PTR_OF(type, x), ASN1_ITEM_rptr(type))
|
||||
|
||||
# ifndef OPENSSL_NO_STDIO
|
||||
void *ASN1_d2i_fp(void *(*xnew) (void), d2i_of_void *d2i, FILE *in, void **x);
|
||||
|
||||
# define ASN1_d2i_fp_of(type,xnew,d2i,in,x) \
|
||||
((type*)ASN1_d2i_fp(CHECKED_NEW_OF(type, xnew), \
|
||||
CHECKED_D2I_OF(type, d2i), \
|
||||
in, \
|
||||
CHECKED_PPTR_OF(type, x)))
|
||||
|
||||
void *ASN1_item_d2i_fp(const ASN1_ITEM *it, FILE *in, void *x);
|
||||
int ASN1_i2d_fp(i2d_of_void *i2d, FILE *out, void *x);
|
||||
|
||||
# define ASN1_i2d_fp_of(type,i2d,out,x) \
|
||||
(ASN1_i2d_fp(CHECKED_I2D_OF(type, i2d), \
|
||||
out, \
|
||||
CHECKED_PTR_OF(type, x)))
|
||||
|
||||
# define ASN1_i2d_fp_of_const(type,i2d,out,x) \
|
||||
(ASN1_i2d_fp(CHECKED_I2D_OF(const type, i2d), \
|
||||
out, \
|
||||
CHECKED_PTR_OF(const type, x)))
|
||||
|
||||
int ASN1_item_i2d_fp(const ASN1_ITEM *it, FILE *out, void *x);
|
||||
int ASN1_STRING_print_ex_fp(FILE *fp, const ASN1_STRING *str, unsigned long flags);
|
||||
# endif
|
||||
|
||||
int ASN1_STRING_to_UTF8(unsigned char **out, const ASN1_STRING *in);
|
||||
|
||||
void *ASN1_d2i_bio(void *(*xnew) (void), d2i_of_void *d2i, BIO *in, void **x);
|
||||
|
||||
# define ASN1_d2i_bio_of(type,xnew,d2i,in,x) \
|
||||
((type*)ASN1_d2i_bio( CHECKED_NEW_OF(type, xnew), \
|
||||
CHECKED_D2I_OF(type, d2i), \
|
||||
in, \
|
||||
CHECKED_PPTR_OF(type, x)))
|
||||
|
||||
void *ASN1_item_d2i_bio(const ASN1_ITEM *it, BIO *in, void *x);
|
||||
int ASN1_i2d_bio(i2d_of_void *i2d, BIO *out, unsigned char *x);
|
||||
|
||||
# define ASN1_i2d_bio_of(type,i2d,out,x) \
|
||||
(ASN1_i2d_bio(CHECKED_I2D_OF(type, i2d), \
|
||||
out, \
|
||||
CHECKED_PTR_OF(type, x)))
|
||||
|
||||
# define ASN1_i2d_bio_of_const(type,i2d,out,x) \
|
||||
(ASN1_i2d_bio(CHECKED_I2D_OF(const type, i2d), \
|
||||
out, \
|
||||
CHECKED_PTR_OF(const type, x)))
|
||||
|
||||
int ASN1_item_i2d_bio(const ASN1_ITEM *it, BIO *out, void *x);
|
||||
int ASN1_UTCTIME_print(BIO *fp, const ASN1_UTCTIME *a);
|
||||
int ASN1_GENERALIZEDTIME_print(BIO *fp, const ASN1_GENERALIZEDTIME *a);
|
||||
int ASN1_TIME_print(BIO *fp, const ASN1_TIME *a);
|
||||
int ASN1_STRING_print(BIO *bp, const ASN1_STRING *v);
|
||||
int ASN1_STRING_print_ex(BIO *out, const ASN1_STRING *str, unsigned long flags);
|
||||
int ASN1_buf_print(BIO *bp, const unsigned char *buf, size_t buflen, int off);
|
||||
int ASN1_bn_print(BIO *bp, const char *number, const BIGNUM *num,
|
||||
unsigned char *buf, int off);
|
||||
int ASN1_parse(BIO *bp, const unsigned char *pp, long len, int indent);
|
||||
int ASN1_parse_dump(BIO *bp, const unsigned char *pp, long len, int indent,
|
||||
int dump);
|
||||
const char *ASN1_tag2str(int tag);
|
||||
|
||||
/* Used to load and write Netscape format cert */
|
||||
|
||||
int ASN1_UNIVERSALSTRING_to_string(ASN1_UNIVERSALSTRING *s);
|
||||
|
||||
int ASN1_TYPE_set_octetstring(ASN1_TYPE *a, unsigned char *data, int len);
|
||||
int ASN1_TYPE_get_octetstring(const ASN1_TYPE *a, unsigned char *data, int max_len);
|
||||
int ASN1_TYPE_set_int_octetstring(ASN1_TYPE *a, long num,
|
||||
unsigned char *data, int len);
|
||||
int ASN1_TYPE_get_int_octetstring(const ASN1_TYPE *a, long *num,
|
||||
unsigned char *data, int max_len);
|
||||
|
||||
void *ASN1_item_unpack(const ASN1_STRING *oct, const ASN1_ITEM *it);
|
||||
|
||||
ASN1_STRING *ASN1_item_pack(void *obj, const ASN1_ITEM *it,
|
||||
ASN1_OCTET_STRING **oct);
|
||||
|
||||
void ASN1_STRING_set_default_mask(unsigned long mask);
|
||||
int ASN1_STRING_set_default_mask_asc(const char *p);
|
||||
unsigned long ASN1_STRING_get_default_mask(void);
|
||||
int ASN1_mbstring_copy(ASN1_STRING **out, const unsigned char *in, int len,
|
||||
int inform, unsigned long mask);
|
||||
int ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in, int len,
|
||||
int inform, unsigned long mask,
|
||||
long minsize, long maxsize);
|
||||
|
||||
ASN1_STRING *ASN1_STRING_set_by_NID(ASN1_STRING **out,
|
||||
const unsigned char *in, int inlen,
|
||||
int inform, int nid);
|
||||
ASN1_STRING_TABLE *ASN1_STRING_TABLE_get(int nid);
|
||||
int ASN1_STRING_TABLE_add(int, long, long, unsigned long, unsigned long);
|
||||
void ASN1_STRING_TABLE_cleanup(void);
|
||||
|
||||
/* ASN1 template functions */
|
||||
|
||||
/* Old API compatible functions */
|
||||
ASN1_VALUE *ASN1_item_new(const ASN1_ITEM *it);
|
||||
void ASN1_item_free(ASN1_VALUE *val, const ASN1_ITEM *it);
|
||||
ASN1_VALUE *ASN1_item_d2i(ASN1_VALUE **val, const unsigned char **in,
|
||||
long len, const ASN1_ITEM *it);
|
||||
int ASN1_item_i2d(ASN1_VALUE *val, unsigned char **out, const ASN1_ITEM *it);
|
||||
int ASN1_item_ndef_i2d(ASN1_VALUE *val, unsigned char **out,
|
||||
const ASN1_ITEM *it);
|
||||
|
||||
void ASN1_add_oid_module(void);
|
||||
void ASN1_add_stable_module(void);
|
||||
|
||||
ASN1_TYPE *ASN1_generate_nconf(const char *str, CONF *nconf);
|
||||
ASN1_TYPE *ASN1_generate_v3(const char *str, X509V3_CTX *cnf);
|
||||
int ASN1_str2mask(const char *str, unsigned long *pmask);
|
||||
|
||||
/* ASN1 Print flags */
|
||||
|
||||
/* Indicate missing OPTIONAL fields */
|
||||
# define ASN1_PCTX_FLAGS_SHOW_ABSENT 0x001
|
||||
/* Mark start and end of SEQUENCE */
|
||||
# define ASN1_PCTX_FLAGS_SHOW_SEQUENCE 0x002
|
||||
/* Mark start and end of SEQUENCE/SET OF */
|
||||
# define ASN1_PCTX_FLAGS_SHOW_SSOF 0x004
|
||||
/* Show the ASN1 type of primitives */
|
||||
# define ASN1_PCTX_FLAGS_SHOW_TYPE 0x008
|
||||
/* Don't show ASN1 type of ANY */
|
||||
# define ASN1_PCTX_FLAGS_NO_ANY_TYPE 0x010
|
||||
/* Don't show ASN1 type of MSTRINGs */
|
||||
# define ASN1_PCTX_FLAGS_NO_MSTRING_TYPE 0x020
|
||||
/* Don't show field names in SEQUENCE */
|
||||
# define ASN1_PCTX_FLAGS_NO_FIELD_NAME 0x040
|
||||
/* Show structure names of each SEQUENCE field */
|
||||
# define ASN1_PCTX_FLAGS_SHOW_FIELD_STRUCT_NAME 0x080
|
||||
/* Don't show structure name even at top level */
|
||||
# define ASN1_PCTX_FLAGS_NO_STRUCT_NAME 0x100
|
||||
|
||||
int ASN1_item_print(BIO *out, ASN1_VALUE *ifld, int indent,
|
||||
const ASN1_ITEM *it, const ASN1_PCTX *pctx);
|
||||
ASN1_PCTX *ASN1_PCTX_new(void);
|
||||
void ASN1_PCTX_free(ASN1_PCTX *p);
|
||||
unsigned long ASN1_PCTX_get_flags(const ASN1_PCTX *p);
|
||||
void ASN1_PCTX_set_flags(ASN1_PCTX *p, unsigned long flags);
|
||||
unsigned long ASN1_PCTX_get_nm_flags(const ASN1_PCTX *p);
|
||||
void ASN1_PCTX_set_nm_flags(ASN1_PCTX *p, unsigned long flags);
|
||||
unsigned long ASN1_PCTX_get_cert_flags(const ASN1_PCTX *p);
|
||||
void ASN1_PCTX_set_cert_flags(ASN1_PCTX *p, unsigned long flags);
|
||||
unsigned long ASN1_PCTX_get_oid_flags(const ASN1_PCTX *p);
|
||||
void ASN1_PCTX_set_oid_flags(ASN1_PCTX *p, unsigned long flags);
|
||||
unsigned long ASN1_PCTX_get_str_flags(const ASN1_PCTX *p);
|
||||
void ASN1_PCTX_set_str_flags(ASN1_PCTX *p, unsigned long flags);
|
||||
|
||||
ASN1_SCTX *ASN1_SCTX_new(int (*scan_cb) (ASN1_SCTX *ctx));
|
||||
void ASN1_SCTX_free(ASN1_SCTX *p);
|
||||
const ASN1_ITEM *ASN1_SCTX_get_item(ASN1_SCTX *p);
|
||||
const ASN1_TEMPLATE *ASN1_SCTX_get_template(ASN1_SCTX *p);
|
||||
unsigned long ASN1_SCTX_get_flags(ASN1_SCTX *p);
|
||||
void ASN1_SCTX_set_app_data(ASN1_SCTX *p, void *data);
|
||||
void *ASN1_SCTX_get_app_data(ASN1_SCTX *p);
|
||||
|
||||
const BIO_METHOD *BIO_f_asn1(void);
|
||||
|
||||
BIO *BIO_new_NDEF(BIO *out, ASN1_VALUE *val, const ASN1_ITEM *it);
|
||||
|
||||
int i2d_ASN1_bio_stream(BIO *out, ASN1_VALUE *val, BIO *in, int flags,
|
||||
const ASN1_ITEM *it);
|
||||
int PEM_write_bio_ASN1_stream(BIO *out, ASN1_VALUE *val, BIO *in, int flags,
|
||||
const char *hdr, const ASN1_ITEM *it);
|
||||
int SMIME_write_ASN1(BIO *bio, ASN1_VALUE *val, BIO *data, int flags,
|
||||
int ctype_nid, int econt_nid,
|
||||
STACK_OF(X509_ALGOR) *mdalgs, const ASN1_ITEM *it);
|
||||
ASN1_VALUE *SMIME_read_ASN1(BIO *bio, BIO **bcont, const ASN1_ITEM *it);
|
||||
int SMIME_crlf_copy(BIO *in, BIO *out, int flags);
|
||||
int SMIME_text(BIO *in, BIO *out);
|
||||
|
||||
const ASN1_ITEM *ASN1_ITEM_lookup(const char *name);
|
||||
const ASN1_ITEM *ASN1_ITEM_get(size_t i);
|
||||
|
||||
# ifdef __cplusplus
|
||||
}
|
||||
# endif
|
||||
#endif
|
||||
@@ -1,10 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the OpenSSL license (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#error "This file is obsolete; please update your software."
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user