Compare commits

...

356 Commits

Author SHA1 Message Date
Documentation Updater
af4397d00e Updating documentation 2026-06-25 22:07:05 +00:00
Documentation Updater
87cd46d42f Updating documentation 2026-06-23 21:13:57 +00:00
Documentation Updater
05e38de854 Updating documentation 2026-06-18 19:40:17 +00:00
Documentation Updater
4d57da79ac Updating documentation 2026-06-18 15:07:21 +00:00
Documentation Updater
8309273cdb Updating documentation 2026-06-16 13:51:53 +00:00
Documentation Updater
394467b694 Updating documentation 2026-06-11 18:18:11 +00:00
Documentation Updater
251fa186dc Updating documentation 2026-06-04 13:25:59 +00:00
Documentation Updater
45fab90a00 Updating documentation 2026-06-02 20:36:54 +00:00
Documentation Updater
5d2ff7fd3b Updating documentation 2026-05-14 17:10:26 +00:00
Documentation Updater
f7b0357563 Updating documentation 2026-04-29 18:13:05 +00:00
Documentation Updater
75dabfcd86 Updating documentation 2026-04-17 18:11:56 +00:00
Documentation Updater
fa0efcaebb Updating documentation 2026-04-17 15:49:37 +00:00
Documentation Updater
7c2d482afe Updating documentation 2026-04-15 22:06:16 +00:00
Documentation Updater
624e45ec8f Updating documentation 2026-04-14 16:03:59 +00:00
Documentation Updater
8d0a494bee Updating documentation 2026-04-14 14:54:00 +00:00
Documentation Updater
cb0dae7ae1 Updating documentation 2026-04-10 00:41:51 +00:00
Documentation Updater
53c520c1ef Updating documentation 2026-04-02 00:57:28 +00:00
Documentation Updater
662ceda87b Updating documentation 2026-04-01 20:32:18 +00:00
Documentation Updater
cbe51139f9 Updating documentation 2026-04-01 17:56:56 +00:00
Documentation Updater
252666b020 Updating documentation 2026-03-31 17:02:57 +00:00
Documentation Updater
2d78b484a4 Updating documentation 2026-03-30 17:36:08 +00:00
Documentation Updater
1155ad62ba Updating documentation 2026-03-24 20:21:57 +00:00
Documentation Updater
8239f0ec89 Updating documentation 2026-03-19 18:45:16 +00:00
Documentation Updater
89036535c7 Updating documentation 2026-03-18 14:49:39 +00:00
Documentation Updater
aadf492a17 Updating documentation 2026-03-17 18:42:04 +00:00
Documentation Updater
49ac4472c4 Updating documentation 2026-03-16 14:33:30 +00:00
Documentation Updater
6fa4d87ec2 Updating documentation 2026-03-13 23:26:25 +00:00
Documentation Updater
229d1bd89c Updating documentation 2026-03-12 22:34:17 +00:00
Documentation Updater
6bde704cc1 Updating documentation 2026-03-12 16:49:36 +00:00
Documentation Updater
ddc0649111 Updating documentation 2026-03-12 16:13:38 +00:00
Documentation Updater
de80d7a3c1 Updating documentation 2026-03-11 18:33:39 +00:00
Documentation Updater
19ca8bf94d Updating documentation 2026-03-06 21:56:28 +00:00
Documentation Updater
c4008500da Updating documentation 2026-03-06 17:47:01 +00:00
Documentation Updater
b6b543d09b Updating documentation 2026-03-03 00:20:38 +00:00
Documentation Updater
ca98e0c45a Updating documentation 2026-02-27 22:49:39 +00:00
Documentation Updater
b672a344e3 Updating documentation 2026-02-24 22:57:28 +00:00
Documentation Updater
29c4694842 Updating documentation 2026-02-17 21:53:40 +00:00
Documentation Updater
22e449dc81 Updating documentation 2026-01-30 20:31:03 +00:00
Documentation Updater
020af68951 Updating documentation 2026-01-29 18:29:03 +00:00
Documentation Updater
8939188dac Updating documentation 2026-01-28 23:59:43 +00:00
Documentation Updater
8ec6cd5ae0 Updating documentation 2026-01-23 22:08:20 +00:00
Documentation Updater
7373d1da32 Updating documentation 2026-01-23 21:37:34 +00:00
Documentation Updater
847ecdb24f Updating documentation 2026-01-22 19:17:14 +00:00
Documentation Updater
f5af61304c Updating documentation 2026-01-13 21:04:51 +00:00
Documentation Updater
4891ef94e2 Updating documentation 2026-01-13 21:02:52 +00:00
Documentation Updater
fb865d9c6c Updating documentation 2026-01-10 16:57:42 +00:00
Documentation Updater
00750c1c41 Updating documentation 2025-12-10 23:28:55 +00:00
Documentation Updater
11bd3ce19c Updating documentation 2025-12-10 16:44:16 +00:00
Documentation Updater
de8d93bfb4 Updating documentation 2025-12-10 15:51:59 +00:00
Documentation Updater
ae190638d8 Updating documentation 2025-12-03 22:17:55 +00:00
Documentation Updater
afaa10b176 Updating documentation 2025-12-02 21:35:45 +00:00
Documentation Updater
d42e45b43c Updating documentation 2025-11-18 19:26:47 +00:00
Documentation Updater
4d464a762a Updating documentation 2025-10-30 15:34:18 +00:00
Documentation Updater
ffd1c80885 Updating documentation 2025-10-24 20:09:56 +00:00
Documentation Updater
bfbfe93af0 Updating documentation 2025-10-16 20:32:43 +00:00
Documentation Updater
f6f38e2fbd Updating documentation 2025-10-06 19:17:37 +00:00
Documentation Updater
2473ac8e62 Updating documentation 2025-10-01 18:42:55 +00:00
Documentation Updater
30e65782a2 Updating documentation 2025-09-17 15:59:49 +00:00
Documentation Updater
4c9a1be489 Updating documentation 2025-09-12 22:38:09 +00:00
Documentation Updater
d53f1dc247 Updating documentation 2025-09-02 20:41:53 +00:00
Documentation Updater
772688cc9a Updating documentation 2025-08-04 19:13:23 +00:00
Documentation Updater
813e390a6e Updating documentation 2025-08-04 14:34:02 +00:00
Documentation Updater
358795ca77 Updating documentation 2025-08-01 18:01:50 +00:00
Documentation Updater
5d145d020f Updating documentation 2025-07-30 20:33:11 +00:00
Documentation Updater
63db4a6770 Updating documentation 2025-07-29 16:25:24 +00:00
Documentation Updater
62d91a3f25 Updating documentation 2025-07-23 16:19:55 +00:00
Documentation Updater
dbfaa1187d Updating documentation 2025-07-21 18:24:40 +00:00
Documentation Updater
9e131743c5 Updating documentation 2025-07-18 23:14:57 +00:00
Documentation Updater
2d3de3a988 Updating documentation 2025-07-16 23:32:35 +00:00
Documentation Updater
bec414bd5c Updating documentation 2025-07-16 17:41:07 +00:00
Documentation Updater
8b5f8bf427 Updating documentation 2025-07-15 22:09:12 +00:00
Documentation Updater
69a14de876 Updating documentation 2025-07-11 19:51:09 +00:00
Documentation Updater
0f5256b758 Updating documentation 2025-07-09 15:28:32 +00:00
Documentation Updater
1fa8ece0b0 Updating documentation 2025-07-07 16:57:19 +00:00
Documentation Updater
f3256f8c29 Updating documentation 2025-06-23 14:50:57 +00:00
Documentation Updater
2dba844a59 Updating documentation 2025-06-18 19:49:28 +00:00
Documentation Updater
66d53cf6f0 Updating documentation 2025-06-17 15:42:10 +00:00
Documentation Updater
2eab162f8c Updating documentation 2025-06-10 23:19:02 +00:00
Documentation Updater
0feab732e3 Updating documentation 2025-06-04 18:23:19 +00:00
Documentation Updater
7e7e3a5c25 Updating documentation 2025-05-30 00:11:05 +00:00
Documentation Updater
61aeb30a8a Updating documentation 2025-05-28 21:51:33 +00:00
Documentation Updater
50901473db Updating documentation 2025-05-15 16:19:11 +00:00
Documentation Updater
b95564ccc4 Updating documentation 2025-04-28 19:32:18 +00:00
Documentation Updater
633755e3e5 Updating documentation 2025-04-25 16:01:42 +00:00
Documentation Updater
55485ed6f6 Updating documentation 2025-04-24 17:47:35 +00:00
Documentation Updater
898e9de371 Updating documentation 2025-04-17 16:23:11 +00:00
Documentation Updater
52399585a6 Updating documentation 2025-04-10 02:10:39 +00:00
Documentation Updater
666850cc2b Updating documentation 2025-04-09 18:49:41 +00:00
Documentation Updater
45987d101b Updating documentation 2025-04-08 15:44:54 +00:00
Documentation Updater
131af1a147 Updating documentation 2025-04-07 16:05:03 +00:00
Documentation Updater
4db5e34305 Updating documentation 2025-04-07 14:04:42 +00:00
Documentation Updater
e1bc6c18d2 Updating documentation 2025-04-02 20:14:08 +00:00
Documentation Updater
4250869835 Updating documentation 2025-04-02 13:44:30 +00:00
Documentation Updater
4e2fcfce5f Updating documentation 2025-03-27 17:22:32 +00:00
Documentation Updater
edfc25832b Updating documentation 2025-03-24 18:44:00 +00:00
Documentation Updater
bbdcb82e86 Updating documentation 2025-03-19 22:10:06 +00:00
Documentation Updater
253ea3a5ba Updating documentation 2025-03-08 17:47:54 +00:00
Documentation Updater
a618c44f4b Updating documentation 2025-02-24 22:15:17 +00:00
Documentation Updater
2407391557 Updating documentation 2025-02-11 00:13:30 +00:00
Documentation Updater
3e1d117b68 Updating documentation 2025-02-10 17:48:55 +00:00
Documentation Updater
6f7bbe3d77 Updating documentation 2025-02-07 20:42:24 +00:00
Documentation Updater
0c1e7d842c Updating documentation 2025-02-03 22:29:28 +00:00
Documentation Updater
c92559c7cf Updating documentation 2025-01-31 16:57:46 +00:00
Documentation Updater
b13b94bc99 Updating documentation 2025-01-14 21:42:03 +00:00
Documentation Updater
138316ab33 Updating documentation 2025-01-10 15:58:03 +00:00
Documentation Updater
9e603380fb Updating documentation 2025-01-03 15:43:50 +00:00
Documentation Updater
85d2ea9eef Updating documentation 2024-12-27 23:50:47 +00:00
Documentation Updater
1d63384de9 Updating documentation 2024-12-20 19:27:04 +00:00
Documentation Updater
c7d628ee62 Updating documentation 2024-12-19 17:12:22 +00:00
Documentation Updater
6662ce651b Updating documentation 2024-12-17 20:57:18 +00:00
Documentation Updater
9366e19014 Updating documentation 2024-12-12 23:16:37 +00:00
Documentation Updater
7af2433167 Updating documentation 2024-12-04 17:55:33 +00:00
Documentation Updater
6fa6b64d9c Updating documentation 2024-11-27 17:49:17 +00:00
Documentation Updater
3da371107d Updating documentation 2024-11-27 00:54:04 +00:00
Documentation Updater
d953f5cde3 Updating documentation 2024-11-26 23:24:32 +00:00
Documentation Updater
36d86e83cf Updating documentation 2024-11-25 21:40:54 +00:00
Documentation Updater
238f896837 Updating documentation 2024-11-23 00:09:55 +00:00
Documentation Updater
605dcb95db Updating documentation 2024-11-22 21:16:07 +00:00
Documentation Updater
7ac354918d Updating documentation 2024-11-15 21:01:07 +00:00
Documentation Updater
6bdda194ff Updating documentation 2024-11-12 17:58:45 +00:00
Documentation Updater
837c97a15a Updating documentation 2024-11-11 17:43:25 +00:00
Documentation Updater
c03b35cc5a Updating documentation 2024-11-06 21:08:01 +00:00
Documentation Updater
c3213b6d39 Updating documentation 2024-11-06 19:26:30 +00:00
Documentation Updater
eeec613888 Updating documentation 2024-11-06 15:17:41 +00:00
Documentation Updater
ff83f19ee9 Updating documentation 2024-11-05 17:02:16 +00:00
Documentation Updater
17f72be3ea Updating documentation 2024-11-01 21:14:22 +00:00
Documentation Updater
f0ba2c9804 Updating documentation 2024-10-31 22:35:50 +00:00
Documentation Updater
e27c12b3a0 Updating documentation 2024-10-31 15:13:49 +00:00
Documentation Updater
fbe5a0babb Updating documentation 2024-10-30 21:18:44 +00:00
Documentation Updater
d75cf059c0 Updating documentation 2024-10-29 20:46:38 +00:00
Documentation Updater
06b039dac4 Updating documentation 2024-10-29 14:58:52 +00:00
Documentation Updater
c99691fca5 Updating documentation 2024-10-24 19:26:37 +00:00
Documentation Updater
baffa0703d Updating documentation 2024-10-23 21:45:59 +00:00
Documentation Updater
78e76338c8 Updating documentation 2024-10-21 19:48:25 +00:00
Documentation Updater
d7e42ae922 Updating documentation 2024-10-17 17:48:49 +00:00
Documentation Updater
420e1d089c Updating documentation 2024-10-15 17:24:29 +00:00
Documentation Updater
1bdbe67a54 Updating documentation 2024-10-10 22:00:09 +00:00
Documentation Updater
39dda7c410 Updating documentation 2024-10-10 15:08:34 +00:00
Documentation Updater
18d1069418 Updating documentation 2024-10-09 15:30:44 +00:00
Documentation Updater
e7c54b3225 Updating documentation 2024-10-04 22:17:59 +00:00
Documentation Updater
9f89e84b58 Updating documentation 2024-10-01 19:50:52 +00:00
Documentation Updater
68b7d824ab Updating documentation 2024-09-27 22:00:09 +00:00
Documentation Updater
e75e38271b Updating documentation 2024-09-26 23:34:16 +00:00
Documentation Updater
1834229177 Updating documentation 2024-09-24 23:34:35 +00:00
Documentation Updater
52bbdbd953 Updating documentation 2024-09-23 20:49:17 +00:00
Documentation Updater
309dd31165 Updating documentation 2024-09-19 00:23:41 +00:00
Documentation Updater
96ab308e6c Updating documentation 2024-09-17 21:23:37 +00:00
Documentation Updater
b2b4e20817 Updating documentation 2024-09-16 19:46:49 +00:00
Documentation Updater
d9d91ed53e Updating documentation 2024-09-13 21:08:03 +00:00
Documentation Updater
bad3a266fb Updating documentation 2024-09-06 18:40:57 +00:00
Documentation Updater
eb97cb07c0 Updating documentation 2024-08-30 19:18:07 +00:00
Documentation Updater
1dd2050c47 Updating documentation 2024-08-28 22:01:56 +00:00
Documentation Updater
578e9b6501 Updating documentation 2024-08-21 22:56:29 +00:00
Documentation Updater
48ca510fb2 Updating documentation 2024-08-16 21:27:40 +00:00
Documentation Updater
4ecb26203c Updating documentation 2024-08-16 00:10:28 +00:00
Documentation Updater
465e8b8449 Updating documentation 2024-08-14 17:19:54 +00:00
Documentation Updater
d9a89c26f9 Updating documentation 2024-08-12 22:58:14 +00:00
Documentation Updater
99a12d3e76 Updating documentation 2024-08-12 22:19:31 +00:00
Documentation Updater
1e07ef8daa Updating documentation 2024-08-07 21:14:33 +00:00
Documentation Updater
6f5fc324ce Updating documentation 2024-08-06 22:00:13 +00:00
Documentation Updater
749a7a43b4 Updating documentation 2024-08-02 21:30:15 +00:00
Documentation Updater
0bf04a27fa Updating documentation 2024-07-25 16:01:13 +00:00
Documentation Updater
1ee2032a38 Updating documentation 2024-07-24 15:08:10 +00:00
Documentation Updater
25ebf298a0 Updating documentation 2024-07-23 22:28:22 +00:00
Documentation Updater
da2727bd42 Updating documentation 2024-07-18 18:57:14 +00:00
Documentation Updater
ee8936c96a Updating documentation 2024-07-17 20:36:43 +00:00
Documentation Updater
4f2018d416 Updating documentation 2024-07-08 21:23:47 +00:00
Documentation Updater
bd29d93293 Updating documentation 2024-06-25 15:49:45 +00:00
Documentation Updater
f1c599a166 Updating documentation 2024-06-24 20:06:40 +00:00
Documentation Updater
f4d35aefb7 Updating documentation 2024-06-20 22:08:49 +00:00
Documentation Updater
135363068f Updating documentation 2024-06-20 15:56:38 +00:00
Documentation Updater
8e8bc2b827 Updating documentation 2024-06-14 14:46:37 +00:00
Documentation Updater
f916e61533 Updating documentation 2024-06-14 14:35:32 +00:00
Documentation Updater
b05f40c33d Updating documentation 2024-06-12 19:07:29 +00:00
Documentation Updater
05ec2d3479 Updating documentation 2024-06-05 16:41:01 +00:00
Documentation Updater
0bd1ace8b5 Updating documentation 2024-06-04 21:28:16 +00:00
Documentation Updater
267aabb361 Updating documentation 2024-05-22 14:51:54 +00:00
Documentation Updater
e83703ee63 Updating documentation 2024-05-17 17:32:34 +00:00
Documentation Updater
22d3fe0c5a Updating documentation 2024-05-01 16:15:18 +00:00
Documentation Updater
b6b719e8b9 Updating documentation 2024-04-29 17:20:10 +00:00
Documentation Updater
ec3ca97994 Updating documentation 2024-04-24 14:04:48 +00:00
Documentation Updater
e37ab06fa3 Updating documentation 2024-04-23 22:24:52 +00:00
Documentation Updater
e522f30140 Updating documentation 2024-04-22 14:17:26 +00:00
Documentation Updater
1b710afb49 Updating documentation 2024-04-20 05:49:37 +00:00
Documentation Updater
ca80ce3806 Updating documentation 2024-04-16 22:43:58 +00:00
Documentation Updater
6ffb8264a2 Updating documentation 2024-04-16 18:08:16 +00:00
Documentation Updater
aaf5f4250b Updating documentation 2024-04-15 21:34:22 +00:00
Documentation Updater
f4a49617f3 Updating documentation 2024-04-12 17:21:38 +00:00
Documentation Updater
c08d412edb Updating documentation 2024-04-11 17:22:13 +00:00
Documentation Updater
6ea5aa0494 Updating documentation 2024-04-11 01:00:34 +00:00
Documentation Updater
61ee38f3f2 Updating documentation 2024-04-04 21:45:29 +00:00
Documentation Updater
09065420ed Updating documentation 2024-04-02 15:37:58 +00:00
Documentation Updater
6a39482216 Updating documentation 2024-04-01 23:02:56 +00:00
Documentation Updater
640a47bafc Updating documentation 2024-04-01 20:31:39 +00:00
Documentation Updater
c02e8928df Updating documentation 2024-03-27 22:52:52 +00:00
Documentation Updater
834fdb5088 Updating documentation 2024-03-26 22:04:17 +00:00
Documentation Updater
4c99d4c328 Updating documentation 2024-03-20 17:53:41 +00:00
Documentation Updater
3f89143650 Updating documentation 2024-03-19 19:05:09 +00:00
Documentation Updater
652cfd16ab Updating documentation 2024-03-13 22:49:46 +00:00
Documentation Updater
63afc00660 Updating documentation 2024-03-08 21:03:06 +00:00
Documentation Updater
3eb49ad7b6 Updating documentation 2024-02-26 16:04:55 +00:00
Documentation Updater
6911de3e7a Updating documentation 2024-02-23 17:42:15 +00:00
Documentation Updater
673c3bff0e Updating documentation 2024-02-21 21:11:28 +00:00
Documentation Updater
afd4b8a87b Updating documentation 2024-02-20 18:09:25 +00:00
Documentation Updater
37e7fcf699 Updating documentation 2024-02-19 01:34:21 +00:00
Documentation Updater
23a95d617b Updating documentation 2024-02-19 00:17:50 +00:00
Documentation Updater
bfeac37ef4 Updating documentation 2024-02-16 18:34:38 +00:00
Documentation Updater
dc2195d8a9 Updating documentation 2024-02-16 00:00:51 +00:00
Documentation Updater
760ca33abc Updating documentation 2024-02-15 23:58:28 +00:00
Documentation Updater
9debf45ed4 Updating documentation 2024-02-15 00:12:01 +00:00
Documentation Updater
92d07a3446 Updating documentation 2024-02-14 22:57:11 +00:00
Documentation Updater
11213d0e50 Updating documentation 2024-02-09 22:16:00 +00:00
Documentation Updater
a69b382190 Updating documentation 2024-02-09 16:46:44 +00:00
Documentation Updater
1f43ed5d8d Updating documentation 2024-02-07 23:58:05 +00:00
Documentation Updater
191477645d Updating documentation 2024-02-07 00:15:16 +00:00
Documentation Updater
b3190b2b8a Updating documentation 2024-02-05 20:46:36 +00:00
Documentation Updater
2818e5f3ef Updating documentation 2024-01-30 21:28:28 +00:00
Documentation Updater
6696213bbd Updating documentation 2024-01-26 15:54:35 +00:00
Documentation Updater
9fe35de833 Updating documentation 2024-01-25 21:46:00 +00:00
Documentation Updater
8f2ad5972d Updating documentation 2024-01-24 22:07:22 +00:00
Documentation Updater
7aec08439a Updating documentation 2024-01-23 22:24:12 +00:00
Documentation Updater
ca326c934f Updating documentation 2024-01-22 18:17:01 +00:00
Documentation Updater
254d5b286a Updating documentation 2024-01-20 17:40:54 +00:00
Documentation Updater
ad2bb0abb1 Updating documentation 2024-01-20 17:40:27 +00:00
Documentation Updater
a36d86d0d7 Updating documentation 2024-01-19 21:33:53 +00:00
Documentation Updater
165141acaf Updating documentation 2024-01-18 17:45:16 +00:00
Documentation Updater
56e0ffbc24 Updating documentation 2024-01-12 19:27:22 +00:00
Documentation Updater
0a41391984 Updating documentation 2024-01-11 17:55:42 +00:00
Documentation Updater
1f2619cbd8 Updating documentation 2024-01-08 23:03:22 +00:00
Documentation Updater
0d0d2daee0 Updating documentation 2024-01-08 16:42:40 +00:00
Documentation Updater
3166b79356 Updating documentation 2024-01-04 21:35:13 +00:00
Documentation Updater
72e1c95ebf Updating documentation 2024-01-02 23:05:41 +00:00
Documentation Updater
aa886b6dfc Updating documentation 2023-12-22 17:32:23 +00:00
Documentation Updater
9cad167f85 Updating documentation 2023-12-19 20:53:10 +00:00
Documentation Updater
b6f78a3549 Updating documentation 2023-12-18 21:27:53 +00:00
Documentation Updater
3b792a4496 Updating documentation 2023-12-15 20:36:44 +00:00
Documentation Updater
03e5a58c75 Updating documentation 2023-12-15 02:00:35 +00:00
Documentation Updater
a80f44b833 Updating documentation 2023-12-13 23:43:21 +00:00
Documentation Updater
37bc1086b5 Updating documentation 2023-12-13 18:16:01 +00:00
Documentation Updater
8ff089ec91 Updating documentation 2023-12-12 21:18:04 +00:00
Documentation Updater
a1da27dea8 Updating documentation 2023-12-12 15:02:47 +00:00
Documentation Updater
ff4f9f77f2 Updating documentation 2023-12-08 17:46:34 +00:00
Documentation Updater
8eaf4b97b9 Updating documentation 2023-12-07 19:20:43 +00:00
Documentation Updater
835a90813a Updating documentation 2023-12-07 17:23:34 +00:00
Documentation Updater
a26bdb0666 Updating documentation 2023-12-06 22:25:53 +00:00
Documentation Updater
1a25b65d0d Updating documentation 2023-12-06 22:02:54 +00:00
Documentation Updater
bc87852873 Updating documentation 2023-12-06 18:16:32 +00:00
Documentation Updater
44af128526 Updating documentation 2023-12-06 17:41:22 +00:00
Documentation Updater
ab8b1a45e3 Updating documentation 2023-12-01 00:49:39 +00:00
Documentation Updater
c0111255d7 Updating documentation 2023-11-29 22:27:29 +00:00
Documentation Updater
335164df8b Updating documentation 2023-11-29 16:08:59 +00:00
Documentation Updater
488e55ab2c Updating documentation 2023-11-28 18:40:39 +00:00
Documentation Updater
d92d8d5e0f Updating documentation 2023-11-28 17:42:54 +00:00
Documentation Updater
5d97bc9d20 Updating documentation 2023-11-27 15:32:51 +00:00
Documentation Updater
8347ca3201 Updating documentation 2023-11-22 16:02:52 +00:00
Documentation Updater
34033e7cec Updating documentation 2023-11-17 00:55:15 +00:00
Documentation Updater
214bc53fae Updating documentation 2023-11-16 18:18:19 +00:00
Documentation Updater
6ce63945c4 Updating documentation 2023-11-16 02:12:09 +00:00
Documentation Updater
31c862b230 Updating documentation 2023-11-15 07:05:42 +00:00
Documentation Updater
64c5638706 Updating documentation 2023-11-13 19:03:27 +00:00
Documentation Updater
53dd49a25b Updating documentation 2023-11-11 01:56:19 +00:00
Documentation Updater
6cb93fc4f9 Updating documentation 2023-11-02 18:05:27 +00:00
Documentation Updater
ff90f7d9f6 Updating documentation 2023-11-01 21:25:16 +00:00
Documentation Updater
3d5f737b53 Updating documentation 2023-11-01 15:58:14 +00:00
Documentation Updater
029634df06 Updating documentation 2023-10-30 21:32:40 +00:00
Documentation Updater
79f47536f1 Updating documentation 2023-10-30 18:37:43 +00:00
Documentation Updater
27a57e0259 Updating documentation 2023-10-27 14:53:36 +00:00
Documentation Updater
fdca7ea9f1 Updating documentation 2023-10-24 20:58:09 +00:00
Documentation Updater
8f2f872113 Updating documentation 2023-10-20 22:09:44 +00:00
Documentation Updater
46d3d5d4d8 Updating documentation 2023-10-20 18:34:59 +00:00
Documentation Updater
5717ea3b5d Updating documentation 2023-10-20 15:43:29 +00:00
Documentation Updater
809a9f99f1 Updating documentation 2023-10-19 18:58:58 +00:00
Documentation Updater
67c780482b Updating documentation 2023-10-19 18:47:33 +00:00
Documentation Updater
df4263a7ae Updating documentation 2023-10-18 16:33:47 +00:00
Documentation Updater
624cf453bc Updating documentation 2023-10-17 16:59:33 +00:00
Documentation Updater
18bd04b502 Updating documentation 2023-10-12 17:24:49 +00:00
Documentation Updater
eaad560404 Updating documentation 2023-10-11 22:51:14 +00:00
Documentation Updater
07daec5e7e Updating documentation 2023-10-05 23:10:44 +00:00
Documentation Updater
24dc9509fe Updating documentation 2023-10-04 15:09:43 +00:00
Documentation Updater
cd18291036 Updating documentation 2023-10-03 20:20:36 +00:00
Documentation Updater
073c172b4c Updating documentation 2023-09-29 16:42:00 +00:00
Documentation Updater
cbc1296898 Updating documentation 2023-09-27 19:55:25 +00:00
Documentation Updater
2828ce0f6c Updating documentation 2023-09-26 16:41:12 +00:00
Documentation Updater
bd5c8cb450 Updating documentation 2023-09-25 20:48:57 +00:00
Documentation Updater
eb05f2cad4 Updating documentation 2023-09-19 17:54:12 +00:00
Documentation Updater
37bdfa2332 Updating documentation 2023-09-14 23:15:14 +00:00
Documentation Updater
49f7d8b712 Updating documentation 2023-09-13 23:32:01 +00:00
Documentation Updater
5ce8a1f59a Updating documentation 2023-09-13 20:37:26 +00:00
Documentation Updater
61c36b7f4d Updating documentation 2023-09-11 21:06:16 +00:00
Documentation Updater
2fb13541b0 Updating documentation 2023-09-07 22:35:33 +00:00
Documentation Updater
4b3c6de61b Updating documentation 2023-09-07 22:07:10 +00:00
Documentation Updater
5fe3f04ba9 Updating documentation 2023-09-07 17:39:58 +00:00
Documentation Updater
d6098f2c9c Updating documentation 2023-09-06 22:01:56 +00:00
Documentation Updater
fa021ff3e5 Updating documentation 2023-09-05 21:29:38 +00:00
Documentation Updater
8f2cd859e1 Updating documentation 2023-09-05 18:26:38 +00:00
Documentation Updater
9df8b4ef19 Updating documentation 2023-08-31 21:22:29 +00:00
Documentation Updater
17c8a79cf7 Updating documentation 2023-08-30 23:14:35 +00:00
Documentation Updater
74211590b5 Updating documentation 2023-08-29 23:23:36 +00:00
Documentation Updater
27f46c8bf8 Updating documentation 2023-08-25 23:02:39 +00:00
Documentation Updater
e109ff7397 Updating documentation 2023-08-24 17:44:31 +00:00
Documentation Updater
daa0ef3352 Updating documentation 2023-08-24 16:58:59 +00:00
Documentation Updater
a10365e30d Updating documentation 2023-08-17 21:37:59 +00:00
Documentation Updater
6f1c94c84f Updating documentation 2023-08-17 16:59:13 +00:00
Documentation Updater
c06286b0f4 Updating documentation 2023-08-15 18:37:36 +00:00
Documentation Updater
00367022a7 Updating documentation 2023-08-10 18:58:20 +00:00
Documentation Updater
1281831d1c Updating documentation 2023-08-09 18:23:29 +00:00
Documentation Updater
d17f5906d2 Updating documentation 2023-08-09 16:45:16 +00:00
Documentation Updater
d04124a42e Updating documentation 2023-08-04 21:52:28 +00:00
Documentation Updater
c149d1e962 Updating documentation 2023-08-04 20:21:16 +00:00
Documentation Updater
9cb5bfcf2a Updating documentation 2023-08-04 17:24:50 +00:00
Documentation Updater
d709cc4ed3 Updating documentation 2023-08-02 21:40:41 +00:00
Documentation Updater
a95fafde1a Updating documentation 2023-07-28 20:09:03 +00:00
Documentation Updater
16fe53a281 Updating documentation 2023-07-26 23:15:16 +00:00
Documentation Updater
3c7632a9dc Updating documentation 2023-07-21 18:15:28 +00:00
Documentation Updater
ab6b6ab09d Updating documentation 2023-07-19 18:50:27 +00:00
Documentation Updater
652e3b1e06 Updating documentation 2023-07-12 20:57:04 +00:00
Documentation Updater
4eb3cc9d97 Updating documentation 2023-07-07 00:55:02 +00:00
Documentation Updater
6e7a06d7be Updating documentation 2023-07-05 18:43:05 +00:00
Documentation Updater
4215ef6aa9 Updating documentation 2023-06-30 18:25:24 +00:00
Documentation Updater
cff83d8124 Updating documentation 2023-06-30 14:20:13 +00:00
Documentation Updater
1467fce54c Updating documentation 2023-06-28 22:32:21 +00:00
Documentation Updater
9be7bacb18 Updating documentation 2023-06-27 03:26:00 +00:00
Documentation Updater
b76b4ffcc4 Updating documentation 2023-06-16 19:32:43 +00:00
Documentation Updater
687e3bcb18 Updating documentation 2023-06-14 22:40:28 +00:00
Documentation Updater
41b02d4e90 Updating documentation 2023-06-09 15:32:43 +00:00
Documentation Updater
19854af8d8 Updating documentation 2023-06-08 17:43:47 +00:00
Documentation Updater
ca7b63743e Updating documentation 2023-06-06 22:31:49 +00:00
Documentation Updater
9d5cdf11ae Updating documentation 2023-06-05 17:13:59 +00:00
Documentation Updater
d4a24f152a Updating documentation 2023-06-02 18:58:43 +00:00
Documentation Updater
e7c24f1f01 Updating documentation 2023-06-01 05:27:23 +00:00
Documentation Updater
c40c1fafc9 Updating documentation 2023-05-31 21:19:52 +00:00
Documentation Updater
431d6d6f59 Updating documentation 2023-05-26 22:52:40 +00:00
Documentation Updater
2e67c13fe3 Updating documentation 2023-05-26 13:58:18 +00:00
Documentation Updater
87bcacdeb4 Updating documentation 2023-05-25 16:25:03 +00:00
Documentation Updater
728757fbdd Updating documentation 2023-05-18 15:09:42 +00:00
Documentation Updater
9404dfb89e Updating documentation 2023-05-17 18:11:55 +00:00
Documentation Updater
075b268805 Updating documentation 2023-05-12 17:12:35 +00:00
Documentation Updater
632941602b Updating documentation 2023-05-09 21:15:08 +00:00
Documentation Updater
4ea1d13d26 Updating documentation 2023-05-09 19:32:24 +00:00
Documentation Updater
d3f68e7d52 Updating documentation 2023-05-05 18:42:54 +00:00
Documentation Updater
5e1ae88b74 Updating documentation 2023-05-04 17:44:02 +00:00
Documentation Updater
a36f20558f Updating documentation 2023-05-03 19:32:42 +00:00
Documentation Updater
926503fa3f Updating documentation 2023-05-01 21:39:07 +00:00
Documentation Updater
5d0e6ff464 Updating documentation 2023-04-21 20:28:41 +00:00
Documentation Updater
c81cbe9550 Updating documentation 2023-04-10 20:40:53 +00:00
Documentation Updater
4e26676290 Updating documentation 2023-03-29 19:49:59 +00:00
Documentation Updater
5159a594c3 Updating documentation 2023-03-22 21:50:15 +00:00
Documentation Updater
d130ec5785 Updating documentation 2023-03-20 20:42:44 +00:00
Documentation Updater
252b9f6f8b Updating documentation 2023-03-17 19:47:30 +00:00
Documentation Updater
50dfd34754 Updating documentation 2023-02-27 22:42:19 +00:00
Documentation Updater
a1a9db6357 Updating documentation 2023-02-27 22:34:32 +00:00
Documentation Updater
a75fa16527 Updating documentation 2023-02-27 22:34:19 +00:00
Documentation Updater
a67e5ef83b Updating documentation 2023-02-24 23:42:09 +00:00
Documentation Updater
f4ba415202 Updating documentation 2023-02-24 21:26:41 +00:00
Documentation Updater
98e7efa9a0 Updating documentation 2023-02-24 20:31:05 +00:00
Sergey Skrobotov
7dd2e1184b initial commit for the documentation branch 2023-02-22 11:57:41 -08:00
770 changed files with 46168 additions and 111759 deletions

File diff suppressed because it is too large Load Diff

4
.github/FUNDING.yml vendored
View File

@ -1,4 +0,0 @@
# Copyright 2021 Signal Messenger, LLC
# SPDX-License-Identifier: AGPL-3.0-only
custom: https://signal.org/donate/

0
.github/stale.yml vendored
View File

View File

@ -1,23 +0,0 @@
name: Service CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
container: ubuntu:22.04
steps:
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
- name: Set up JDK 17
uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc # v3.6.0
with:
distribution: 'temurin'
java-version: 17
cache: 'maven'
env:
# work around an issue with actions/runner setting an incorrect HOME in containers, which breaks maven caching
# https://github.com/actions/setup-java/issues/356
HOME: /root
- name: Build with Maven
run: ./mvnw -e -B verify

31
.gitignore vendored
View File

@ -1,31 +0,0 @@
target
local.properties
.idea
*.iml
run.sh
*~
local.yml
config/production.yml
config/federated.yml
config/staging.yml
config/testing.yml
config/deploy.properties
/service/config/production.yml
/service/config/federated.yml
/service/config/staging.yml
/service/config/testing.yml
/service/config/deploy.properties
/service/dependency-reduced-pom.xml
.java-version
.opsmanage
put.sh
deployer-staging.properties
deployer-production.properties
deployer.log
/service/src/main/resources/org/signal/badges/Badges_*.properties
!/service/src/main/resources/org/signal/badges/Badges_en.properties
/service/src/main/resources/org/signal/subscriptions/Subscriptions_*.properties
!/service/src/main/resources/org/signal/subscriptions/Subscriptions_en.properties
.project
.classpath
.settings

11
.gitmodules vendored
View File

@ -1,11 +0,0 @@
# Note that the implementation of the spam filter is private; internal
# developers will need to override this URL with:
#
# ```
# git config submodule.spam-filter.url PRIVATE_URL
# ```
#
# External developers may safely ignore this submodule.
[submodule "spam-filter"]
path = spam-filter
url = REDACTED

View File

@ -1,9 +0,0 @@
<extensions xmlns="http://maven.apache.org/EXTENSIONS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/EXTENSIONS/1.0.0 http://maven.apache.org/xsd/core-extensions-1.0.0.xsd">
<extension>
<groupId>fr.brouillard.oss</groupId>
<artifactId>jgitver-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>

View File

@ -1,14 +0,0 @@
<configuration xmlns="http://jgitver.github.io/maven/configuration/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jgitver.github.io/maven/configuration/1.1.0 https://jgitver.github.io/maven/configuration/jgitver-configuration-v1_1_0.xsd">
<useDirty>true</useDirty>
<useDefaultBranchingPolicy>false</useDefaultBranchingPolicy>
<branchPolicies>
<branchPolicy>
<pattern>(.*)</pattern>
<transformations>
<transformation>IGNORE</transformation>
</transformations>
</branchPolicy>
</branchPolicies>
</configuration>

Binary file not shown.

View File

@ -1,18 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar

View File

@ -1,26 +0,0 @@
Signal-Server
=================
Documentation
-------------
Looking for protocol documentation? Check out the website!
https://signal.org/docs/
Cryptography Notice
------------
This distribution includes cryptographic software. The country in which you currently reside may have restrictions on the import, possession, use, and/or re-export to another country, of encryption software.
BEFORE using any encryption software, please check your country's laws, regulations and policies concerning the import, possession, or use, and re-export of encryption software, to see if this is permitted.
See <https://www.wassenaar.org/> for more information.
The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software using or performing cryptographic functions with asymmetric algorithms.
The form and manner of this distribution makes it eligible for export under the License Exception ENC Technology Software Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section 740.13) for both object code and source code.
License
---------------------
Copyright 2013-2022 Signal Messenger, LLC
Licensed under the AGPLv3: https://www.gnu.org/licenses/agpl-3.0.html

View File

@ -1,86 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2022 Signal Messenger, LLC
~ SPDX-License-Identifier: AGPL-3.0-only
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>TextSecureServer</artifactId>
<groupId>org.whispersystems.textsecure</groupId>
<version>JGITVER</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>event-logger</artifactId>
<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-logging</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<exclusions>
<exclusion>
<groupId>org.jetbrains</groupId>
<!--
depends on an outdated version (13.0) for JDK 6 compatibility, but its safe to override
https://youtrack.jetbrains.com/issue/KT-25047
-->
<artifactId>annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-serialization-json</artifactId>
<version>${kotlinx-serialization.version}</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<compilerPlugins>
<plugin>kotlinx-serialization</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-serialization</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,40 +0,0 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.event
import java.util.Collections
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
val module = SerializersModule {
polymorphic(Event::class) {
subclass(RemoteConfigSetEvent::class)
subclass(RemoteConfigDeleteEvent::class)
}
}
val jsonFormat = Json { serializersModule = module }
sealed interface Event
@Serializable
data class RemoteConfigSetEvent(
val token: String,
val name: String,
val percentage: Int,
val defaultValue: String? = null,
val value: String? = null,
val hashKey: String? = null,
val uuids: Collection<String> = Collections.emptyList(),
) : Event
@Serializable
data class RemoteConfigDeleteEvent(
val token: String,
val name: String,
) : Event

View File

@ -1,41 +0,0 @@
/*
* Copyright 2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.event
import com.google.cloud.logging.LogEntry
import com.google.cloud.logging.Logging
import com.google.cloud.logging.MonitoredResourceUtil
import com.google.cloud.logging.Payload.JsonPayload
import com.google.cloud.logging.Severity
import com.google.protobuf.Struct
import com.google.protobuf.util.JsonFormat
import kotlinx.serialization.encodeToString
interface AdminEventLogger {
fun logEvent(event: Event, labels: Map<String, String>?)
fun logEvent(event: Event) = logEvent(event, null)
}
class NoOpAdminEventLogger : AdminEventLogger {
override fun logEvent(event: Event, labels: Map<String, String>?) {}
}
class GoogleCloudAdminEventLogger(private val logging: Logging, private val projectId: String, private val logName: String) : AdminEventLogger {
override fun logEvent(event: Event, labels: Map<String, String>?) {
val structBuilder = Struct.newBuilder()
JsonFormat.parser().merge(jsonFormat.encodeToString(event), structBuilder)
val struct = structBuilder.build()
val logEntryBuilder = LogEntry.newBuilder(JsonPayload.of(struct))
.setLogName(logName)
.setSeverity(Severity.NOTICE)
.setResource(MonitoredResourceUtil.getResource(projectId, "project"));
if (labels != null) {
logEntryBuilder.setLabels(labels);
}
logging.write(listOf(logEntryBuilder.build()))
}
}

View File

@ -1,22 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.event
import com.google.cloud.logging.Logging
import org.junit.jupiter.api.Test
import org.mockito.Mockito.mock
class GoogleCloudAdminEventLoggerTest {
@Test
fun logEvent() {
val logging = mock(Logging::class.java)
val logger = GoogleCloudAdminEventLogger(logging, "my-project", "test")
val event = RemoteConfigDeleteEvent("token", "test")
logger.logEvent(event)
}
}

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

291
grpc/index.html Normal file
View File

@ -0,0 +1,291 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Protobuf module documentation</title>
<link rel="stylesheet" href="static/mystyles.css" />
<script src="static/fontawesome.js"></script>
<script src="static/lunr.js"></script>
<script src="static/theme.js"></script>
</head>
<body>
<div class="container">
<nav class="level pt-6 mb-5 pb-2 bottom-border">
<div class="level-left">
<p class="level-item has-text-centered is-size-3 has-text-weight-medium">
<a class="link is-info" href="index.html">
Protobuf module documentation
</a>
</p>
</div>
<div class="level-right">
<form action="search.html" class="mr-2 has-text-centered">
<input type="text" name="query" class="input mr-3 navbar-search-query-input" placeholder="Search">
<input type="submit" class="button is-primary mr-5" value="Search" />
</form>
<p class="level-item has-text-centered is-size-5">
<a href="" class="link is-info mr-5" target="_blank">
<span>Source</span>
</a>
<button class="theme-toggle" id="theme-toggle" title="Toggles light & dark" aria-label="auto" aria-live="polite" type="button">
<svg class="sun-and-moon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<mask class="moon" id="moon-mask">
<rect x="0" y="0" width="100%" height="100%" fill="white" />
<circle cx="24" cy="10" r="6" fill="black" />
</mask>
<circle class="sun" cx="12" cy="12" r="6" mask="url(#moon-mask)" fill="currentColor" />
<g class="sun-beams" stroke="currentColor">
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</g>
</svg>
</button>
</p>
</div>
</nav>
</div>
<div id="main-content" class="container mb-6">
<p class="mb-2">
<a style="font-size: 0.9em" href="#packages">↓ Packages</a>
</p>
<div class="block content"><h1>Chat gRPC API</h1>
<h2>Request metadata</h2>
<p>Clients may provide headers for gRPC requests via <a href="https://grpc.io/docs/guides/metadata/">gRPC metadata</a> which translates directly to HTTP/2 headers.</p>
<ul>
<li>Clients should provide a <code>User-Agent</code> header on all gRPC requests. </li>
<li>Clients may provide an <code>Accept-Language</code> on any gRPC requests.</li>
</ul>
<h2>Authentication</h2>
<p>To authenticate for a specific signal account + device, clients may provide a <a href="https://www.rfc-editor.org/info/rfc7617/">Basic authorization header</a>
on the request. The username must be the account identifier encoded in the 8-4-4-4-12 format followed by a '.'
character and then the device id serialized as a string.</p>
<p>All services either require or forbid providing authentication via an Authorization header. If the service is annotated
with the <code>require.auth</code> option <code>AUTH_ONLY_AUTHENTICATED</code>, all requests to the service must contain valid authentication
headers. If the service is annotated with the option <code>AUTH_ONLY_ANONYMOUS</code> the client must not provide an authorization
header. If provided, the request will fail with a <code>BAD_AUTHENTICATION</code> error.</p>
<h2>Errors</h2>
<p>In the gRPC protocol all errors are at the request level. That is, errors are returned in response to individual requests and do not impact other H2 streams on the same connection nor terminate the connection.</p>
<p>For errors that may be returned by any RPCs, the chat server will return the well-defined gRPC status codes returned as part of every RPC call. For errors that are specific to a particular RPC, the error must be encoded in the service proto definition and will be returned with a <code>Status</code> of <code>OK</code>.</p>
<p>Status errors include additional metadata as described in <a href="https://google.aip.dev/193#error_model">AIP-193 (google's richer error model)</a>. Every <code>Status != OK</code> response returned by the chat server's application layer will include a <code>Grpc-Status-Details-Bin</code> response trailer with a <code>google.rpc.Status</code> proto.</p>
<p>Each <code>google.rpc.Status</code> must have a status matching the top-level status on the gRPC response. Additionally, a single <code>ErrorInfo</code> must always be present in the details field of the <code>Status</code>. The <code>ErrorInfo</code> must contain a <code>domain</code> field and a <code>reason</code> field.</p>
<p>The <code>domain</code> for a status error generated by the chat server will always be <code>grpc.chat.signal.org</code></p>
<p>The server may set the <code>reason</code> to match the enum string for the <code>Status.Code</code> if there is no need to further distinguish a code. Clients should inspect the <code>reason</code>, not the <code>status</code>, for automated error handling.</p>
<p>The chat server may return the following errors from any RPC</p>
<table>
<thead>
<tr>
<th style="text-align: left;">Status Code</th>
<th style="text-align: left;">Reason</th>
<th style="text-align: left;">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><code>INVALID_ARGUMENT</code></td>
<td style="text-align: left;"><code>UPGRADE_REQUIRED</code></td>
<td style="text-align: left;">The client version provided in the <code>User-Agent</code> is no longer supported. The client must upgrade to use the service.</td>
</tr>
<tr>
<td style="text-align: left;"><code>INVALID_ARGUMENT</code></td>
<td style="text-align: left;"><code>CONSTRAINT_VIOLATED</code></td>
<td style="text-align: left;">The RPC argument violated a constraint that was annotated or documented in the service definition. It is always possible to check this constraint without communicating with the chat server. This always represents a client bug or out of date client. <br><br> The <code>details</code> may include a <a href="https://github.com/googleapis/googleapis/blob/8c06c1e04ae562f49f411357577c700e9142f33c/google/rpc/error_details.proto#L236"><code>BadRequest</code> message</a> that indicates additional information about the invalid field.</td>
</tr>
<tr>
<td style="text-align: left;"><code>INVALID_ARGUMENT</code></td>
<td style="text-align: left;"><code>BAD_AUTHENTICATION</code></td>
<td style="text-align: left;">The request has incorrectly set authentication credentials for the RPC. This represents a client bug where the authorization mode is not correct for the RPC. For example, <br><br> The RPC was for an anonymous service, but included an Authentication header in the RPC metadata. <br><br>The RPC should only be made by the primary device, but the request had linked device credentials.</td>
</tr>
<tr>
<td style="text-align: left;"><code>UNAUTHENTICATED</code></td>
<td style="text-align: left;"><code>INVALID_CREDENTIALS</code></td>
<td style="text-align: left;">The account credentials provided in the authorization header are not valid.</td>
</tr>
<tr>
<td style="text-align: left;"><code>RESOURCE_EXHAUSTED</code></td>
<td style="text-align: left;"><code>RESOURCE_EXHAUSTED</code></td>
<td style="text-align: left;">A server-side resource was exhausted. The <code>details</code> field may include a <a href="https://github.com/googleapis/googleapis/blob/8c06c1e04ae562f49f411357577c700e9142f33c/google/rpc/error_details.proto#L92"><code>RetryInfo</code> message</a> that includes the amount of time in seconds the client should wait before retrying the request. <br><br> If a <code>RetryInfo</code> is present, the client must wait the indicated time before retrying the request. If absent, the client should retry with an exponential backoff.</td>
</tr>
<tr>
<td style="text-align: left;"><code>ABORTED</code></td>
<td style="text-align: left;"><code>STREAM_CLOSED</code></td>
<td style="text-align: left;">The server terminated a streaming RPC for some domain specific reason. The <code>details</code> field must include a message with additional information about why the stream was closed. This message must be defined in the corresponding service proto for the streaming RPC. This status must only be returned from server-side streaming RPCs. Clients must handle a STREAM_CLOSED status even if no corresponding message is defined in the version of the proto they are targeting. Unless otherwise noted, if a stream termination message is defined, clients must handle the error at any point in the message stream.</td>
</tr>
<tr>
<td style="text-align: left;"><code>UNAVAILABLE</code></td>
<td style="text-align: left;"><code>UNAVAILABLE</code></td>
<td style="text-align: left;">There was an internal error processing the RPC. The client should retry the request with exponential backoff.</td>
</tr>
</tbody>
</table>
<h3>Logging Errors</h3>
<p>When logging error responses, clients may always log the status code, domain, and reason.</p>
<p>For errors with domain <code>grpc.chat.signal.org</code> and reason <code>CONSTRAINT_VIOLATED</code>, clients should check the <code>details</code> field for a <code>BadRequest</code> proto. If present, they should log the <code>field</code> of each violation in <code>field_violations</code>.</p>
<h3>GOAWAY</h3>
<p>In a partial or total server outage, the server may start rejecting new connection requests or terminating existing connections with an HTTP/2 GOAWAY frame with code 0x40.</p>
<p>If clients receive a 0x40 GOAWAY they should retry connections with an aggressive exponential backoff.</p></div>
<h3 id="packages" class="title is-3">
Packages
</h3>
<div class="block">This site contains the documentation for the following Protobuf packages.</div>
<div class="block">
<h4 class="title is-5">
<a href="org.signal.chat.account.html">org.signal.chat.account</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.attachments.html">org.signal.chat.attachments</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.backup.html">org.signal.chat.backup</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.calling.html">org.signal.chat.calling</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.calling.quality.html">org.signal.chat.calling.quality</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.challenge.html">org.signal.chat.challenge</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.common.html">org.signal.chat.common</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.credentials.html">org.signal.chat.credentials</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.device.html">org.signal.chat.device</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.donations.html">org.signal.chat.donations</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.errors.html">org.signal.chat.errors</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.keys.html">org.signal.chat.keys</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.messages.html">org.signal.chat.messages</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.one_time_donations.html">org.signal.chat.one_time_donations</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.payments.html">org.signal.chat.payments</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.profile.html">org.signal.chat.profile</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.require.html">org.signal.chat.require</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.subscriptions.html">org.signal.chat.subscriptions</a>
</h4>
<h4 class="title is-5">
<a href="org.signal.chat.tag.html">org.signal.chat.tag</a>
</h4>
</div>
</div>
<footer class="footer">
<div class="content has-text-centered">
<p>Built with <strong><a href="https://github.com/markvincze/sabledocs" target="_blank">sabledocs</a></strong>.</p>
</div>
</footer>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,487 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Protobuf module documentation</title>
<link rel="stylesheet" href="static/mystyles.css" />
<script src="static/fontawesome.js"></script>
<script src="static/lunr.js"></script>
<script src="static/theme.js"></script>
</head>
<body>
<div class="container">
<nav class="level pt-6 mb-5 pb-2 bottom-border">
<div class="level-left">
<p class="level-item has-text-centered is-size-3 has-text-weight-medium">
<a class="link is-info" href="index.html">
Protobuf module documentation
</a>
</p>
</div>
<div class="level-right">
<form action="search.html" class="mr-2 has-text-centered">
<input type="text" name="query" class="input mr-3 navbar-search-query-input" placeholder="Search">
<input type="submit" class="button is-primary mr-5" value="Search" />
</form>
<p class="level-item has-text-centered is-size-5">
<a href="" class="link is-info mr-5" target="_blank">
<span>Source</span>
</a>
<button class="theme-toggle" id="theme-toggle" title="Toggles light & dark" aria-label="auto" aria-live="polite" type="button">
<svg class="sun-and-moon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<mask class="moon" id="moon-mask">
<rect x="0" y="0" width="100%" height="100%" fill="white" />
<circle cx="24" cy="10" r="6" fill="black" />
</mask>
<circle class="sun" cx="12" cy="12" r="6" mask="url(#moon-mask)" fill="currentColor" />
<g class="sun-beams" stroke="currentColor">
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</g>
</svg>
</button>
</p>
</div>
</nav>
</div>
<div id="main-content" class="container mb-6">
<p class="mb-2">
<a style="font-size: 0.9em" href="index.html">← Back to packages</a>
</p>
<h3 class="title is-3">
Package <code>org.signal.chat.attachments</code>
</h3>
<div class="block">
<p>
<a href="#service-org.signal.chat.attachments.Attachments">
<span class="tag" style="min-width: 6em">Service</span>
<code>Attachments</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.attachments.GetStickerUploadFormRequest">
<span class="tag" style="min-width: 6em">Message</span>
<code>GetStickerUploadFormRequest</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.attachments.GetStickerUploadFormResponse">
<span class="tag" style="min-width: 6em">Message</span>
<code>GetStickerUploadFormResponse</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.attachments.GetUploadFormRequest">
<span class="tag" style="min-width: 6em">Message</span>
<code>GetUploadFormRequest</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.attachments.GetUploadFormResponse">
<span class="tag" style="min-width: 6em">Message</span>
<code>GetUploadFormResponse</code>
</a>
</p>
</div>
<div class="block">
</div>
<div class="block">
<h3 class="title is-3">
Services
</h3>
<div>
<h4 class="title is-4 type-heading" id="service-org.signal.chat.attachments.Attachments">
<a href="#service-org.signal.chat.attachments.Attachments" title="org.signal.chat.attachments.Attachments">service Attachments</a>
</h4>
<div class="block content">
<p></p>
</div>
<div class="box">
<div class="block">
<h5 class="title is-5" id="service-org.signal.chat.attachments.Attachments-GetUploadForm">
<a href="#service-org.signal.chat.attachments.Attachments-GetUploadForm">
rpc <code>GetUploadForm</code>
</a>
</h5>
Request: <code><a href="org.signal.chat.attachments.html#message-org.signal.chat.attachments.GetUploadFormRequest"><span title="org.signal.chat.attachments.GetUploadFormRequest">org.signal.chat.attachments.GetUploadFormRequest</span>
</a></code><br>
Response: <code><a href="org.signal.chat.attachments.html#message-org.signal.chat.attachments.GetUploadFormResponse"><span title="org.signal.chat.attachments.GetUploadFormResponse">org.signal.chat.attachments.GetUploadFormResponse</span>
</a></code><br>
</div>
<div class="block">
<p><p>Retrieve an upload form that can be used to perform a resumable upload</p></p>
</div>
</div>
<div class="box">
<div class="block">
<h5 class="title is-5" id="service-org.signal.chat.attachments.Attachments-GetStickerUploadForm">
<a href="#service-org.signal.chat.attachments.Attachments-GetStickerUploadForm">
rpc <code>GetStickerUploadForm</code>
</a>
</h5>
Request: <code><a href="org.signal.chat.attachments.html#message-org.signal.chat.attachments.GetStickerUploadFormRequest"><span title="org.signal.chat.attachments.GetStickerUploadFormRequest">org.signal.chat.attachments.GetStickerUploadFormRequest</span>
</a></code><br>
Response: <code><a href="org.signal.chat.attachments.html#message-org.signal.chat.attachments.GetStickerUploadFormResponse"><span title="org.signal.chat.attachments.GetStickerUploadFormResponse">org.signal.chat.attachments.GetStickerUploadFormResponse</span>
</a></code><br>
</div>
<div class="block">
<p><p>Retrieve an upload form that can be used to upload a sticker pack</p></p>
</div>
</div>
</div>
</div>
<h3 class="title is-3">
Messages
</h3>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.attachments.GetStickerUploadFormRequest">
<span>
<a href="#message-org.signal.chat.attachments.GetStickerUploadFormRequest" title="org.signal.chat.attachments.GetStickerUploadFormRequest">message GetStickerUploadFormRequest</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
sticker_count
</td>
<td>
<code title="uint32">
<span>uint32</span>
</code>
</td>
<td class="content"><p>The number of stickers in the sticker pack to upload</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.attachments.GetStickerUploadFormResponse">
<span>
<a href="#message-org.signal.chat.attachments.GetStickerUploadFormResponse" title="org.signal.chat.attachments.GetStickerUploadFormResponse">message GetStickerUploadFormResponse</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
pack_id
</td>
<td>
<code title="string">
<span>string</span>
</code>
</td>
<td class="content"><p>A randomly-generated ID for the new sticker pack</p></td>
</tr>
<tr>
<td>2</td>
<td>
manifest_upload_form
</td>
<td>
<code title="org.signal.chat.common.S3UploadForm">
<a href="org.signal.chat.common.html#message-org.signal.chat.common.S3UploadForm"><span title="org.signal.chat.common.S3UploadForm">org.signal.chat.common.S3UploadForm</span>
</a>
</code>
</td>
<td class="content"><p>An upload form clients must use to upload a manifest for the sticker pack</p></td>
</tr>
<tr>
<td>3</td>
<td>
sticker_upload_forms
</td>
<td>
<code title="org.signal.chat.common.S3UploadForm">
repeated
<a href="org.signal.chat.common.html#message-org.signal.chat.common.S3UploadForm"><span title="org.signal.chat.common.S3UploadForm">org.signal.chat.common.S3UploadForm</span>
</a>
</code>
</td>
<td class="content"><p>Upload forms for individual stickers within the sticker pack</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.attachments.GetUploadFormRequest">
<span>
<a href="#message-org.signal.chat.attachments.GetUploadFormRequest" title="org.signal.chat.attachments.GetUploadFormRequest">message GetUploadFormRequest</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
uploadLength
</td>
<td>
<code title="uint64">
<span>uint64</span>
</code>
</td>
<td class="content"><p>The length of the attachment for the requested upload form. Uploads
performed with this form will be limited to the provided length.</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.attachments.GetUploadFormResponse">
<span>
<a href="#message-org.signal.chat.attachments.GetUploadFormResponse" title="org.signal.chat.attachments.GetUploadFormResponse">message GetUploadFormResponse</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="nohover"><td style="border: 0px"></td></tr>
<tr class="nohover">
<td class="oneof-heading" colspan="4">
<div class="oneof-heading">
<strong>oneof outcome</strong>
</div>
</td>
</tr>
<tr>
<td>
1
</td>
<td>
upload_form
</td>
<td>
<code title="org.signal.chat.common.UploadForm">
<a href="org.signal.chat.common.html#message-org.signal.chat.common.UploadForm"><span title="org.signal.chat.common.UploadForm">org.signal.chat.common.UploadForm</span>
</a>
</code>
</td>
<td class="content"></td>
</tr>
<tr>
<td>
2
</td>
<td>
exceeds_max_upload_length
</td>
<td>
<code title="org.signal.chat.errors.FailedPrecondition">
<a href="org.signal.chat.errors.html#message-org.signal.chat.errors.FailedPrecondition"><span title="org.signal.chat.errors.FailedPrecondition">org.signal.chat.errors.FailedPrecondition</span>
</a>
</code>
</td>
<td class="content"><p>The request size was larger than the maximum supported upload size. The
maximum upload size is subject to change and is governed by
<code>global.attachments.maxBytes</code></p></td>
</tr>
</tbody>
</table>
</div>
</div>
<footer class="footer">
<div class="content has-text-centered">
<p>Built with <strong><a href="https://github.com/markvincze/sabledocs" target="_blank">sabledocs</a></strong>.</p>
</div>
</footer>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,544 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Protobuf module documentation</title>
<link rel="stylesheet" href="static/mystyles.css" />
<script src="static/fontawesome.js"></script>
<script src="static/lunr.js"></script>
<script src="static/theme.js"></script>
</head>
<body>
<div class="container">
<nav class="level pt-6 mb-5 pb-2 bottom-border">
<div class="level-left">
<p class="level-item has-text-centered is-size-3 has-text-weight-medium">
<a class="link is-info" href="index.html">
Protobuf module documentation
</a>
</p>
</div>
<div class="level-right">
<form action="search.html" class="mr-2 has-text-centered">
<input type="text" name="query" class="input mr-3 navbar-search-query-input" placeholder="Search">
<input type="submit" class="button is-primary mr-5" value="Search" />
</form>
<p class="level-item has-text-centered is-size-5">
<a href="" class="link is-info mr-5" target="_blank">
<span>Source</span>
</a>
<button class="theme-toggle" id="theme-toggle" title="Toggles light & dark" aria-label="auto" aria-live="polite" type="button">
<svg class="sun-and-moon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<mask class="moon" id="moon-mask">
<rect x="0" y="0" width="100%" height="100%" fill="white" />
<circle cx="24" cy="10" r="6" fill="black" />
</mask>
<circle class="sun" cx="12" cy="12" r="6" mask="url(#moon-mask)" fill="currentColor" />
<g class="sun-beams" stroke="currentColor">
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</g>
</svg>
</button>
</p>
</div>
</nav>
</div>
<div id="main-content" class="container mb-6">
<p class="mb-2">
<a style="font-size: 0.9em" href="index.html">← Back to packages</a>
</p>
<h3 class="title is-3">
Package <code>org.signal.chat.calling</code>
</h3>
<div class="block">
<p>
<a href="#service-org.signal.chat.calling.Calling">
<span class="tag" style="min-width: 6em">Service</span>
<code>Calling</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.calling.GetCallingRelaysRequest">
<span class="tag" style="min-width: 6em">Message</span>
<code>GetCallingRelaysRequest</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.calling.GetCallingRelaysResponse">
<span class="tag" style="min-width: 6em">Message</span>
<code>GetCallingRelaysResponse</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.calling.GetCallingRelaysResponse.HostnameUrlList">
<span class="tag" style="min-width: 6em">Message</span>
<code>HostnameUrlList</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.calling.GetCallingRelaysResponse.IpUrlList">
<span class="tag" style="min-width: 6em">Message</span>
<code>IpUrlList</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.calling.GetCallingRelaysResponse.Relay">
<span class="tag" style="min-width: 6em">Message</span>
<code>Relay</code>
</a>
</p>
</div>
<div class="block">
</div>
<div class="block">
<h3 class="title is-3">
Services
</h3>
<div>
<h4 class="title is-4 type-heading" id="service-org.signal.chat.calling.Calling">
<a href="#service-org.signal.chat.calling.Calling" title="org.signal.chat.calling.Calling">service Calling</a>
</h4>
<div class="block content">
<p><p>Provides methods for getting credentials and relay options for one-on-one
calls.</p></p>
</div>
<div class="box">
<div class="block">
<h5 class="title is-5" id="service-org.signal.chat.calling.Calling-GetCallingRelays">
<a href="#service-org.signal.chat.calling.Calling-GetCallingRelays">
rpc <code>GetCallingRelays</code>
</a>
</h5>
Request: <code><a href="org.signal.chat.calling.html#message-org.signal.chat.calling.GetCallingRelaysRequest"><span title="org.signal.chat.calling.GetCallingRelaysRequest">org.signal.chat.calling.GetCallingRelaysRequest</span>
</a></code><br>
Response: <code><a href="org.signal.chat.calling.html#message-org.signal.chat.calling.GetCallingRelaysResponse"><span title="org.signal.chat.calling.GetCallingRelaysResponse">org.signal.chat.calling.GetCallingRelaysResponse</span>
</a></code><br>
</div>
<div class="block">
<p><p>Retrieves TURN credentials and relay options for one-on-one calls.</p></p>
</div>
</div>
</div>
</div>
<h3 class="title is-3">
Messages
</h3>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.calling.GetCallingRelaysRequest">
<span>
<a href="#message-org.signal.chat.calling.GetCallingRelaysRequest" title="org.signal.chat.calling.GetCallingRelaysRequest">message GetCallingRelaysRequest</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.calling.GetCallingRelaysResponse">
<span>
<a href="#message-org.signal.chat.calling.GetCallingRelaysResponse" title="org.signal.chat.calling.GetCallingRelaysResponse">message GetCallingRelaysResponse</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
relays
</td>
<td>
<code title="org.signal.chat.calling.GetCallingRelaysResponse.Relay">
repeated
<a href="org.signal.chat.calling.html#message-org.signal.chat.calling.GetCallingRelaysResponse.Relay"><span title="org.signal.chat.calling.GetCallingRelaysResponse.Relay">org.signal.chat.calling.GetCallingRelaysResponse.Relay</span>
</a>
</code>
</td>
<td class="content"><p>A collection of calling relays a client may use for one-on-one calls.</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.calling.GetCallingRelaysResponse.HostnameUrlList">
<span>
<a href="#message-org.signal.chat.calling.GetCallingRelaysResponse.HostnameUrlList" title="org.signal.chat.calling.GetCallingRelaysResponse.HostnameUrlList">message HostnameUrlList</a>
<br /><span style="font-size: 0.7em; font-weight: normal">(Nested in <a href="org.signal.chat.calling.html#message-org.signal.chat.calling.GetCallingRelaysResponse"><span title="org.signal.chat.calling.GetCallingRelaysResponse">org.signal.chat.calling.GetCallingRelaysResponse</span>
</a>)</span>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
urls
</td>
<td>
<code title="string">
repeated
<span>string</span>
</code>
</td>
<td class="content"><p>A collection of hostname-based TURN, TURNS, or STUN URLs a client can use
to connect to a relay.</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.calling.GetCallingRelaysResponse.IpUrlList">
<span>
<a href="#message-org.signal.chat.calling.GetCallingRelaysResponse.IpUrlList" title="org.signal.chat.calling.GetCallingRelaysResponse.IpUrlList">message IpUrlList</a>
<br /><span style="font-size: 0.7em; font-weight: normal">(Nested in <a href="org.signal.chat.calling.html#message-org.signal.chat.calling.GetCallingRelaysResponse"><span title="org.signal.chat.calling.GetCallingRelaysResponse">org.signal.chat.calling.GetCallingRelaysResponse</span>
</a>)</span>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
urls
</td>
<td>
<code title="string">
repeated
<span>string</span>
</code>
</td>
<td class="content"><p>A collection of IP-based TURN, TURNS, or STUN URLs a client can use to
connect to a relay.</p></td>
</tr>
<tr>
<td>2</td>
<td>
hostname
</td>
<td>
<code title="string">
optional
<span>string</span>
</code>
</td>
<td class="content"><p>A hostname clients must use to validate the relay's TLS certificate when
connecting via an IP-based TURNS URL. May not be specified if <code>urls</code>
contains no TURNS URLs.</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.calling.GetCallingRelaysResponse.Relay">
<span>
<a href="#message-org.signal.chat.calling.GetCallingRelaysResponse.Relay" title="org.signal.chat.calling.GetCallingRelaysResponse.Relay">message Relay</a>
<br /><span style="font-size: 0.7em; font-weight: normal">(Nested in <a href="org.signal.chat.calling.html#message-org.signal.chat.calling.GetCallingRelaysResponse"><span title="org.signal.chat.calling.GetCallingRelaysResponse">org.signal.chat.calling.GetCallingRelaysResponse</span>
</a>)</span>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
username
</td>
<td>
<code title="string">
<span>string</span>
</code>
</td>
<td class="content"><p>A username that can be presented to authenticate with a TURN server.</p></td>
</tr>
<tr>
<td>2</td>
<td>
password
</td>
<td>
<code title="string">
<span>string</span>
</code>
</td>
<td class="content"><p>A password that can be presented to authenticate with a TURN server.</p></td>
</tr>
<tr>
<td>3</td>
<td>
credential_ttl_seconds
</td>
<td>
<code title="uint64">
<span>uint64</span>
</code>
</td>
<td class="content"><p>The duration, in seconds, after which the included username and password
will no longer be valid.</p></td>
</tr>
<tr>
<td>4</td>
<td>
hostname_urls
</td>
<td>
<code title="org.signal.chat.calling.GetCallingRelaysResponse.HostnameUrlList">
optional
<a href="org.signal.chat.calling.html#message-org.signal.chat.calling.GetCallingRelaysResponse.HostnameUrlList"><span title="org.signal.chat.calling.GetCallingRelaysResponse.HostnameUrlList">org.signal.chat.calling.GetCallingRelaysResponse.HostnameUrlList</span>
</a>
</code>
</td>
<td class="content"><p>A collection of hostname-based URLs clients may use to connect to this
relay.</p></td>
</tr>
<tr>
<td>5</td>
<td>
ip_urls
</td>
<td>
<code title="org.signal.chat.calling.GetCallingRelaysResponse.IpUrlList">
optional
<a href="org.signal.chat.calling.html#message-org.signal.chat.calling.GetCallingRelaysResponse.IpUrlList"><span title="org.signal.chat.calling.GetCallingRelaysResponse.IpUrlList">org.signal.chat.calling.GetCallingRelaysResponse.IpUrlList</span>
</a>
</code>
</td>
<td class="content"><p>A collection of IP-based URLs clients may use to connect to this relay.</p></td>
</tr>
</tbody>
</table>
</div>
</div>
<footer class="footer">
<div class="content has-text-centered">
<p>Built with <strong><a href="https://github.com/markvincze/sabledocs" target="_blank">sabledocs</a></strong>.</p>
</div>
</footer>
</body>
</html>

View File

@ -0,0 +1,635 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Protobuf module documentation</title>
<link rel="stylesheet" href="static/mystyles.css" />
<script src="static/fontawesome.js"></script>
<script src="static/lunr.js"></script>
<script src="static/theme.js"></script>
</head>
<body>
<div class="container">
<nav class="level pt-6 mb-5 pb-2 bottom-border">
<div class="level-left">
<p class="level-item has-text-centered is-size-3 has-text-weight-medium">
<a class="link is-info" href="index.html">
Protobuf module documentation
</a>
</p>
</div>
<div class="level-right">
<form action="search.html" class="mr-2 has-text-centered">
<input type="text" name="query" class="input mr-3 navbar-search-query-input" placeholder="Search">
<input type="submit" class="button is-primary mr-5" value="Search" />
</form>
<p class="level-item has-text-centered is-size-5">
<a href="" class="link is-info mr-5" target="_blank">
<span>Source</span>
</a>
<button class="theme-toggle" id="theme-toggle" title="Toggles light & dark" aria-label="auto" aria-live="polite" type="button">
<svg class="sun-and-moon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<mask class="moon" id="moon-mask">
<rect x="0" y="0" width="100%" height="100%" fill="white" />
<circle cx="24" cy="10" r="6" fill="black" />
</mask>
<circle class="sun" cx="12" cy="12" r="6" mask="url(#moon-mask)" fill="currentColor" />
<g class="sun-beams" stroke="currentColor">
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</g>
</svg>
</button>
</p>
</div>
</nav>
</div>
<div id="main-content" class="container mb-6">
<p class="mb-2">
<a style="font-size: 0.9em" href="index.html">← Back to packages</a>
</p>
<h3 class="title is-3">
Package <code>org.signal.chat.calling.quality</code>
</h3>
<div class="block">
<p>
<a href="#service-org.signal.chat.calling.quality.CallQuality">
<span class="tag" style="min-width: 6em">Service</span>
<code>CallQuality</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.calling.quality.SubmitCallQualitySurveyRequest">
<span class="tag" style="min-width: 6em">Message</span>
<code>SubmitCallQualitySurveyRequest</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.calling.quality.SubmitCallQualitySurveyResponse">
<span class="tag" style="min-width: 6em">Message</span>
<code>SubmitCallQualitySurveyResponse</code>
</a>
</p>
</div>
<div class="block">
</div>
<div class="block">
<h3 class="title is-3">
Services
</h3>
<div>
<h4 class="title is-4 type-heading" id="service-org.signal.chat.calling.quality.CallQuality">
<a href="#service-org.signal.chat.calling.quality.CallQuality" title="org.signal.chat.calling.quality.CallQuality">service CallQuality</a>
</h4>
<div class="block content">
<p><p>Provides methods for submitting call quality surveys</p></p>
</div>
<div class="box">
<div class="block">
<h5 class="title is-5" id="service-org.signal.chat.calling.quality.CallQuality-SubmitCallQualitySurvey">
<a href="#service-org.signal.chat.calling.quality.CallQuality-SubmitCallQualitySurvey">
rpc <code>SubmitCallQualitySurvey</code>
</a>
</h5>
Request: <code><a href="org.signal.chat.calling.quality.html#message-org.signal.chat.calling.quality.SubmitCallQualitySurveyRequest"><span title="org.signal.chat.calling.quality.SubmitCallQualitySurveyRequest">org.signal.chat.calling.quality.SubmitCallQualitySurveyRequest</span>
</a></code><br>
Response: <code><a href="org.signal.chat.calling.quality.html#message-org.signal.chat.calling.quality.SubmitCallQualitySurveyResponse"><span title="org.signal.chat.calling.quality.SubmitCallQualitySurveyResponse">org.signal.chat.calling.quality.SubmitCallQualitySurveyResponse</span>
</a></code><br>
</div>
<div class="block">
<p><p>Submits a call quality survey response.</p></p>
</div>
</div>
</div>
</div>
<h3 class="title is-3">
Messages
</h3>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.calling.quality.SubmitCallQualitySurveyRequest">
<span>
<a href="#message-org.signal.chat.calling.quality.SubmitCallQualitySurveyRequest" title="org.signal.chat.calling.quality.SubmitCallQualitySurveyRequest">message SubmitCallQualitySurveyRequest</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
user_satisfied
</td>
<td>
<code title="bool">
<span>bool</span>
</code>
</td>
<td class="content"><p>Indicates whether the caller was generally satisfied with the quality of
the call</p></td>
</tr>
<tr>
<td>2</td>
<td>
call_quality_issues
</td>
<td>
<code title="string">
repeated
<span>string</span>
</code>
</td>
<td class="content"><p>A list of call quality issues selected by the caller</p></td>
</tr>
<tr>
<td>3</td>
<td>
additional_issues_description
</td>
<td>
<code title="string">
optional
<span>string</span>
</code>
</td>
<td class="content"><p>A free-form description of any additional issues as written by the caller</p></td>
</tr>
<tr>
<td>4</td>
<td>
debug_log_url
</td>
<td>
<code title="string">
optional
<span>string</span>
</code>
</td>
<td class="content"><p>A URL for a set of debug logs associated with the call if the caller chose
to submit debug logs</p></td>
</tr>
<tr>
<td>5</td>
<td>
start_timestamp
</td>
<td>
<code title="int64">
<span>int64</span>
</code>
</td>
<td class="content"><p>The time at which the call started in milliseconds since the epoch</p></td>
</tr>
<tr>
<td>6</td>
<td>
end_timestamp
</td>
<td>
<code title="int64">
<span>int64</span>
</code>
</td>
<td class="content"><p>The time at which the call ended in milliseconds since the epoch</p></td>
</tr>
<tr>
<td>7</td>
<td>
call_type
</td>
<td>
<code title="string">
<span>string</span>
</code>
</td>
<td class="content"><p>The type of call; note that direct voice calls can become video calls and
vice versa, and this field indicates which mode was selected at call
initiation time. At the time of writing, expected call types are
"direct_voice", "direct_video", "group", and "call_link".</p></td>
</tr>
<tr>
<td>8</td>
<td>
success
</td>
<td>
<code title="bool">
<span>bool</span>
</code>
</td>
<td class="content"><p>Indicates whether the call completed without error or if it terminated
abnormally</p></td>
</tr>
<tr>
<td>9</td>
<td>
call_end_reason
</td>
<td>
<code title="string">
<span>string</span>
</code>
</td>
<td class="content"><p>A client-defined, but human-readable reason for call termination</p></td>
</tr>
<tr>
<td>10</td>
<td>
connection_rtt_median
</td>
<td>
<code title="float">
optional
<span>float</span>
</code>
</td>
<td class="content"><p>The median round-trip time, measured in milliseconds, for STUN/ICE packets
(i.e. connection maintenance and establishment)</p></td>
</tr>
<tr>
<td>11</td>
<td>
audio_rtt_median
</td>
<td>
<code title="float">
optional
<span>float</span>
</code>
</td>
<td class="content"><p>The median round-trip time, measured in milliseconds, for RTP/RTCP packets
for audio streams</p></td>
</tr>
<tr>
<td>12</td>
<td>
video_rtt_median
</td>
<td>
<code title="float">
optional
<span>float</span>
</code>
</td>
<td class="content"><p>The median round-trip time, measured in milliseconds, for RTP/RTCP packets
for video streams</p></td>
</tr>
<tr>
<td>13</td>
<td>
audio_recv_jitter_median
</td>
<td>
<code title="float">
optional
<span>float</span>
</code>
</td>
<td class="content"><p>The median jitter for audio streams, measured in milliseconds, for the
duration of the call as measured by the client submitting the survey</p></td>
</tr>
<tr>
<td>14</td>
<td>
video_recv_jitter_median
</td>
<td>
<code title="float">
optional
<span>float</span>
</code>
</td>
<td class="content"><p>The median jitter for video streams, measured in milliseconds, for the
duration of the call as measured by the client submitting the survey</p></td>
</tr>
<tr>
<td>15</td>
<td>
audio_send_jitter_median
</td>
<td>
<code title="float">
optional
<span>float</span>
</code>
</td>
<td class="content"><p>The median jitter for audio streams, measured in milliseconds, for the
duration of the call as measured by the remote endpoint in the call (either
the peer of the client submitting the survey in a direct call or the SFU in
a group call)</p></td>
</tr>
<tr>
<td>16</td>
<td>
video_send_jitter_median
</td>
<td>
<code title="float">
optional
<span>float</span>
</code>
</td>
<td class="content"><p>The median jitter for video streams, measured in milliseconds, for the
duration of the call as measured by the remote endpoint in the call (either
the peer of the client submitting the survey in a direct call or the SFU in
a group call)</p></td>
</tr>
<tr>
<td>17</td>
<td>
audio_recv_packet_loss_fraction
</td>
<td>
<code title="float">
optional
<span>float</span>
</code>
</td>
<td class="content"><p>The fraction of audio packets lost over the duration of the call as
measured by the client submitting the survey</p></td>
</tr>
<tr>
<td>18</td>
<td>
video_recv_packet_loss_fraction
</td>
<td>
<code title="float">
optional
<span>float</span>
</code>
</td>
<td class="content"><p>The fraction of video packets lost over the duration of the call as
measured by the client submitting the survey</p></td>
</tr>
<tr>
<td>19</td>
<td>
audio_send_packet_loss_fraction
</td>
<td>
<code title="float">
optional
<span>float</span>
</code>
</td>
<td class="content"><p>The fraction of audio packets lost over the duration of the call as
measured by the remote endpoint in the call (either the peer of the client
submitting the survey in a direct call or the SFU in a group call)</p></td>
</tr>
<tr>
<td>20</td>
<td>
video_send_packet_loss_fraction
</td>
<td>
<code title="float">
optional
<span>float</span>
</code>
</td>
<td class="content"><p>The fraction of video packets lost over the duration of the call as
measured by the remote endpoint in the call (either the peer of the client
submitting the survey in a direct call or the SFU in a group call)</p></td>
</tr>
<tr>
<td>21</td>
<td>
call_telemetry
</td>
<td>
<code title="bytes">
optional
<span>bytes</span>
</code>
</td>
<td class="content"><p>Machine-generated telemetry from the call; this is a serialized protobuf
entity generated (and, critically, explained to the user!) by the calling
library</p></td>
</tr>
<tr>
<td>22</td>
<td>
call_id_hash
</td>
<td>
<code title="bytes">
optional
<span>bytes</span>
</code>
</td>
<td class="content"><p>A hash of a call ID (shared between clients and never sent to the calling
server) that can be used to correlate survey responses from multiple
participants in a call</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.calling.quality.SubmitCallQualitySurveyResponse">
<span>
<a href="#message-org.signal.chat.calling.quality.SubmitCallQualitySurveyResponse" title="org.signal.chat.calling.quality.SubmitCallQualitySurveyResponse">message SubmitCallQualitySurveyResponse</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<footer class="footer">
<div class="content has-text-centered">
<p>Built with <strong><a href="https://github.com/markvincze/sabledocs" target="_blank">sabledocs</a></strong>.</p>
</div>
</footer>
</body>
</html>

View File

@ -0,0 +1,456 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Protobuf module documentation</title>
<link rel="stylesheet" href="static/mystyles.css" />
<script src="static/fontawesome.js"></script>
<script src="static/lunr.js"></script>
<script src="static/theme.js"></script>
</head>
<body>
<div class="container">
<nav class="level pt-6 mb-5 pb-2 bottom-border">
<div class="level-left">
<p class="level-item has-text-centered is-size-3 has-text-weight-medium">
<a class="link is-info" href="index.html">
Protobuf module documentation
</a>
</p>
</div>
<div class="level-right">
<form action="search.html" class="mr-2 has-text-centered">
<input type="text" name="query" class="input mr-3 navbar-search-query-input" placeholder="Search">
<input type="submit" class="button is-primary mr-5" value="Search" />
</form>
<p class="level-item has-text-centered is-size-5">
<a href="" class="link is-info mr-5" target="_blank">
<span>Source</span>
</a>
<button class="theme-toggle" id="theme-toggle" title="Toggles light & dark" aria-label="auto" aria-live="polite" type="button">
<svg class="sun-and-moon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<mask class="moon" id="moon-mask">
<rect x="0" y="0" width="100%" height="100%" fill="white" />
<circle cx="24" cy="10" r="6" fill="black" />
</mask>
<circle class="sun" cx="12" cy="12" r="6" mask="url(#moon-mask)" fill="currentColor" />
<g class="sun-beams" stroke="currentColor">
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</g>
</svg>
</button>
</p>
</div>
</nav>
</div>
<div id="main-content" class="container mb-6">
<p class="mb-2">
<a style="font-size: 0.9em" href="index.html">← Back to packages</a>
</p>
<h3 class="title is-3">
Package <code>org.signal.chat.challenge</code>
</h3>
<div class="block">
<p>
<a href="#service-org.signal.chat.challenge.Challenge">
<span class="tag" style="min-width: 6em">Service</span>
<code>Challenge</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.challenge.AnswerChallengeRequest.AnswerCaptchaChallengeRequest">
<span class="tag" style="min-width: 6em">Message</span>
<code>AnswerCaptchaChallengeRequest</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.challenge.AnswerChallengeRequest">
<span class="tag" style="min-width: 6em">Message</span>
<code>AnswerChallengeRequest</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.challenge.AnswerChallengeResponse">
<span class="tag" style="min-width: 6em">Message</span>
<code>AnswerChallengeResponse</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.challenge.AnswerChallengeRequest.AnswerPushChallengeRequest">
<span class="tag" style="min-width: 6em">Message</span>
<code>AnswerPushChallengeRequest</code>
</a>
</p>
</div>
<div class="block">
</div>
<div class="block">
<h3 class="title is-3">
Services
</h3>
<div>
<h4 class="title is-4 type-heading" id="service-org.signal.chat.challenge.Challenge">
<a href="#service-org.signal.chat.challenge.Challenge" title="org.signal.chat.challenge.Challenge">service Challenge</a>
</h4>
<div class="block content">
<p></p>
</div>
<div class="box">
<div class="block">
<h5 class="title is-5" id="service-org.signal.chat.challenge.Challenge-HandleChallengeResponse">
<a href="#service-org.signal.chat.challenge.Challenge-HandleChallengeResponse">
rpc <code>HandleChallengeResponse</code>
</a>
</h5>
Request: <code><a href="org.signal.chat.challenge.html#message-org.signal.chat.challenge.AnswerChallengeRequest"><span title="org.signal.chat.challenge.AnswerChallengeRequest">org.signal.chat.challenge.AnswerChallengeRequest</span>
</a></code><br>
Response: <code><a href="org.signal.chat.challenge.html#message-org.signal.chat.challenge.AnswerChallengeResponse"><span title="org.signal.chat.challenge.AnswerChallengeResponse">org.signal.chat.challenge.AnswerChallengeResponse</span>
</a></code><br>
</div>
<div class="block">
<p><p>Some server endpoints (the "send message" endpoint, for example) may return
a response indicating the client must complete a challenge before continuing.
Clients may use this endpoint to provide proof of a completed challenge.
If successful, the client may then continue their original operation.</p></p>
</div>
</div>
</div>
</div>
<h3 class="title is-3">
Messages
</h3>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.challenge.AnswerChallengeRequest.AnswerCaptchaChallengeRequest">
<span>
<a href="#message-org.signal.chat.challenge.AnswerChallengeRequest.AnswerCaptchaChallengeRequest" title="org.signal.chat.challenge.AnswerChallengeRequest.AnswerCaptchaChallengeRequest">message AnswerCaptchaChallengeRequest</a>
<br /><span style="font-size: 0.7em; font-weight: normal">(Nested in <a href="org.signal.chat.challenge.html#message-org.signal.chat.challenge.AnswerChallengeRequest"><span title="org.signal.chat.challenge.AnswerChallengeRequest">org.signal.chat.challenge.AnswerChallengeRequest</span>
</a>)</span>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
captcha
</td>
<td>
<code title="string">
<span>string</span>
</code>
</td>
<td class="content"><p>A string representing a solved captcha
Example: signal-hcaptcha.30b01b46-d8c9-4c30-bbd7-9719acfe0c10.challenge.abcdefg1345</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.challenge.AnswerChallengeRequest">
<span>
<a href="#message-org.signal.chat.challenge.AnswerChallengeRequest" title="org.signal.chat.challenge.AnswerChallengeRequest">message AnswerChallengeRequest</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
token
</td>
<td>
<code title="string">
<span>string</span>
</code>
</td>
<td class="content"><p>The opaque token id from the ChallengeRequired response returned by the
server endpoint that requested the challenge</p></td>
</tr>
<tr class="nohover"><td style="border: 0px"></td></tr>
<tr class="nohover">
<td class="oneof-heading" colspan="4">
<div class="oneof-heading">
<strong>oneof request</strong>
</div>
</td>
</tr>
<tr>
<td>
2
</td>
<td>
push
</td>
<td>
<code title="org.signal.chat.challenge.AnswerChallengeRequest.AnswerPushChallengeRequest">
<a href="org.signal.chat.challenge.html#message-org.signal.chat.challenge.AnswerChallengeRequest.AnswerPushChallengeRequest"><span title="org.signal.chat.challenge.AnswerChallengeRequest.AnswerPushChallengeRequest">org.signal.chat.challenge.AnswerChallengeRequest.AnswerPushChallengeRequest</span>
</a>
</code>
</td>
<td class="content"></td>
</tr>
<tr>
<td>
3
</td>
<td>
captcha
</td>
<td>
<code title="org.signal.chat.challenge.AnswerChallengeRequest.AnswerCaptchaChallengeRequest">
<a href="org.signal.chat.challenge.html#message-org.signal.chat.challenge.AnswerChallengeRequest.AnswerCaptchaChallengeRequest"><span title="org.signal.chat.challenge.AnswerChallengeRequest.AnswerCaptchaChallengeRequest">org.signal.chat.challenge.AnswerChallengeRequest.AnswerCaptchaChallengeRequest</span>
</a>
</code>
</td>
<td class="content"></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.challenge.AnswerChallengeResponse">
<span>
<a href="#message-org.signal.chat.challenge.AnswerChallengeResponse" title="org.signal.chat.challenge.AnswerChallengeResponse">message AnswerChallengeResponse</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
success
</td>
<td>
<code title="bool">
<span>bool</span>
</code>
</td>
<td class="content"><p>Whether the challenge proof was accepted</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.challenge.AnswerChallengeRequest.AnswerPushChallengeRequest">
<span>
<a href="#message-org.signal.chat.challenge.AnswerChallengeRequest.AnswerPushChallengeRequest" title="org.signal.chat.challenge.AnswerChallengeRequest.AnswerPushChallengeRequest">message AnswerPushChallengeRequest</a>
<br /><span style="font-size: 0.7em; font-weight: normal">(Nested in <a href="org.signal.chat.challenge.html#message-org.signal.chat.challenge.AnswerChallengeRequest"><span title="org.signal.chat.challenge.AnswerChallengeRequest">org.signal.chat.challenge.AnswerChallengeRequest</span>
</a>)</span>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
challenge
</td>
<td>
<code title="string">
<span>string</span>
</code>
</td>
<td class="content"><p>The challenge string provided to the client via a push payload</p></td>
</tr>
</tbody>
</table>
</div>
</div>
<footer class="footer">
<div class="content has-text-centered">
<p>Built with <strong><a href="https://github.com/markvincze/sabledocs" target="_blank">sabledocs</a></strong>.</p>
</div>
</footer>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,503 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Protobuf module documentation</title>
<link rel="stylesheet" href="static/mystyles.css" />
<script src="static/fontawesome.js"></script>
<script src="static/lunr.js"></script>
<script src="static/theme.js"></script>
</head>
<body>
<div class="container">
<nav class="level pt-6 mb-5 pb-2 bottom-border">
<div class="level-left">
<p class="level-item has-text-centered is-size-3 has-text-weight-medium">
<a class="link is-info" href="index.html">
Protobuf module documentation
</a>
</p>
</div>
<div class="level-right">
<form action="search.html" class="mr-2 has-text-centered">
<input type="text" name="query" class="input mr-3 navbar-search-query-input" placeholder="Search">
<input type="submit" class="button is-primary mr-5" value="Search" />
</form>
<p class="level-item has-text-centered is-size-5">
<a href="" class="link is-info mr-5" target="_blank">
<span>Source</span>
</a>
<button class="theme-toggle" id="theme-toggle" title="Toggles light & dark" aria-label="auto" aria-live="polite" type="button">
<svg class="sun-and-moon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<mask class="moon" id="moon-mask">
<rect x="0" y="0" width="100%" height="100%" fill="white" />
<circle cx="24" cy="10" r="6" fill="black" />
</mask>
<circle class="sun" cx="12" cy="12" r="6" mask="url(#moon-mask)" fill="currentColor" />
<g class="sun-beams" stroke="currentColor">
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</g>
</svg>
</button>
</p>
</div>
</nav>
</div>
<div id="main-content" class="container mb-6">
<p class="mb-2">
<a style="font-size: 0.9em" href="index.html">← Back to packages</a>
</p>
<h3 class="title is-3">
Package <code>org.signal.chat.donations</code>
</h3>
<div class="block">
<p>
<a href="#service-org.signal.chat.donations.Donations">
<span class="tag" style="min-width: 6em">Service</span>
<code>Donations</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.donations.CreateDonationPermitRequest">
<span class="tag" style="min-width: 6em">Message</span>
<code>CreateDonationPermitRequest</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.donations.CreateDonationPermitResponse">
<span class="tag" style="min-width: 6em">Message</span>
<code>CreateDonationPermitResponse</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.donations.RedeemReceiptRequest">
<span class="tag" style="min-width: 6em">Message</span>
<code>RedeemReceiptRequest</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.donations.RedeemReceiptResponse">
<span class="tag" style="min-width: 6em">Message</span>
<code>RedeemReceiptResponse</code>
</a>
</p>
</div>
<div class="block">
</div>
<div class="block">
<h3 class="title is-3">
Services
</h3>
<div>
<h4 class="title is-4 type-heading" id="service-org.signal.chat.donations.Donations">
<a href="#service-org.signal.chat.donations.Donations" title="org.signal.chat.donations.Donations">service Donations</a>
</h4>
<div class="block content">
<p></p>
</div>
<div class="box">
<div class="block">
<h5 class="title is-5" id="service-org.signal.chat.donations.Donations-RedeemReceipt">
<a href="#service-org.signal.chat.donations.Donations-RedeemReceipt">
rpc <code>RedeemReceipt</code>
</a>
</h5>
Request: <code><a href="org.signal.chat.donations.html#message-org.signal.chat.donations.RedeemReceiptRequest"><span title="org.signal.chat.donations.RedeemReceiptRequest">org.signal.chat.donations.RedeemReceiptRequest</span>
</a></code><br>
Response: <code><a href="org.signal.chat.donations.html#message-org.signal.chat.donations.RedeemReceiptResponse"><span title="org.signal.chat.donations.RedeemReceiptResponse">org.signal.chat.donations.RedeemReceiptResponse</span>
</a></code><br>
</div>
<div class="block">
<p><p>Redeem a receipt acquired from Subscriptions.CreateSubscriptionReceiptCredentials
to add a badge to the account. After successful redemption, profile
responses will include the corresponding badge (if configured as visible)
until the expiration time on the receipt.</p></p>
</div>
</div>
<div class="box">
<div class="block">
<h5 class="title is-5" id="service-org.signal.chat.donations.Donations-CreateDonationPermit">
<a href="#service-org.signal.chat.donations.Donations-CreateDonationPermit">
rpc <code>CreateDonationPermit</code>
</a>
</h5>
Request: <code><a href="org.signal.chat.donations.html#message-org.signal.chat.donations.CreateDonationPermitRequest"><span title="org.signal.chat.donations.CreateDonationPermitRequest">org.signal.chat.donations.CreateDonationPermitRequest</span>
</a></code><br>
Response: <code><a href="org.signal.chat.donations.html#message-org.signal.chat.donations.CreateDonationPermitResponse"><span title="org.signal.chat.donations.CreateDonationPermitResponse">org.signal.chat.donations.CreateDonationPermitResponse</span>
</a></code><br>
</div>
<div class="block">
<p><p>Generate a set of anonymous, single-use, permits for use with /v1/subscription endpoints.</p>
<p>If rate limited, reduce requested permit count and/or try again after the prescribed delay.</p></p>
</div>
</div>
</div>
</div>
<h3 class="title is-3">
Messages
</h3>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.donations.CreateDonationPermitRequest">
<span>
<a href="#message-org.signal.chat.donations.CreateDonationPermitRequest" title="org.signal.chat.donations.CreateDonationPermitRequest">message CreateDonationPermitRequest</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
donation_permit_request
</td>
<td>
<code title="bytes">
<span>bytes</span>
</code>
</td>
<td class="content"><p>a serialized libsignal DonationPermitRequest</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.donations.CreateDonationPermitResponse">
<span>
<a href="#message-org.signal.chat.donations.CreateDonationPermitResponse" title="org.signal.chat.donations.CreateDonationPermitResponse">message CreateDonationPermitResponse</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
donation_permit_response
</td>
<td>
<code title="bytes">
<span>bytes</span>
</code>
</td>
<td class="content"><p>a serialized libsignal DonationPermitResponse</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.donations.RedeemReceiptRequest">
<span>
<a href="#message-org.signal.chat.donations.RedeemReceiptRequest" title="org.signal.chat.donations.RedeemReceiptRequest">message RedeemReceiptRequest</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
receiptCredentialPresentation
</td>
<td>
<code title="bytes">
<span>bytes</span>
</code>
</td>
<td class="content"><p>Presentation of the ZK receipt acquired when the subscription was created</p></td>
</tr>
<tr>
<td>2</td>
<td>
visible
</td>
<td>
<code title="bool">
<span>bool</span>
</code>
</td>
<td class="content"><p>If true, the corresponding badge should be visible on the profile</p></td>
</tr>
<tr>
<td>3</td>
<td>
primary
</td>
<td>
<code title="bool">
<span>bool</span>
</code>
</td>
<td class="content"><p>If true, and the new badge is visible, it should be the primary badge on the profile</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.donations.RedeemReceiptResponse">
<span>
<a href="#message-org.signal.chat.donations.RedeemReceiptResponse" title="org.signal.chat.donations.RedeemReceiptResponse">message RedeemReceiptResponse</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="nohover"><td style="border: 0px"></td></tr>
<tr class="nohover">
<td class="oneof-heading" colspan="4">
<div class="oneof-heading">
<strong>oneof response</strong>
</div>
</td>
</tr>
<tr>
<td>
1
</td>
<td>
success
</td>
<td>
<code title="google.protobuf.Empty">
<span>google.protobuf.Empty</span>
</code>
</td>
<td class="content"><p>The receipt was successfully redeemed</p></td>
</tr>
<tr>
<td>
2
</td>
<td>
failed_authentication
</td>
<td>
<code title="org.signal.chat.errors.FailedZkAuthentication">
<a href="org.signal.chat.errors.html#message-org.signal.chat.errors.FailedZkAuthentication"><span title="org.signal.chat.errors.FailedZkAuthentication">org.signal.chat.errors.FailedZkAuthentication</span>
</a>
</code>
</td>
<td class="content"><p>The provided presentation is invalid</p></td>
</tr>
<tr>
<td>
3
</td>
<td>
already_redeemed
</td>
<td>
<code title="org.signal.chat.errors.FailedPrecondition">
<a href="org.signal.chat.errors.html#message-org.signal.chat.errors.FailedPrecondition"><span title="org.signal.chat.errors.FailedPrecondition">org.signal.chat.errors.FailedPrecondition</span>
</a>
</code>
</td>
<td class="content"><p>The receipt was already redeemed for a different account</p></td>
</tr>
</tbody>
</table>
</div>
</div>
<footer class="footer">
<div class="content has-text-centered">
<p>Built with <strong><a href="https://github.com/markvincze/sabledocs" target="_blank">sabledocs</a></strong>.</p>
</div>
</footer>
</body>
</html>

View File

@ -0,0 +1,330 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Protobuf module documentation</title>
<link rel="stylesheet" href="static/mystyles.css" />
<script src="static/fontawesome.js"></script>
<script src="static/lunr.js"></script>
<script src="static/theme.js"></script>
</head>
<body>
<div class="container">
<nav class="level pt-6 mb-5 pb-2 bottom-border">
<div class="level-left">
<p class="level-item has-text-centered is-size-3 has-text-weight-medium">
<a class="link is-info" href="index.html">
Protobuf module documentation
</a>
</p>
</div>
<div class="level-right">
<form action="search.html" class="mr-2 has-text-centered">
<input type="text" name="query" class="input mr-3 navbar-search-query-input" placeholder="Search">
<input type="submit" class="button is-primary mr-5" value="Search" />
</form>
<p class="level-item has-text-centered is-size-5">
<a href="" class="link is-info mr-5" target="_blank">
<span>Source</span>
</a>
<button class="theme-toggle" id="theme-toggle" title="Toggles light & dark" aria-label="auto" aria-live="polite" type="button">
<svg class="sun-and-moon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<mask class="moon" id="moon-mask">
<rect x="0" y="0" width="100%" height="100%" fill="white" />
<circle cx="24" cy="10" r="6" fill="black" />
</mask>
<circle class="sun" cx="12" cy="12" r="6" mask="url(#moon-mask)" fill="currentColor" />
<g class="sun-beams" stroke="currentColor">
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</g>
</svg>
</button>
</p>
</div>
</nav>
</div>
<div id="main-content" class="container mb-6">
<p class="mb-2">
<a style="font-size: 0.9em" href="index.html">← Back to packages</a>
</p>
<h3 class="title is-3">
Package <code>org.signal.chat.errors</code>
</h3>
<div class="block">
<p>
<a href="#message-org.signal.chat.errors.FailedPrecondition">
<span class="tag" style="min-width: 6em">Message</span>
<code>FailedPrecondition</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.errors.FailedUnidentifiedAuthorization">
<span class="tag" style="min-width: 6em">Message</span>
<code>FailedUnidentifiedAuthorization</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.errors.FailedZkAuthentication">
<span class="tag" style="min-width: 6em">Message</span>
<code>FailedZkAuthentication</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.errors.NotFound">
<span class="tag" style="min-width: 6em">Message</span>
<code>NotFound</code>
</a>
</p>
</div>
<div class="block">
</div>
<h3 class="title is-3">
Messages
</h3>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.errors.FailedPrecondition">
<span>
<a href="#message-org.signal.chat.errors.FailedPrecondition" title="org.signal.chat.errors.FailedPrecondition">message FailedPrecondition</a>
</span>
</h4>
<div class="block content">
<p>Response message that indicates that some precondition of the request was not
met. For example, if there was a request to update foo, but foo had not been
set, this would be an appropriate error.</p>
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
description
</td>
<td>
<code title="string">
<span>string</span>
</code>
</td>
<td class="content"><p>An optional description indicating what precondition failed.</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.errors.FailedUnidentifiedAuthorization">
<span>
<a href="#message-org.signal.chat.errors.FailedUnidentifiedAuthorization" title="org.signal.chat.errors.FailedUnidentifiedAuthorization">message FailedUnidentifiedAuthorization</a>
</span>
</h4>
<div class="block content">
<p>Response message that indicates authorization to perform an unidentified
operation via an endorsement or access key failed</p>
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
description
</td>
<td>
<code title="string">
<span>string</span>
</code>
</td>
<td class="content"><p>An optional description with additional information about the failure.</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.errors.FailedZkAuthentication">
<span>
<a href="#message-org.signal.chat.errors.FailedZkAuthentication" title="org.signal.chat.errors.FailedZkAuthentication">message FailedZkAuthentication</a>
</span>
</h4>
<div class="block content">
<p>Response message that authentication via an anonymous credential failed.</p>
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
description
</td>
<td>
<code title="string">
<span>string</span>
</code>
</td>
<td class="content"><p>An optional description with additional information about the failure.</p></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.errors.NotFound">
<span>
<a href="#message-org.signal.chat.errors.NotFound" title="org.signal.chat.errors.NotFound">message NotFound</a>
</span>
</h4>
<div class="block content">
<p>Response message that indicates a particular resource was not found.</p>
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<footer class="footer">
<div class="content has-text-centered">
<p>Built with <strong><a href="https://github.com/markvincze/sabledocs" target="_blank">sabledocs</a></strong>.</p>
</div>
</footer>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,356 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Protobuf module documentation</title>
<link rel="stylesheet" href="static/mystyles.css" />
<script src="static/fontawesome.js"></script>
<script src="static/lunr.js"></script>
<script src="static/theme.js"></script>
</head>
<body>
<div class="container">
<nav class="level pt-6 mb-5 pb-2 bottom-border">
<div class="level-left">
<p class="level-item has-text-centered is-size-3 has-text-weight-medium">
<a class="link is-info" href="index.html">
Protobuf module documentation
</a>
</p>
</div>
<div class="level-right">
<form action="search.html" class="mr-2 has-text-centered">
<input type="text" name="query" class="input mr-3 navbar-search-query-input" placeholder="Search">
<input type="submit" class="button is-primary mr-5" value="Search" />
</form>
<p class="level-item has-text-centered is-size-5">
<a href="" class="link is-info mr-5" target="_blank">
<span>Source</span>
</a>
<button class="theme-toggle" id="theme-toggle" title="Toggles light & dark" aria-label="auto" aria-live="polite" type="button">
<svg class="sun-and-moon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<mask class="moon" id="moon-mask">
<rect x="0" y="0" width="100%" height="100%" fill="white" />
<circle cx="24" cy="10" r="6" fill="black" />
</mask>
<circle class="sun" cx="12" cy="12" r="6" mask="url(#moon-mask)" fill="currentColor" />
<g class="sun-beams" stroke="currentColor">
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</g>
</svg>
</button>
</p>
</div>
</nav>
</div>
<div id="main-content" class="container mb-6">
<p class="mb-2">
<a style="font-size: 0.9em" href="index.html">← Back to packages</a>
</p>
<h3 class="title is-3">
Package <code>org.signal.chat.payments</code>
</h3>
<div class="block">
<p>
<a href="#service-org.signal.chat.payments.Payments">
<span class="tag" style="min-width: 6em">Service</span>
<code>Payments</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.payments.GetCurrencyConversionsResponse.CurrencyConversionEntity">
<span class="tag" style="min-width: 6em">Message</span>
<code>CurrencyConversionEntity</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.payments.GetCurrencyConversionsRequest">
<span class="tag" style="min-width: 6em">Message</span>
<code>GetCurrencyConversionsRequest</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.payments.GetCurrencyConversionsResponse">
<span class="tag" style="min-width: 6em">Message</span>
<code>GetCurrencyConversionsResponse</code>
</a>
</p>
</div>
<div class="block">
</div>
<div class="block">
<h3 class="title is-3">
Services
</h3>
<div>
<h4 class="title is-4 type-heading" id="service-org.signal.chat.payments.Payments">
<a href="#service-org.signal.chat.payments.Payments" title="org.signal.chat.payments.Payments">service Payments</a>
</h4>
<div class="block content">
<p><p>Provides methods for working with payments.</p></p>
</div>
<div class="box">
<div class="block">
<h5 class="title is-5" id="service-org.signal.chat.payments.Payments-GetCurrencyConversions">
<a href="#service-org.signal.chat.payments.Payments-GetCurrencyConversions">
rpc <code>GetCurrencyConversions</code>
</a>
</h5>
Request: <code><a href="org.signal.chat.payments.html#message-org.signal.chat.payments.GetCurrencyConversionsRequest"><span title="org.signal.chat.payments.GetCurrencyConversionsRequest">org.signal.chat.payments.GetCurrencyConversionsRequest</span>
</a></code><br>
Response: <code><a href="org.signal.chat.payments.html#message-org.signal.chat.payments.GetCurrencyConversionsResponse"><span title="org.signal.chat.payments.GetCurrencyConversionsResponse">org.signal.chat.payments.GetCurrencyConversionsResponse</span>
</a></code><br>
</div>
</div>
</div>
</div>
<h3 class="title is-3">
Messages
</h3>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.payments.GetCurrencyConversionsResponse.CurrencyConversionEntity">
<span>
<a href="#message-org.signal.chat.payments.GetCurrencyConversionsResponse.CurrencyConversionEntity" title="org.signal.chat.payments.GetCurrencyConversionsResponse.CurrencyConversionEntity">message CurrencyConversionEntity</a>
<br /><span style="font-size: 0.7em; font-weight: normal">(Nested in <a href="org.signal.chat.payments.html#message-org.signal.chat.payments.GetCurrencyConversionsResponse"><span title="org.signal.chat.payments.GetCurrencyConversionsResponse">org.signal.chat.payments.GetCurrencyConversionsResponse</span>
</a>)</span>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
base
</td>
<td>
<code title="string">
<span>string</span>
</code>
</td>
<td class="content"></td>
</tr>
<tr>
<td>2</td>
<td>
conversions
</td>
<td>
<code title="map&lt;string, string&gt;">
<span>map&lt;string, string&gt;</span>
</code>
</td>
<td class="content"></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.payments.GetCurrencyConversionsRequest">
<span>
<a href="#message-org.signal.chat.payments.GetCurrencyConversionsRequest" title="org.signal.chat.payments.GetCurrencyConversionsRequest">message GetCurrencyConversionsRequest</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.payments.GetCurrencyConversionsResponse">
<span>
<a href="#message-org.signal.chat.payments.GetCurrencyConversionsResponse" title="org.signal.chat.payments.GetCurrencyConversionsResponse">message GetCurrencyConversionsResponse</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
timestamp
</td>
<td>
<code title="uint64">
<span>uint64</span>
</code>
</td>
<td class="content"></td>
</tr>
<tr>
<td>2</td>
<td>
currencies
</td>
<td>
<code title="org.signal.chat.payments.GetCurrencyConversionsResponse.CurrencyConversionEntity">
repeated
<a href="org.signal.chat.payments.html#message-org.signal.chat.payments.GetCurrencyConversionsResponse.CurrencyConversionEntity"><span title="org.signal.chat.payments.GetCurrencyConversionsResponse.CurrencyConversionEntity">org.signal.chat.payments.GetCurrencyConversionsResponse.CurrencyConversionEntity</span>
</a>
</code>
</td>
<td class="content"></td>
</tr>
</tbody>
</table>
</div>
</div>
<footer class="footer">
<div class="content has-text-centered">
<p>Built with <strong><a href="https://github.com/markvincze/sabledocs" target="_blank">sabledocs</a></strong>.</p>
</div>
</footer>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,555 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Protobuf module documentation</title>
<link rel="stylesheet" href="static/mystyles.css" />
<script src="static/fontawesome.js"></script>
<script src="static/lunr.js"></script>
<script src="static/theme.js"></script>
</head>
<body>
<div class="container">
<nav class="level pt-6 mb-5 pb-2 bottom-border">
<div class="level-left">
<p class="level-item has-text-centered is-size-3 has-text-weight-medium">
<a class="link is-info" href="index.html">
Protobuf module documentation
</a>
</p>
</div>
<div class="level-right">
<form action="search.html" class="mr-2 has-text-centered">
<input type="text" name="query" class="input mr-3 navbar-search-query-input" placeholder="Search">
<input type="submit" class="button is-primary mr-5" value="Search" />
</form>
<p class="level-item has-text-centered is-size-5">
<a href="" class="link is-info mr-5" target="_blank">
<span>Source</span>
</a>
<button class="theme-toggle" id="theme-toggle" title="Toggles light & dark" aria-label="auto" aria-live="polite" type="button">
<svg class="sun-and-moon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<mask class="moon" id="moon-mask">
<rect x="0" y="0" width="100%" height="100%" fill="white" />
<circle cx="24" cy="10" r="6" fill="black" />
</mask>
<circle class="sun" cx="12" cy="12" r="6" mask="url(#moon-mask)" fill="currentColor" />
<g class="sun-beams" stroke="currentColor">
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</g>
</svg>
</button>
</p>
</div>
</nav>
</div>
<div id="main-content" class="container mb-6">
<p class="mb-2">
<a style="font-size: 0.9em" href="index.html">← Back to packages</a>
</p>
<h3 class="title is-3">
Package <code>org.signal.chat.require</code>
</h3>
<div class="block">
<p>
<a href="#message-org.signal.chat.require.ElementConstraint">
<span class="tag" style="min-width: 6em">Message</span>
<code>ElementConstraint</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.require.SizeConstraint">
<span class="tag" style="min-width: 6em">Message</span>
<code>SizeConstraint</code>
</a>
</p>
<p>
<a href="#message-org.signal.chat.require.ValueRangeConstraint">
<span class="tag" style="min-width: 6em">Message</span>
<code>ValueRangeConstraint</code>
</a>
</p>
<p>
<a href="#enum-org.signal.chat.require.Auth">
<span class="tag" style="min-width: 6em">Enum</span>
<code>Auth</code>
</a>
</p>
<p>
<a href="#enum-org.signal.chat.require.IdentityType">
<span class="tag" style="min-width: 6em">Enum</span>
<code>IdentityType</code>
</a>
</p>
</div>
<div class="block">
</div>
<h3 class="title is-3">
Messages
</h3>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.require.ElementConstraint">
<span>
<a href="#message-org.signal.chat.require.ElementConstraint" title="org.signal.chat.require.ElementConstraint">message ElementConstraint</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
nonEmpty
</td>
<td>
<code title="bool">
optional
<span>bool</span>
</code>
</td>
<td class="content"></td>
</tr>
<tr>
<td>2</td>
<td>
size
</td>
<td>
<code title="org.signal.chat.require.SizeConstraint">
optional
<a href="org.signal.chat.require.html#message-org.signal.chat.require.SizeConstraint"><span title="org.signal.chat.require.SizeConstraint">org.signal.chat.require.SizeConstraint</span>
</a>
</code>
</td>
<td class="content"></td>
</tr>
<tr>
<td>3</td>
<td>
exactlySize
</td>
<td>
<code title="uint32">
repeated
<span>uint32</span>
</code>
</td>
<td class="content"></td>
</tr>
<tr>
<td>4</td>
<td>
e164
</td>
<td>
<code title="bool">
optional
<span>bool</span>
</code>
</td>
<td class="content"></td>
</tr>
<tr>
<td>5</td>
<td>
base64url
</td>
<td>
<code title="bool">
optional
<span>bool</span>
</code>
</td>
<td class="content"></td>
</tr>
<tr>
<td>6</td>
<td>
range
</td>
<td>
<code title="org.signal.chat.require.ValueRangeConstraint">
optional
<a href="org.signal.chat.require.html#message-org.signal.chat.require.ValueRangeConstraint"><span title="org.signal.chat.require.ValueRangeConstraint">org.signal.chat.require.ValueRangeConstraint</span>
</a>
</code>
</td>
<td class="content"></td>
</tr>
<tr>
<td>7</td>
<td>
identityType
</td>
<td>
<code title="org.signal.chat.require.IdentityType">
optional
<a href="org.signal.chat.require.html#enum-org.signal.chat.require.IdentityType"><span title="org.signal.chat.require.IdentityType">org.signal.chat.require.IdentityType</span>
</a>
</code>
</td>
<td class="content"></td>
</tr>
<tr>
<td>8</td>
<td>
specified
</td>
<td>
<code title="bool">
optional
<span>bool</span>
</code>
</td>
<td class="content"></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.require.SizeConstraint">
<span>
<a href="#message-org.signal.chat.require.SizeConstraint" title="org.signal.chat.require.SizeConstraint">message SizeConstraint</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
min
</td>
<td>
<code title="uint32">
optional
<span>uint32</span>
</code>
</td>
<td class="content"></td>
</tr>
<tr>
<td>2</td>
<td>
max
</td>
<td>
<code title="uint32">
optional
<span>uint32</span>
</code>
</td>
<td class="content"></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="message-org.signal.chat.require.ValueRangeConstraint">
<span>
<a href="#message-org.signal.chat.require.ValueRangeConstraint" title="org.signal.chat.require.ValueRangeConstraint">message ValueRangeConstraint</a>
</span>
</h4>
<div class="block content">
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th></th>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
min
</td>
<td>
<code title="int64">
optional
<span>int64</span>
</code>
</td>
<td class="content"></td>
</tr>
<tr>
<td>2</td>
<td>
max
</td>
<td>
<code title="int64">
optional
<span>int64</span>
</code>
</td>
<td class="content"></td>
</tr>
</tbody>
</table>
</div>
<h3 class="title is-3">
Enums
</h3>
<div class="block">
<h4 class="title is-4 type-heading" id="enum-org.signal.chat.require.Auth">
<a href="#enum-org.signal.chat.require.Auth" title="org.signal.chat.require.Auth">enum Auth</a>
</h4>
<div class="block content">
<p></p>
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th>Name</th>
<th>Number</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AUTH_UNSPECIFIED</code></td>
<td>0</td>
<td class="content"></td>
</tr>
<tr>
<td><code>AUTH_ONLY_AUTHENTICATED</code></td>
<td>1</td>
<td class="content"></td>
</tr>
<tr>
<td><code>AUTH_ONLY_ANONYMOUS</code></td>
<td>2</td>
<td class="content"></td>
</tr>
</tbody>
</table>
</div>
<div class="block">
<h4 class="title is-4 type-heading" id="enum-org.signal.chat.require.IdentityType">
<a href="#enum-org.signal.chat.require.IdentityType" title="org.signal.chat.require.IdentityType">enum IdentityType</a>
</h4>
<div class="block content">
<p><p>This is duplicated from common.proto because:</p>
<ol>
<li>importing would be a circular dependency</li>
<li>the canonical declaration belongs there</li>
</ol></p>
</div>
<table class="table is-fullwidth is-hoverable is-bordered">
<thead>
<tr>
<th>Name</th>
<th>Number</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>IDENTITY_TYPE_UNSPECIFIED</code></td>
<td>0</td>
<td class="content"></td>
</tr>
<tr>
<td><code>IDENTITY_TYPE_ACI</code></td>
<td>1</td>
<td class="content"></td>
</tr>
<tr>
<td><code>IDENTITY_TYPE_PNI</code></td>
<td>2</td>
<td class="content"></td>
</tr>
</tbody>
</table>
</div>
</div>
<footer class="footer">
<div class="content has-text-centered">
<p>Built with <strong><a href="https://github.com/markvincze/sabledocs" target="_blank">sabledocs</a></strong>.</p>
</div>
</footer>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,98 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Protobuf module documentation</title>
<link rel="stylesheet" href="static/mystyles.css" />
<script src="static/fontawesome.js"></script>
<script src="static/lunr.js"></script>
<script src="static/theme.js"></script>
</head>
<body>
<div class="container">
<nav class="level pt-6 mb-5 pb-2 bottom-border">
<div class="level-left">
<p class="level-item has-text-centered is-size-3 has-text-weight-medium">
<a class="link is-info" href="index.html">
Protobuf module documentation
</a>
</p>
</div>
<div class="level-right">
<form action="search.html" class="mr-2 has-text-centered">
<input type="text" name="query" class="input mr-3 navbar-search-query-input" placeholder="Search">
<input type="submit" class="button is-primary mr-5" value="Search" />
</form>
<p class="level-item has-text-centered is-size-5">
<a href="" class="link is-info mr-5" target="_blank">
<span>Source</span>
</a>
<button class="theme-toggle" id="theme-toggle" title="Toggles light & dark" aria-label="auto" aria-live="polite" type="button">
<svg class="sun-and-moon" aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<mask class="moon" id="moon-mask">
<rect x="0" y="0" width="100%" height="100%" fill="white" />
<circle cx="24" cy="10" r="6" fill="black" />
</mask>
<circle class="sun" cx="12" cy="12" r="6" mask="url(#moon-mask)" fill="currentColor" />
<g class="sun-beams" stroke="currentColor">
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</g>
</svg>
</button>
</p>
</div>
</nav>
</div>
<div id="main-content" class="container mb-6">
<p class="mb-2">
<a style="font-size: 0.9em" href="index.html">← Back to packages</a>
</p>
<h3 class="title is-3">
Package <code>org.signal.chat.tag</code>
</h3>
<div class="block">
</div>
<div class="block">
</div>
</div>
<footer class="footer">
<div class="content has-text-centered">
<p>Built with <strong><a href="https://github.com/markvincze/sabledocs" target="_blank">sabledocs</a></strong>.</p>
</div>
</footer>
</body>
</html>

144
grpc/search.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3475
grpc/static/lunr.js Normal file

File diff suppressed because it is too large Load Diff

8083
grpc/static/mystyles.css Normal file

File diff suppressed because it is too large Load Diff

54
grpc/static/theme.js Normal file
View File

@ -0,0 +1,54 @@
const storageKey = 'theme-preference';
const onClick = () => {
// flip current value
theme.value = theme.value === 'light'
? 'dark'
: 'light';
setPreference();
}
const getColorPreference = () => {
if (localStorage.getItem(storageKey))
return localStorage.getItem(storageKey);
else
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
}
const setPreference = () => {
localStorage.setItem(storageKey, theme.value);
reflectPreference();
}
const reflectPreference = () => {
document.documentElement.className =
theme.value === 'dark' ? 'dark' : '';
document.documentElement
.setAttribute('data-theme', theme.value);
}
const theme = {
value: getColorPreference(),
}
window.addEventListener(
"load",
() => {
document
.querySelector('#theme-toggle')
.addEventListener('click', onClick);
});
// sync with system changes
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', ({ matches: isDark }) => {
theme.value = isDark ? 'dark' : 'light';
setPreference();
});
reflectPreference();

20
index.html Normal file
View File

@ -0,0 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Signal Server API</title>
<script src="https://unpkg.com/@stoplight/elements/web-components.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@stoplight/elements/styles.min.css">
<link rel="icon" type="image/x-icon" href="/favicon.ico">
</head>
<body>
<elements-api
apiDescriptionUrl="signal-server-openapi.yaml"
router="hash"
/>
</body>
</html>

316
mvnw vendored
View File

@ -1,316 +0,0 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`\\unset -f command; \\command -v java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

188
mvnw.cmd vendored
View File

@ -1,188 +0,0 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%

511
pom.xml
View File

@ -1,511 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<repositories>
<repository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>dynamodb-local-oregon</id>
<name>DynamoDB Local Release Repository</name>
<url>https://s3-us-west-2.amazonaws.com/dynamodb-local/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>ossrh-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<modules>
<module>event-logger</module>
<module>redis-dispatch</module>
<module>websocket-resources</module>
<module>service</module>
</modules>
<properties>
<aws.sdk.version>1.12.376</aws.sdk.version>
<aws.sdk2.version>2.19.8</aws.sdk2.version>
<braintree.version>3.19.0</braintree.version>
<commons-csv.version>1.9.0</commons-csv.version>
<commons-io.version>2.9.0</commons-io.version>
<dropwizard.version>2.0.34</dropwizard.version>
<dropwizard-metrics-datadog.version>1.1.13</dropwizard-metrics-datadog.version>
<google-cloud-libraries.version>26.1.3</google-cloud-libraries.version>
<grpc.version>1.51.1</grpc.version> <!-- this should be kept in sync with the value from Googles libraries-bom -->
<gson.version>2.9.0</gson.version>
<jackson.version>2.13.4</jackson.version>
<jaxb.version>2.3.1</jaxb.version>
<jedis.version>2.9.0</jedis.version>
<kotlin.version>1.8.0</kotlin.version>
<kotlinx-serialization.version>1.4.1</kotlinx-serialization.version>
<lettuce.version>6.2.1.RELEASE</lettuce.version>
<libphonenumber.version>8.12.54</libphonenumber.version>
<logstash.logback.version>7.2</logstash.logback.version>
<micrometer.version>1.10.3</micrometer.version>
<mockito.version>4.11.0</mockito.version>
<netty.version>4.1.82.Final</netty.version>
<opentest4j.version>1.2.0</opentest4j.version>
<protobuf.version>3.21.7</protobuf.version>
<pushy.version>0.15.2</pushy.version>
<resilience4j.version>1.7.0</resilience4j.version>
<semver4j.version>3.1.0</semver4j.version>
<slf4j.version>1.7.30</slf4j.version>
<stripe.version>21.2.0</stripe.version>
<vavr.version>0.10.4</vavr.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>TextSecureServer</artifactId>
<version>JGITVER</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${jackson.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-dependencies</artifactId>
<version>${dropwizard.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Needed for gRPC with Java 9+ -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
<version>${netty.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-bom</artifactId>
<version>${aws.sdk.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>${aws.sdk2.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>libraries-bom</artifactId>
<version>${google-cloud-libraries.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bom</artifactId>
<version>${resilience4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-bom</artifactId>
<version>${micrometer.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>2020.0.24</version> <!-- 3.4.x, see https://github.com/reactor/reactor#bom-versioning-scheme -->
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-bom</artifactId>
<version>${kotlin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.eatthepath</groupId>
<artifactId>pushy</artifactId>
<version>${pushy.version}</version>
</dependency>
<dependency>
<groupId>com.eatthepath</groupId>
<artifactId>pushy-dropwizard-metrics-listener</artifactId>
<version>${pushy.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>com.googlecode.libphonenumber</groupId>
<artifactId>libphonenumber</artifactId>
<version>${libphonenumber.version}</version>
</dependency>
<dependency>
<groupId>com.vdurmont</groupId>
<artifactId>semver4j</artifactId>
<version>${semver4j.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>${lettuce.version}</version>
</dependency>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>${vavr.version}</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb.version}</version>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>${logstash.logback.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>${commons-csv.version}</version>
</dependency>
<dependency>
<groupId>org.coursera</groupId>
<artifactId>dropwizard-metrics-datadog</artifactId>
<version>${dropwizard-metrics-datadog.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>${jaxb.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.opentest4j</groupId>
<artifactId>opentest4j</artifactId>
<version>${opentest4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.stripe</groupId>
<artifactId>stripe-java</artifactId>
<version>${stripe.version}</version>
</dependency>
<dependency>
<groupId>com.braintreepayments.gateway</groupId>
<artifactId>braintree-java</artifactId>
<version>${braintree.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<dependency>
<groupId>org.signal</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.8.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.signal</groupId>
<artifactId>libsignal-server</artifactId>
<version>0.22.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>2.17.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.35.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
<exclusion>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>1.9.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>include-spam-filter</id>
<activation>
<file>
<exists>spam-filter/pom.xml</exists>
</file>
</activation>
<modules>
<module>spam-filter</module>
</modules>
</profile>
<profile>
<id>exclude-spam-filter</id>
<activation>
<file>
<missing>spam-filter/pom.xml</missing>
</file>
</activation>
</profile>
</profiles>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.0</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<checkStaleness>false</checkStaleness>
<protocArtifact>com.google.protobuf:protoc:3.21.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<id>copy</id>
<phase>test-compile</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeScope>test</includeScope>
<includeTypes>so,dll,dylib</includeTypes>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<systemProperties>
<property>
<name>sqlite4java.library.path</name>
<value>${project.build.directory}/lib</value>
</property>
</systemProperties>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M3</version>
<executions>
<execution>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<dependencyConvergence/>
<requireMavenVersion>
<version>3.8.6</version>
</requireMavenVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>3.0.0-M1</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.0.0-M1</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>TextSecureServer</artifactId>
<groupId>org.whispersystems.textsecure</groupId>
<version>JGITVER</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>redis-dispatch</artifactId>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,11 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch;
public interface DispatchChannel {
void onDispatchMessage(String channel, byte[] message);
void onDispatchSubscribed(String channel);
void onDispatchUnsubscribed(String channel);
}

View File

@ -1,157 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.dispatch.io.RedisPubSubConnectionFactory;
import org.whispersystems.dispatch.redis.PubSubConnection;
import org.whispersystems.dispatch.redis.PubSubReply;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class DispatchManager extends Thread {
private final Logger logger = LoggerFactory.getLogger(DispatchManager.class);
private final Executor executor = Executors.newCachedThreadPool();
private final Map<String, DispatchChannel> subscriptions = new ConcurrentHashMap<>();
private final Optional<DispatchChannel> deadLetterChannel;
private final RedisPubSubConnectionFactory redisPubSubConnectionFactory;
private PubSubConnection pubSubConnection;
private volatile boolean running;
public DispatchManager(RedisPubSubConnectionFactory redisPubSubConnectionFactory,
Optional<DispatchChannel> deadLetterChannel)
{
this.redisPubSubConnectionFactory = redisPubSubConnectionFactory;
this.deadLetterChannel = deadLetterChannel;
}
@Override
public void start() {
this.pubSubConnection = redisPubSubConnectionFactory.connect();
this.running = true;
super.start();
}
public void shutdown() {
this.running = false;
this.pubSubConnection.close();
}
public synchronized void subscribe(String name, DispatchChannel dispatchChannel) {
Optional<DispatchChannel> previous = Optional.ofNullable(subscriptions.get(name));
subscriptions.put(name, dispatchChannel);
try {
pubSubConnection.subscribe(name);
} catch (IOException e) {
logger.warn("Subscription error", e);
}
previous.ifPresent(channel -> dispatchUnsubscription(name, channel));
}
public synchronized void unsubscribe(String name, DispatchChannel channel) {
Optional<DispatchChannel> subscription = Optional.ofNullable(subscriptions.get(name));
if (subscription.isPresent() && subscription.get() == channel) {
subscriptions.remove(name);
try {
pubSubConnection.unsubscribe(name);
} catch (IOException e) {
logger.warn("Unsubscribe error", e);
}
dispatchUnsubscription(name, subscription.get());
}
}
public boolean hasSubscription(String name) {
return subscriptions.containsKey(name);
}
@Override
public void run() {
while (running) {
try {
PubSubReply reply = pubSubConnection.read();
switch (reply.getType()) {
case UNSUBSCRIBE: break;
case SUBSCRIBE: dispatchSubscribe(reply); break;
case MESSAGE: dispatchMessage(reply); break;
default: throw new AssertionError("Unknown pubsub reply type! " + reply.getType());
}
} catch (IOException e) {
logger.warn("***** PubSub Connection Error *****", e);
if (running) {
this.pubSubConnection.close();
this.pubSubConnection = redisPubSubConnectionFactory.connect();
resubscribeAll();
}
}
}
logger.warn("DispatchManager Shutting Down...");
}
private void dispatchSubscribe(final PubSubReply reply) {
Optional<DispatchChannel> subscription = Optional.ofNullable(subscriptions.get(reply.getChannel()));
if (subscription.isPresent()) {
dispatchSubscription(reply.getChannel(), subscription.get());
} else {
logger.info("Received subscribe event for non-existing channel: " + reply.getChannel());
}
}
private void dispatchMessage(PubSubReply reply) {
Optional<DispatchChannel> subscription = Optional.ofNullable(subscriptions.get(reply.getChannel()));
if (subscription.isPresent()) {
dispatchMessage(reply.getChannel(), subscription.get(), reply.getContent().get());
} else if (deadLetterChannel.isPresent()) {
dispatchMessage(reply.getChannel(), deadLetterChannel.get(), reply.getContent().get());
} else {
logger.warn("Received message for non-existing channel, with no dead letter handler: " + reply.getChannel());
}
}
private void resubscribeAll() {
new Thread(() -> {
synchronized (DispatchManager.this) {
try {
for (String name : subscriptions.keySet()) {
pubSubConnection.subscribe(name);
}
} catch (IOException e) {
logger.warn("***** RESUBSCRIPTION ERROR *****", e);
}
}
}).start();
}
private void dispatchMessage(final String name, final DispatchChannel channel, final byte[] message) {
executor.execute(() -> channel.onDispatchMessage(name, message));
}
private void dispatchSubscription(final String name, final DispatchChannel channel) {
executor.execute(() -> channel.onDispatchSubscribed(name));
}
private void dispatchUnsubscription(final String name, final DispatchChannel channel) {
executor.execute(() -> channel.onDispatchUnsubscribed(name));
}
}

View File

@ -1,68 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.io;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class RedisInputStream {
private static final byte CR = 0x0D;
private static final byte LF = 0x0A;
private final InputStream inputStream;
public RedisInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public String readLine() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
boolean foundCr = false;
while (true) {
int character = inputStream.read();
if (character == -1) {
throw new IOException("Stream closed!");
}
baos.write(character);
if (foundCr && character == LF) break;
else if (character == CR) foundCr = true;
else if (foundCr) foundCr = false;
}
byte[] data = baos.toByteArray();
return new String(data, 0, data.length-2);
}
public byte[] readFully(int size) throws IOException {
byte[] result = new byte[size];
int offset = 0;
int remaining = result.length;
while (remaining > 0) {
int read = inputStream.read(result, offset, remaining);
if (read < 0) {
throw new IOException("Stream closed!");
}
offset += read;
remaining -= read;
}
return result;
}
public void close() throws IOException {
inputStream.close();
}
}

View File

@ -1,13 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.io;
import org.whispersystems.dispatch.redis.PubSubConnection;
public interface RedisPubSubConnectionFactory {
PubSubConnection connect();
}

View File

@ -1,123 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.dispatch.io.RedisInputStream;
import org.whispersystems.dispatch.redis.protocol.ArrayReplyHeader;
import org.whispersystems.dispatch.redis.protocol.IntReply;
import org.whispersystems.dispatch.redis.protocol.StringReplyHeader;
import org.whispersystems.dispatch.util.Util;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
public class PubSubConnection {
private final Logger logger = LoggerFactory.getLogger(PubSubConnection.class);
private static final byte[] UNSUBSCRIBE_TYPE = {'u', 'n', 's', 'u', 'b', 's', 'c', 'r', 'i', 'b', 'e' };
private static final byte[] SUBSCRIBE_TYPE = {'s', 'u', 'b', 's', 'c', 'r', 'i', 'b', 'e' };
private static final byte[] MESSAGE_TYPE = {'m', 'e', 's', 's', 'a', 'g', 'e' };
private static final byte[] SUBSCRIBE_COMMAND = {'S', 'U', 'B', 'S', 'C', 'R', 'I', 'B', 'E', ' ' };
private static final byte[] UNSUBSCRIBE_COMMAND = {'U', 'N', 'S', 'U', 'B', 'S', 'C', 'R', 'I', 'B', 'E', ' '};
private static final byte[] CRLF = {'\r', '\n' };
private final OutputStream outputStream;
private final RedisInputStream inputStream;
private final Socket socket;
private final AtomicBoolean closed;
public PubSubConnection(Socket socket) throws IOException {
this.socket = socket;
this.outputStream = socket.getOutputStream();
this.inputStream = new RedisInputStream(new BufferedInputStream(socket.getInputStream()));
this.closed = new AtomicBoolean(false);
}
public void subscribe(String channelName) throws IOException {
if (closed.get()) throw new IOException("Connection closed!");
byte[] command = Util.combine(SUBSCRIBE_COMMAND, channelName.getBytes(), CRLF);
outputStream.write(command);
}
public void unsubscribe(String channelName) throws IOException {
if (closed.get()) throw new IOException("Connection closed!");
byte[] command = Util.combine(UNSUBSCRIBE_COMMAND, channelName.getBytes(), CRLF);
outputStream.write(command);
}
public PubSubReply read() throws IOException {
if (closed.get()) throw new IOException("Connection closed!");
ArrayReplyHeader replyHeader = new ArrayReplyHeader(inputStream.readLine());
if (replyHeader.getElementCount() != 3) {
throw new IOException("Received array reply header with strange count: " + replyHeader.getElementCount());
}
StringReplyHeader replyTypeHeader = new StringReplyHeader(inputStream.readLine());
byte[] replyType = inputStream.readFully(replyTypeHeader.getStringLength());
inputStream.readLine();
if (Arrays.equals(SUBSCRIBE_TYPE, replyType)) return readSubscribeReply();
else if (Arrays.equals(UNSUBSCRIBE_TYPE, replyType)) return readUnsubscribeReply();
else if (Arrays.equals(MESSAGE_TYPE, replyType)) return readMessageReply();
else throw new IOException("Unknown reply type: " + new String(replyType));
}
public void close() {
try {
this.closed.set(true);
this.inputStream.close();
this.outputStream.close();
this.socket.close();
} catch (IOException e) {
logger.warn("Exception while closing", e);
}
}
private PubSubReply readMessageReply() throws IOException {
StringReplyHeader channelNameHeader = new StringReplyHeader(inputStream.readLine());
byte[] channelName = inputStream.readFully(channelNameHeader.getStringLength());
inputStream.readLine();
StringReplyHeader messageHeader = new StringReplyHeader(inputStream.readLine());
byte[] message = inputStream.readFully(messageHeader.getStringLength());
inputStream.readLine();
return new PubSubReply(PubSubReply.Type.MESSAGE, new String(channelName), Optional.of(message));
}
private PubSubReply readUnsubscribeReply() throws IOException {
String channelName = readSubscriptionReply();
return new PubSubReply(PubSubReply.Type.UNSUBSCRIBE, channelName, Optional.empty());
}
private PubSubReply readSubscribeReply() throws IOException {
String channelName = readSubscriptionReply();
return new PubSubReply(PubSubReply.Type.SUBSCRIBE, channelName, Optional.empty());
}
private String readSubscriptionReply() throws IOException {
StringReplyHeader channelNameHeader = new StringReplyHeader(inputStream.readLine());
byte[] channelName = inputStream.readFully(channelNameHeader.getStringLength());
inputStream.readLine();
new IntReply(inputStream.readLine());
return new String(channelName);
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis;
import java.util.Optional;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class PubSubReply {
public enum Type {
MESSAGE,
SUBSCRIBE,
UNSUBSCRIBE
}
private final Type type;
private final String channel;
private final Optional<byte[]> content;
public PubSubReply(Type type, String channel, Optional<byte[]> content) {
this.type = type;
this.channel = channel;
this.content = content;
}
public Type getType() {
return type;
}
public String getChannel() {
return channel;
}
public Optional<byte[]> getContent() {
return content;
}
}

View File

@ -1,28 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis.protocol;
import java.io.IOException;
public class ArrayReplyHeader {
private final int elementCount;
public ArrayReplyHeader(String header) throws IOException {
if (header == null || header.length() < 2 || header.charAt(0) != '*') {
throw new IOException("Invalid array reply header: " + header);
}
try {
this.elementCount = Integer.parseInt(header.substring(1));
} catch (NumberFormatException e) {
throw new IOException(e);
}
}
public int getElementCount() {
return elementCount;
}
}

View File

@ -1,28 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis.protocol;
import java.io.IOException;
public class IntReply {
private final int value;
public IntReply(String reply) throws IOException {
if (reply == null || reply.length() < 2 || reply.charAt(0) != ':') {
throw new IOException("Invalid int reply: " + reply);
}
try {
this.value = Integer.parseInt(reply.substring(1));
} catch (NumberFormatException e) {
throw new IOException(e);
}
}
public int getValue() {
return value;
}
}

View File

@ -1,28 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis.protocol;
import java.io.IOException;
public class StringReplyHeader {
private final int stringLength;
public StringReplyHeader(String header) throws IOException {
if (header == null || header.length() < 2 || header.charAt(0) != '$') {
throw new IOException("Invalid string reply header: " + header);
}
try {
this.stringLength = Integer.parseInt(header.substring(1));
} catch (NumberFormatException e) {
throw new IOException(e);
}
}
public int getStringLength() {
return stringLength;
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class Util {
public static byte[] combine(byte[]... elements) {
try {
int sum = 0;
for (byte[] element : elements) {
sum += element.length;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream(sum);
for (byte[] element : elements) {
baos.write(element);
}
return baos.toByteArray();
} catch (IOException e) {
throw new AssertionError(e);
}
}
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
}

View File

@ -1,123 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.stubbing.Answer;
import org.whispersystems.dispatch.io.RedisPubSubConnectionFactory;
import org.whispersystems.dispatch.redis.PubSubConnection;
import org.whispersystems.dispatch.redis.PubSubReply;
public class DispatchManagerTest {
private PubSubConnection pubSubConnection;
private RedisPubSubConnectionFactory socketFactory;
private DispatchManager dispatchManager;
private PubSubReplyInputStream pubSubReplyInputStream;
@BeforeEach
void setUp() throws Exception {
pubSubConnection = mock(PubSubConnection.class );
socketFactory = mock(RedisPubSubConnectionFactory.class);
pubSubReplyInputStream = new PubSubReplyInputStream();
when(socketFactory.connect()).thenReturn(pubSubConnection);
when(pubSubConnection.read()).thenAnswer((Answer<PubSubReply>) invocationOnMock -> pubSubReplyInputStream.read());
dispatchManager = new DispatchManager(socketFactory, Optional.empty());
dispatchManager.start();
}
@AfterEach
void tearDown() {
dispatchManager.shutdown();
}
@Test
public void testConnect() {
verify(socketFactory).connect();
}
@Test
public void testSubscribe() {
DispatchChannel dispatchChannel = mock(DispatchChannel.class);
dispatchManager.subscribe("foo", dispatchChannel);
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.empty()));
verify(dispatchChannel, timeout(1000)).onDispatchSubscribed(eq("foo"));
}
@Test
public void testSubscribeUnsubscribe() {
DispatchChannel dispatchChannel = mock(DispatchChannel.class);
dispatchManager.subscribe("foo", dispatchChannel);
dispatchManager.unsubscribe("foo", dispatchChannel);
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.empty()));
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.UNSUBSCRIBE, "foo", Optional.empty()));
verify(dispatchChannel, timeout(1000)).onDispatchUnsubscribed(eq("foo"));
}
@Test
public void testMessages() {
DispatchChannel fooChannel = mock(DispatchChannel.class);
DispatchChannel barChannel = mock(DispatchChannel.class);
dispatchManager.subscribe("foo", fooChannel);
dispatchManager.subscribe("bar", barChannel);
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "foo", Optional.empty()));
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.SUBSCRIBE, "bar", Optional.empty()));
verify(fooChannel, timeout(1000)).onDispatchSubscribed(eq("foo"));
verify(barChannel, timeout(1000)).onDispatchSubscribed(eq("bar"));
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.MESSAGE, "foo", Optional.of("hello".getBytes())));
pubSubReplyInputStream.write(new PubSubReply(PubSubReply.Type.MESSAGE, "bar", Optional.of("there".getBytes())));
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
verify(fooChannel, timeout(1000)).onDispatchMessage(eq("foo"), captor.capture());
assertArrayEquals("hello".getBytes(), captor.getValue());
verify(barChannel, timeout(1000)).onDispatchMessage(eq("bar"), captor.capture());
assertArrayEquals("there".getBytes(), captor.getValue());
}
private static class PubSubReplyInputStream {
private final List<PubSubReply> pubSubReplyList = new LinkedList<>();
public synchronized PubSubReply read() {
try {
while (pubSubReplyList.isEmpty()) wait();
return pubSubReplyList.remove(0);
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
public synchronized void write(PubSubReply pubSubReply) {
pubSubReplyList.add(pubSubReply);
notifyAll();
}
}
}

View File

@ -1,264 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.security.SecureRandom;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
class PubSubConnectionTest {
private static final String REPLY = "*3\r\n" +
"$9\r\n" +
"subscribe\r\n" +
"$5\r\n" +
"abcde\r\n" +
":1\r\n" +
"*3\r\n" +
"$9\r\n" +
"subscribe\r\n" +
"$5\r\n" +
"fghij\r\n" +
":2\r\n" +
"*3\r\n" +
"$9\r\n" +
"subscribe\r\n" +
"$5\r\n" +
"klmno\r\n" +
":2\r\n" +
"*3\r\n" +
"$7\r\n" +
"message\r\n" +
"$5\r\n" +
"abcde\r\n" +
"$10\r\n" +
"1234567890\r\n" +
"*3\r\n" +
"$7\r\n" +
"message\r\n" +
"$5\r\n" +
"klmno\r\n" +
"$10\r\n" +
"0987654321\r\n";
@Test
void testSubscribe() throws IOException {
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );
when(socket.getOutputStream()).thenReturn(outputStream);
PubSubConnection connection = new PubSubConnection(socket);
connection.subscribe("foobar");
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
verify(outputStream).write(captor.capture());
assertArrayEquals(captor.getValue(), "SUBSCRIBE foobar\r\n".getBytes());
}
@Test
void testUnsubscribe() throws IOException {
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );
when(socket.getOutputStream()).thenReturn(outputStream);
PubSubConnection connection = new PubSubConnection(socket);
connection.unsubscribe("bazbar");
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
verify(outputStream).write(captor.capture());
assertArrayEquals(captor.getValue(), "UNSUBSCRIBE bazbar\r\n".getBytes());
}
@Test
void testTricklyResponse() throws Exception {
InputStream inputStream = mockInputStreamFor(new TrickleInputStream(REPLY.getBytes()));
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );
when(socket.getOutputStream()).thenReturn(outputStream);
when(socket.getInputStream()).thenReturn(inputStream);
PubSubConnection pubSubConnection = new PubSubConnection(socket);
readResponses(pubSubConnection);
}
@Test
void testFullResponse() throws Exception {
InputStream inputStream = mockInputStreamFor(new FullInputStream(REPLY.getBytes()));
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );
when(socket.getOutputStream()).thenReturn(outputStream);
when(socket.getInputStream()).thenReturn(inputStream);
PubSubConnection pubSubConnection = new PubSubConnection(socket);
readResponses(pubSubConnection);
}
@Test
void testRandomLengthResponse() throws Exception {
InputStream inputStream = mockInputStreamFor(new RandomInputStream(REPLY.getBytes()));
OutputStream outputStream = mock(OutputStream.class);
Socket socket = mock(Socket.class );
when(socket.getOutputStream()).thenReturn(outputStream);
when(socket.getInputStream()).thenReturn(inputStream);
PubSubConnection pubSubConnection = new PubSubConnection(socket);
readResponses(pubSubConnection);
}
private InputStream mockInputStreamFor(final MockInputStream stub) throws IOException {
InputStream result = mock(InputStream.class);
when(result.read()).thenAnswer(new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
return stub.read();
}
});
when(result.read(any(byte[].class))).thenAnswer(new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
byte[] buffer = (byte[])invocationOnMock.getArguments()[0];
return stub.read(buffer, 0, buffer.length);
}
});
when(result.read(any(byte[].class), anyInt(), anyInt())).thenAnswer(new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
byte[] buffer = (byte[]) invocationOnMock.getArguments()[0];
int offset = (int) invocationOnMock.getArguments()[1];
int length = (int) invocationOnMock.getArguments()[2];
return stub.read(buffer, offset, length);
}
});
return result;
}
private void readResponses(PubSubConnection pubSubConnection) throws Exception {
PubSubReply reply = pubSubConnection.read();
assertEquals(reply.getType(), PubSubReply.Type.SUBSCRIBE);
assertEquals(reply.getChannel(), "abcde");
assertFalse(reply.getContent().isPresent());
reply = pubSubConnection.read();
assertEquals(reply.getType(), PubSubReply.Type.SUBSCRIBE);
assertEquals(reply.getChannel(), "fghij");
assertFalse(reply.getContent().isPresent());
reply = pubSubConnection.read();
assertEquals(reply.getType(), PubSubReply.Type.SUBSCRIBE);
assertEquals(reply.getChannel(), "klmno");
assertFalse(reply.getContent().isPresent());
reply = pubSubConnection.read();
assertEquals(reply.getType(), PubSubReply.Type.MESSAGE);
assertEquals(reply.getChannel(), "abcde");
assertArrayEquals(reply.getContent().get(), "1234567890".getBytes());
reply = pubSubConnection.read();
assertEquals(reply.getType(), PubSubReply.Type.MESSAGE);
assertEquals(reply.getChannel(), "klmno");
assertArrayEquals(reply.getContent().get(), "0987654321".getBytes());
}
private interface MockInputStream {
public int read();
public int read(byte[] input, int offset, int length);
}
private static class TrickleInputStream implements MockInputStream {
private final byte[] data;
private int index = 0;
private TrickleInputStream(byte[] data) {
this.data = data;
}
public int read() {
return data[index++];
}
public int read(byte[] input, int offset, int length) {
input[offset] = data[index++];
return 1;
}
}
private static class FullInputStream implements MockInputStream {
private final byte[] data;
private int index = 0;
private FullInputStream(byte[] data) {
this.data = data;
}
public int read() {
return data[index++];
}
public int read(byte[] input, int offset, int length) {
int amount = Math.min(data.length - index, length);
System.arraycopy(data, index, input, offset, amount);
index += length;
return amount;
}
}
private static class RandomInputStream implements MockInputStream {
private final byte[] data;
private int index = 0;
private RandomInputStream(byte[] data) {
this.data = data;
}
public int read() {
return data[index++];
}
public int read(byte[] input, int offset, int length) {
int maxCopy = Math.min(data.length - index, length);
int randomCopy = new SecureRandom().nextInt(maxCopy) + 1;
int copyAmount = Math.min(maxCopy, randomCopy);
System.arraycopy(data, index, input, offset, copyAmount);
index += copyAmount;
return copyAmount;
}
}
}

View File

@ -1,54 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis.protocol;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
import org.junit.jupiter.api.Test;
class ArrayReplyHeaderTest {
@Test
void testNull() {
assertThrows(IOException.class, () -> new ArrayReplyHeader(null));
}
@Test
void testBadPrefix() {
assertThrows(IOException.class, () -> new ArrayReplyHeader(":3"));
}
@Test
void testEmpty() {
assertThrows(IOException.class, () -> new ArrayReplyHeader(""));
}
@Test
void testTruncated() {
assertThrows(IOException.class, () -> new ArrayReplyHeader("*"));
}
@Test
void testBadNumber() {
assertThrows(IOException.class, () -> new ArrayReplyHeader("*ABC"));
}
@Test
void testValid() throws IOException {
assertEquals(4, new ArrayReplyHeader("*4").getElementCount());
}
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis.protocol;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
import org.junit.jupiter.api.Test;
class IntReplyHeaderTest {
@Test
void testNull() {
assertThrows(IOException.class, () -> new IntReply(null));
}
@Test
void testEmpty() {
assertThrows(IOException.class, () -> new IntReply(""));
}
@Test
void testBadNumber() {
assertThrows(IOException.class, () -> new IntReply(":A"));
}
@Test
void testBadFormat() {
assertThrows(IOException.class, () -> new IntReply("*"));
}
@Test
void testValid() throws IOException {
assertEquals(23, new IntReply(":23").getValue());
}
}

View File

@ -1,35 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.dispatch.redis.protocol;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
import org.junit.jupiter.api.Test;
class StringReplyHeaderTest {
@Test
void testNull() {
assertThrows(IOException.class, () -> new StringReplyHeader(null));
}
@Test
void testBadNumber() {
assertThrows(IOException.class, () -> new StringReplyHeader("$100A"));
}
@Test
void testBadPrefix() {
assertThrows(IOException.class, () -> new StringReplyHeader("*"));
}
@Test
void testValid() throws IOException {
assertEquals(1000, new StringReplyHeader("$1000").getStringLength());
}
}

View File

@ -1,25 +0,0 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.0 http://maven.apache.org/xsd/assembly-2.1.0.xsd">
<id>bin</id>
<includeBaseDirectory>false</includeBaseDirectory>
<formats>
<format>tar.gz</format>
</formats>
<fileSets>
<fileSet>
<directory>${project.basedir}/config</directory>
<outputDirectory>/config</outputDirectory>
<includes>
<include>*</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>${parent.artifactId}-${project.version}.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View File

@ -1,407 +0,0 @@
# Example, relatively minimal, configuration that passes validation (see `io.dropwizard.cli.CheckCommand`)
#
# `unset` values will need to be set to work properly.
# Most other values are technically valid for a local/demonstration environment, but are probably not production-ready.
adminEventLoggingConfiguration:
credentials: |
Some credentials text
blah blah blah
projectId: some-project-id
logName: some-log-name
stripe:
apiKey: unset
idempotencyKeyGenerator: abcdefg12345678= # base64 for creating request idempotency hash
boostDescription: >
Example
supportedCurrencies:
- xts
# - ...
# - Nth supported currency
braintree:
merchantId: unset
publicKey: unset
privateKey: unset
environment: unset
graphqlUrl: unset
merchantAccounts:
# ISO 4217 currency code and its corresponding sub-merchant account
'xts': unset
supportedCurrencies:
- xts
# - ...
# - Nth supported currency
dynamoDbClientConfiguration:
region: us-west-2 # AWS Region
dynamoDbTables:
accounts:
tableName: Example_Accounts
phoneNumberTableName: Example_Accounts_PhoneNumbers
phoneNumberIdentifierTableName: Example_Accounts_PhoneNumberIdentifiers
usernamesTableName: Example_Accounts_Usernames
scanPageSize: 100
deletedAccounts:
tableName: Example_DeletedAccounts
needsReconciliationIndexName: NeedsReconciliation
deletedAccountsLock:
tableName: Example_DeletedAccountsLock
issuedReceipts:
tableName: Example_IssuedReceipts
expiration: P30D # Duration of time until rows expire
generator: abcdefg12345678= # random base64-encoded binary sequence
keys:
tableName: Example_Keys
messages:
tableName: Example_Messages
expiration: P30D # Duration of time until rows expire
pendingAccounts:
tableName: Example_PendingAccounts
pendingDevices:
tableName: Example_PendingDevices
phoneNumberIdentifiers:
tableName: Example_PhoneNumberIdentifiers
profiles:
tableName: Example_Profiles
pushChallenge:
tableName: Example_PushChallenge
redeemedReceipts:
tableName: Example_RedeemedReceipts
expiration: P30D # Duration of time until rows expire
remoteConfig:
tableName: Example_RemoteConfig
reportMessage:
tableName: Example_ReportMessage
reservedUsernames:
tableName: Example_ReservedUsernames
subscriptions:
tableName: Example_Subscriptions
registrationRecovery:
tableName: Example_RegistrationRecovery
expiration: P300D # Duration of time until rows expire
cacheCluster: # Redis server configuration for cache cluster
configurationUri: redis://redis.example.com:6379/
clientPresenceCluster: # Redis server configuration for client presence cluster
configurationUri: redis://redis.example.com:6379/
pubsub: # Redis server configuration for pubsub cluster
url: redis://redis.example.com:6379/
replicaUrls:
- redis://redis.example.com:6379/
pushSchedulerCluster: # Redis server configuration for push scheduler cluster
configurationUri: redis://redis.example.com:6379/
rateLimitersCluster: # Redis server configuration for rate limiters cluster
configurationUri: redis://redis.example.com:6379/
directory:
client: # Configuration for interfacing with Contact Discovery Service cluster
userAuthenticationTokenSharedSecret: 00000f # hex-encoded secret shared with CDS used to generate auth tokens for Signal users
userAuthenticationTokenUserIdSecret: 00000f # hex-encoded secret shared among Signal-Servers to obscure user phone numbers from CDS
sqs:
accessKey: test # AWS SQS accessKey
accessSecret: test # AWS SQS accessSecret
queueUrls: # AWS SQS queue urls
- https://sqs.example.com/directory.fifo
server: # One or more CDS servers
- replicationName: example # CDS replication name
replicationUrl: cds.example.com # CDS replication endpoint base url
replicationPassword: example # CDS replication endpoint password
replicationCaCertificates: # CDS replication endpoint TLS certificate trust root
- |
-----BEGIN CERTIFICATE-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----
directoryV2:
client: # Configuration for interfacing with Contact Discovery Service v2 cluster
userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth tokens for Signal users
userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with CDS to generate auth identity tokens for Signal users
svr2:
userAuthenticationTokenSharedSecret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth tokens for Signal users
userIdTokenSharedSecret: bbcdefghijklmnopqrstuvwxyz0123456789ABCDEFG= # base64-encoded secret shared with SVR2 to generate auth identity tokens for Signal users
messageCache: # Redis server configuration for message store cache
persistDelayMinutes: 1
cluster:
configurationUri: redis://redis.example.com:6379/
metricsCluster:
configurationUri: redis://redis.example.com:6379/
awsAttachments: # AWS S3 configuration
accessKey: test
accessSecret: test
bucket: aws-attachments
region: us-west-2
gcpAttachments: # GCP Storage configuration
domain: example.com
email: user@example.cocm
maxSizeInBytes: 1024
pathPrefix:
rsaSigningKey: |
-----BEGIN PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAA
-----END PRIVATE KEY-----
accountDatabaseCrawler:
chunkSize: 10 # accounts per run
chunkIntervalMs: 60000 # time per run
apn: # Apple Push Notifications configuration
sandbox: true
bundleId: com.example.textsecuregcm
keyId: unset
teamId: unset
signingKey: |
-----BEGIN PRIVATE KEY-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAA
-----END PRIVATE KEY-----
fcm: # FCM configuration
credentials: |
{ "json": true }
cdn:
accessKey: test # AWS Access Key ID
accessSecret: test # AWS Access Secret
bucket: cdn # S3 Bucket name
region: us-west-2 # AWS region
datadog:
apiKey: unset
environment: dev
unidentifiedDelivery:
certificate: ABCD1234
privateKey: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789AAAAAAA
expiresDays: 7
recaptcha:
projectPath: projects/example
credentialConfigurationJson: "{ }" # service account configuration for backend authentication
hCaptcha:
apiKey: unset
storageService:
uri: storage.example.com
userAuthenticationTokenSharedSecret: 00000f
storageCaCertificates:
- |
-----BEGIN CERTIFICATE-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----
backupService:
uri: backup.example.com
userAuthenticationTokenSharedSecret: 00000f
backupCaCertificates:
- |
-----BEGIN CERTIFICATE-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----
zkConfig:
serverPublic: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
serverSecret: ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyzAA==
appConfig:
application: example
environment: example
configuration: example
remoteConfig:
authorizedTokens:
- # 1st authorized token
- # 2nd authorized token
- # ...
- # Nth authorized token
globalConfig: # keys and values that are given to clients on GET /v1/config
EXAMPLE_KEY: VALUE
paymentsService:
userAuthenticationTokenSharedSecret: 0000000f0000000f0000000f0000000f0000000f0000000f0000000f0000000f # hex-encoded 32-byte secret shared with MobileCoin services used to generate auth tokens for Signal users
fixerApiKey: unset
coinMarketCapApiKey: unset
coinMarketCapCurrencyIds:
MOB: 7878
paymentCurrencies:
# list of symbols for supported currencies
- MOB
artService:
userAuthenticationTokenSharedSecret: 0000000f0000000f0000000f0000000f0000000f0000000f0000000f0000000f # hex-encoded 32-byte secret not shared with any external service, but used in ArtController
userAuthenticationTokenUserIdSecret: 00000f # hex-encoded secret to obscure user phone numbers from Sticker Creator
badges:
badges:
- id: TEST
category: other
sprites: # exactly 6
- sprite-1.png
- sprite-2.png
- sprite-3.png
- sprite-4.png
- sprite-5.png
- sprite-6.png
svg: example.svg
svgs:
- light: example-light.svg
dark: example-dark.svg
badgeIdsEnabledForAll:
- TEST
receiptLevels:
'1': TEST
subscription: # configuration for Stripe subscriptions
badgeGracePeriod: P15D
levels:
500:
badge: EXAMPLE
prices:
# list of ISO 4217 currency codes and amounts for the given badge level
xts:
amount: '10'
processorIds:
STRIPE: price_example # stripe Price ID
BRAINTREE: plan_example # braintree Plan ID
oneTimeDonations:
boost:
level: 1
expiration: P90D
badge: EXAMPLE
gift:
level: 10
expiration: P90D
badge: EXAMPLE
currencies:
# ISO 4217 currency codes and amounts in those currencies
xts:
minimum: '0.5'
gift: '2'
boosts:
- '1'
- '2'
- '4'
- '8'
- '20'
- '40'
registrationService:
host: registration.example.com
apiKey: EXAMPLE
registrationCaCertificate: | # Registration service TLS certificate trust root
-----BEGIN CERTIFICATE-----
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ/0123456789+abcdefghijklmnopqrstuvwxyz
AAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE-----

View File

@ -1,630 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>TextSecureServer</artifactId>
<groupId>org.whispersystems.textsecure</groupId>
<version>JGITVER</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service</artifactId>
<dependencies>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>event-logger</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>redis-dispatch</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.whispersystems.textsecure</groupId>
<artifactId>websocket-resources</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.signal</groupId>
<artifactId>libsignal-server</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-auth</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-client</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-db</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-logging</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-metrics</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-util</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-servlets</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-lifecycle</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-jersey</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-jetty</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-validation</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-migrations</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-healthchecks</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-annotation</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-common</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-testing</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
</dependency>
<dependency>
<groupId>com.google.firebase</groupId>
<artifactId>firebase-admin</artifactId>
<version>9.1.1</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-circuitbreaker</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-reactor</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<!-- Needed for gRPC with Java 9+ -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-datadog</artifactId>
</dependency>
<dependency>
<groupId>org.coursera</groupId>
<artifactId>dropwizard-metrics-datadog</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sts</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sqs</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>appconfig</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>appconfigdata</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-core</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>dynamodb-lock-client</artifactId>
<version>1.1.0</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
<dependency>
<groupId>com.eatthepath</groupId>
<artifactId>pushy</artifactId>
</dependency>
<dependency>
<groupId>com.eatthepath</groupId>
<artifactId>pushy-dropwizard-metrics-listener</artifactId>
</dependency>
<dependency>
<groupId>com.vdurmont</groupId>
<artifactId>semver4j</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.libphonenumber</groupId>
<artifactId>libphonenumber</artifactId>
</dependency>
<dependency>
<groupId>net.sourceforge.argparse4j</groupId>
<artifactId>argparse4j</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework</groupId>
<artifactId>jersey-test-framework-core</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.almworks.sqlite4java</groupId>
<artifactId>sqlite4java</artifactId>
<version>1.0.392</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
</dependency>
<dependency>
<groupId>org.signal</groupId>
<artifactId>embedded-redis</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.uuid</groupId>
<artifactId>java-uuid-generator</artifactId>
<version>4.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>DynamoDBLocal</artifactId>
<version>1.21.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.ganadist.sqlite4java</groupId>
<artifactId>libsqlite4java-osx-aarch64</artifactId>
<version>1.0.392</version>
<type>dylib</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-recaptchaenterprise</artifactId>
</dependency>
<dependency>
<groupId>com.stripe</groupId>
<artifactId>stripe-java</artifactId>
</dependency>
<dependency>
<groupId>com.braintreepayments.gateway</groupId>
<artifactId>braintree-java</artifactId>
</dependency>
<dependency>
<groupId>com.apollographql.apollo3</groupId>
<artifactId>apollo-api-jvm</artifactId>
<version>3.7.1</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>exclude-spam-filter</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.whispersystems.textsecuregcm.WhisperServerService</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<id>read-deploy-configuration</id>
<phase>deploy</phase>
<goals>
<goal>read-project-properties</goal>
</goals>
<configuration>
<files>${project.basedir}/config/deploy.properties</files>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.signal</groupId>
<artifactId>s3-upload-maven-plugin</artifactId>
<version>1.6-SNAPSHOT</version>
<configuration>
<source>${project.build.directory}/${project.build.finalName}-bin.tar.gz</source>
<bucketName>${deploy.bucketName}</bucketName>
<region>${deploy.bucketRegion}</region>
<destination>${project.build.finalName}-bin.tar.gz</destination>
</configuration>
<executions>
<execution>
<id>deploy-to-s3</id>
<phase>deploy</phase>
<goals>
<goal>s3-upload</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<finalName>${project.parent.artifactId}-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>templating-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<id>filter-src</id>
<goals>
<goal>filter-sources</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>check-all-service-config</id>
<phase>verify</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>org.whispersystems.textsecuregcm.CheckServiceConfigurations</mainClass>
<classpathScope>test</classpathScope>
<arguments>
<argument>${project.basedir}/config</argument>
</arguments>
</configuration>
</plugin>
<plugin>
<groupId>com.github.aoudiamoncef</groupId>
<artifactId>apollo-client-maven-plugin</artifactId>
<version>5.0.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<services>
<braintree>
<compilationUnit>
<name>braintree</name>
<compilerParams>
<schemaPackageName>com.braintree.graphql.client</schemaPackageName>
</compilerParams>
</compilationUnit>
</braintree>
</services>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,9 +0,0 @@
# https://graphql.braintreepayments.com/reference/#Mutation--chargePaymentMethod
mutation ChargePayPalOneTimePayment($input: ChargePaymentMethodInput!) {
chargePaymentMethod(input: $input) {
transaction {
id,
status
}
}
}

View File

@ -1,6 +0,0 @@
mutation CreatePayPalBillingAgreement($input: CreatePayPalBillingAgreementInput!) {
createPayPalBillingAgreement(input: $input) {
approvalUrl,
billingAgreementToken
}
}

View File

@ -1,7 +0,0 @@
# https://graphql.braintreepayments.com/reference/#Mutation--createPayPalOneTimePayment
mutation CreatePayPalOneTimePayment($input: CreatePayPalOneTimePaymentInput!) {
createPayPalOneTimePayment(input: $input) {
approvalUrl,
paymentId
}
}

View File

@ -1,7 +0,0 @@
mutation TokenizePayPalBillingAgreement($input: TokenizePayPalBillingAgreementInput!) {
tokenizePayPalBillingAgreement(input: $input) {
paymentMethod {
id
}
}
}

View File

@ -1,8 +0,0 @@
# https://graphql.braintreepayments.com/reference/#Mutation--tokenizePayPalOneTimePayment
mutation TokenizePayPalOneTimePayment($input: TokenizePayPalOneTimePaymentInput!) {
tokenizePayPalOneTimePayment(input: $input) {
paymentMethod {
id
}
}
}

View File

@ -1,7 +0,0 @@
mutation VaultPaymentMethod($input: VaultPaymentMethodInput!) {
vaultPaymentMethod(input: $input) {
paymentMethod {
id
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm;
public class WhisperServerVersion {
private static final String VERSION = "${project.version}";
public static String getServerVersion() {
return VERSION;
}
}

View File

@ -1,67 +0,0 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.i18n;
import com.google.common.annotations.VisibleForTesting;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.ResourceBundle.Control;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
public class HeaderControlledResourceBundleLookup {
private static final int MAX_LOCALES = 15;
private final ResourceBundleFactory resourceBundleFactory;
public HeaderControlledResourceBundleLookup() {
this(ResourceBundle::getBundle);
}
@VisibleForTesting
public HeaderControlledResourceBundleLookup(
@Nonnull final ResourceBundleFactory resourceBundleFactory) {
this.resourceBundleFactory = Objects.requireNonNull(resourceBundleFactory);
}
@Nonnull
private List<Locale> getAcceptableLocales(final List<Locale> acceptableLanguages) {
return acceptableLanguages.stream().limit(MAX_LOCALES).distinct().collect(Collectors.toList());
}
@Nonnull
public ResourceBundle getResourceBundle(final String baseName, final List<Locale> acceptableLocales) {
final List<Locale> deduplicatedLocales = getAcceptableLocales(acceptableLocales);
final Locale desiredLocale = deduplicatedLocales.isEmpty() ? Locale.getDefault() : deduplicatedLocales.get(0);
// define a control with a fallback order as specified in the header
Control control = new Control() {
@Override
public List<String> getFormats(final String baseName) {
Objects.requireNonNull(baseName);
return Control.FORMAT_PROPERTIES;
}
@Override
public Locale getFallbackLocale(final String baseName, final Locale locale) {
Objects.requireNonNull(baseName);
if (locale.equals(Locale.getDefault())) {
return null;
}
final int localeIndex = deduplicatedLocales.indexOf(locale);
if (localeIndex < 0 || localeIndex >= deduplicatedLocales.size() - 1) {
return Locale.getDefault();
}
// [0, deduplicatedLocales.size() - 2] is now the possible range for localeIndex
return deduplicatedLocales.get(localeIndex + 1);
}
};
return resourceBundleFactory.createBundle(baseName, desiredLocale, control);
}
}

View File

@ -1,13 +0,0 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.i18n;
import java.util.Locale;
import java.util.ResourceBundle;
public interface ResourceBundleFactory {
ResourceBundle createBundle(String baseName, Locale locale, ResourceBundle.Control control);
}

View File

@ -1,447 +0,0 @@
/*
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.configuration.SpamFilterConfiguration;
import org.whispersystems.textsecuregcm.configuration.AccountDatabaseCrawlerConfiguration;
import org.whispersystems.textsecuregcm.configuration.AdminEventLoggingConfiguration;
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
import org.whispersystems.textsecuregcm.configuration.AppConfigConfiguration;
import org.whispersystems.textsecuregcm.configuration.AwsAttachmentsConfiguration;
import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration;
import org.whispersystems.textsecuregcm.configuration.BraintreeConfiguration;
import org.whispersystems.textsecuregcm.configuration.CdnConfiguration;
import org.whispersystems.textsecuregcm.configuration.DatadogConfiguration;
import org.whispersystems.textsecuregcm.configuration.DirectoryConfiguration;
import org.whispersystems.textsecuregcm.configuration.DirectoryV2Configuration;
import org.whispersystems.textsecuregcm.configuration.DynamoDbClientConfiguration;
import org.whispersystems.textsecuregcm.configuration.DynamoDbTables;
import org.whispersystems.textsecuregcm.configuration.FcmConfiguration;
import org.whispersystems.textsecuregcm.configuration.GcpAttachmentsConfiguration;
import org.whispersystems.textsecuregcm.configuration.HCaptchaConfiguration;
import org.whispersystems.textsecuregcm.configuration.MaxDeviceConfiguration;
import org.whispersystems.textsecuregcm.configuration.MessageCacheConfiguration;
import org.whispersystems.textsecuregcm.configuration.OneTimeDonationConfiguration;
import org.whispersystems.textsecuregcm.configuration.PaymentsServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.ArtServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.RateLimitsConfiguration;
import org.whispersystems.textsecuregcm.configuration.RecaptchaConfiguration;
import org.whispersystems.textsecuregcm.configuration.RedisClusterConfiguration;
import org.whispersystems.textsecuregcm.configuration.RedisConfiguration;
import org.whispersystems.textsecuregcm.configuration.RegistrationServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.RemoteConfigConfiguration;
import org.whispersystems.textsecuregcm.configuration.ReportMessageConfiguration;
import org.whispersystems.textsecuregcm.configuration.SecureBackupServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.SecureStorageServiceConfiguration;
import org.whispersystems.textsecuregcm.configuration.SecureValueRecovery2Configuration;
import org.whispersystems.textsecuregcm.configuration.StripeConfiguration;
import org.whispersystems.textsecuregcm.configuration.SubscriptionConfiguration;
import org.whispersystems.textsecuregcm.configuration.TestDeviceConfiguration;
import org.whispersystems.textsecuregcm.configuration.UnidentifiedDeliveryConfiguration;
import org.whispersystems.textsecuregcm.configuration.ZkConfig;
import org.whispersystems.websocket.configuration.WebSocketConfiguration;
/** @noinspection MismatchedQueryAndUpdateOfCollection, WeakerAccess */
public class WhisperServerConfiguration extends Configuration {
@NotNull
@Valid
@JsonProperty
private AdminEventLoggingConfiguration adminEventLoggingConfiguration;
@NotNull
@Valid
@JsonProperty
private StripeConfiguration stripe;
@NotNull
@Valid
@JsonProperty
private BraintreeConfiguration braintree;
@NotNull
@Valid
@JsonProperty
private DynamoDbClientConfiguration dynamoDbClientConfiguration;
@NotNull
@Valid
@JsonProperty
private DynamoDbTables dynamoDbTables;
@NotNull
@Valid
@JsonProperty
private AwsAttachmentsConfiguration awsAttachments;
@NotNull
@Valid
@JsonProperty
private GcpAttachmentsConfiguration gcpAttachments;
@NotNull
@Valid
@JsonProperty
private CdnConfiguration cdn;
@NotNull
@Valid
@JsonProperty
private DatadogConfiguration datadog;
@NotNull
@Valid
@JsonProperty
private RedisClusterConfiguration cacheCluster;
@NotNull
@Valid
@JsonProperty
private RedisConfiguration pubsub;
@NotNull
@Valid
@JsonProperty
private RedisClusterConfiguration metricsCluster;
@NotNull
@Valid
@JsonProperty
private DirectoryConfiguration directory;
@NotNull
@Valid
@JsonProperty
private DirectoryV2Configuration directoryV2;
@NotNull
@Valid
@JsonProperty
private SecureValueRecovery2Configuration svr2;
@NotNull
@Valid
@JsonProperty
private AccountDatabaseCrawlerConfiguration accountDatabaseCrawler;
@NotNull
@Valid
@JsonProperty
private RedisClusterConfiguration pushSchedulerCluster;
@NotNull
@Valid
@JsonProperty
private RedisClusterConfiguration rateLimitersCluster;
@NotNull
@Valid
@JsonProperty
private MessageCacheConfiguration messageCache;
@NotNull
@Valid
@JsonProperty
private RedisClusterConfiguration clientPresenceCluster;
@Valid
@NotNull
@JsonProperty
private List<TestDeviceConfiguration> testDevices = new LinkedList<>();
@Valid
@NotNull
@JsonProperty
private List<MaxDeviceConfiguration> maxDevices = new LinkedList<>();
@Valid
@NotNull
@JsonProperty
private RateLimitsConfiguration limits = new RateLimitsConfiguration();
@Valid
@NotNull
@JsonProperty
private WebSocketConfiguration webSocket = new WebSocketConfiguration();
@Valid
@NotNull
@JsonProperty
private FcmConfiguration fcm;
@Valid
@NotNull
@JsonProperty
private ApnConfiguration apn;
@Valid
@NotNull
@JsonProperty
private UnidentifiedDeliveryConfiguration unidentifiedDelivery;
@Valid
@NotNull
@JsonProperty
private RecaptchaConfiguration recaptcha;
@Valid
@NotNull
@JsonProperty
private HCaptchaConfiguration hCaptcha;
@Valid
@NotNull
@JsonProperty
private SecureStorageServiceConfiguration storageService;
@Valid
@NotNull
@JsonProperty
private SecureBackupServiceConfiguration backupService;
@Valid
@NotNull
@JsonProperty
private PaymentsServiceConfiguration paymentsService;
@Valid
@NotNull
@JsonProperty
private ArtServiceConfiguration artService;
@Valid
@NotNull
@JsonProperty
private ZkConfig zkConfig;
@Valid
@NotNull
@JsonProperty
private RemoteConfigConfiguration remoteConfig;
@Valid
@NotNull
@JsonProperty
private AppConfigConfiguration appConfig;
@Valid
@NotNull
@JsonProperty
private BadgesConfiguration badges;
@Valid
@JsonProperty
@NotNull
private SubscriptionConfiguration subscription;
@Valid
@JsonProperty
@NotNull
private OneTimeDonationConfiguration oneTimeDonations;
@Valid
@NotNull
@JsonProperty
private ReportMessageConfiguration reportMessage = new ReportMessageConfiguration();
@Valid
@JsonProperty
private SpamFilterConfiguration spamFilterConfiguration;
@Valid
@NotNull
@JsonProperty
private RegistrationServiceConfiguration registrationService;
public AdminEventLoggingConfiguration getAdminEventLoggingConfiguration() {
return adminEventLoggingConfiguration;
}
public StripeConfiguration getStripe() {
return stripe;
}
public BraintreeConfiguration getBraintree() {
return braintree;
}
public DynamoDbClientConfiguration getDynamoDbClientConfiguration() {
return dynamoDbClientConfiguration;
}
public DynamoDbTables getDynamoDbTables() {
return dynamoDbTables;
}
public RecaptchaConfiguration getRecaptchaConfiguration() {
return recaptcha;
}
public HCaptchaConfiguration getHCaptchaConfiguration() {
return hCaptcha;
}
public WebSocketConfiguration getWebSocketConfiguration() {
return webSocket;
}
public AwsAttachmentsConfiguration getAwsAttachmentsConfiguration() {
return awsAttachments;
}
public GcpAttachmentsConfiguration getGcpAttachmentsConfiguration() {
return gcpAttachments;
}
public RedisClusterConfiguration getCacheClusterConfiguration() {
return cacheCluster;
}
public RedisConfiguration getPubsubCacheConfiguration() {
return pubsub;
}
public RedisClusterConfiguration getMetricsClusterConfiguration() {
return metricsCluster;
}
public DirectoryConfiguration getDirectoryConfiguration() {
return directory;
}
public SecureValueRecovery2Configuration getSvr2Configuration() {
return svr2;
}
public DirectoryV2Configuration getDirectoryV2Configuration() {
return directoryV2;
}
public SecureStorageServiceConfiguration getSecureStorageServiceConfiguration() {
return storageService;
}
public AccountDatabaseCrawlerConfiguration getAccountDatabaseCrawlerConfiguration() {
return accountDatabaseCrawler;
}
public MessageCacheConfiguration getMessageCacheConfiguration() {
return messageCache;
}
public RedisClusterConfiguration getClientPresenceClusterConfiguration() {
return clientPresenceCluster;
}
public RedisClusterConfiguration getPushSchedulerCluster() {
return pushSchedulerCluster;
}
public RedisClusterConfiguration getRateLimitersCluster() {
return rateLimitersCluster;
}
public RateLimitsConfiguration getLimitsConfiguration() {
return limits;
}
public FcmConfiguration getFcmConfiguration() {
return fcm;
}
public ApnConfiguration getApnConfiguration() {
return apn;
}
public CdnConfiguration getCdnConfiguration() {
return cdn;
}
public DatadogConfiguration getDatadogConfiguration() {
return datadog;
}
public UnidentifiedDeliveryConfiguration getDeliveryCertificate() {
return unidentifiedDelivery;
}
public Map<String, Integer> getTestDevices() {
Map<String, Integer> results = new HashMap<>();
for (TestDeviceConfiguration testDeviceConfiguration : testDevices) {
results.put(testDeviceConfiguration.getNumber(),
testDeviceConfiguration.getCode());
}
return results;
}
public Map<String, Integer> getMaxDevices() {
Map<String, Integer> results = new HashMap<>();
for (MaxDeviceConfiguration maxDeviceConfiguration : maxDevices) {
results.put(maxDeviceConfiguration.getNumber(),
maxDeviceConfiguration.getCount());
}
return results;
}
public SecureBackupServiceConfiguration getSecureBackupServiceConfiguration() {
return backupService;
}
public PaymentsServiceConfiguration getPaymentsServiceConfiguration() {
return paymentsService;
}
public ArtServiceConfiguration getArtServiceConfiguration() {
return artService;
}
public ZkConfig getZkConfig() {
return zkConfig;
}
public RemoteConfigConfiguration getRemoteConfigConfiguration() {
return remoteConfig;
}
public AppConfigConfiguration getAppConfig() {
return appConfig;
}
public BadgesConfiguration getBadges() {
return badges;
}
public SubscriptionConfiguration getSubscription() {
return subscription;
}
public OneTimeDonationConfiguration getOneTimeDonations() {
return oneTimeDonations;
}
public ReportMessageConfiguration getReportMessageConfiguration() {
return reportMessage;
}
public SpamFilterConfiguration getSpamFilterConfiguration() {
return spamFilterConfiguration;
}
public RegistrationServiceConfiguration getRegistrationServiceConfiguration() {
return registrationService;
}
}

View File

@ -1,870 +0,0 @@
/*
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm;
import static com.codahale.metrics.MetricRegistry.name;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.InstanceProfileCredentialsProvider;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.codahale.metrics.SharedMetricRegistries;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.logging.LoggingOptions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import io.dropwizard.Application;
import io.dropwizard.auth.AuthFilter;
import io.dropwizard.auth.PolymorphicAuthDynamicFeature;
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
import io.dropwizard.auth.basic.BasicCredentialAuthFilter;
import io.dropwizard.auth.basic.BasicCredentials;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import io.lettuce.core.metrics.MicrometerCommandLatencyRecorder;
import io.lettuce.core.metrics.MicrometerOptions;
import io.lettuce.core.resource.ClientResources;
import io.micrometer.core.instrument.Meter.Id;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.datadog.DatadogMeterRegistry;
import java.io.ByteArrayInputStream;
import java.net.http.HttpClient;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletRegistration;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.glassfish.jersey.server.ServerProperties;
import org.signal.event.AdminEventLogger;
import org.signal.event.GoogleCloudAdminEventLogger;
import org.signal.i18n.HeaderControlledResourceBundleLookup;
import org.signal.libsignal.zkgroup.ServerSecretParams;
import org.signal.libsignal.zkgroup.auth.ServerZkAuthOperations;
import org.signal.libsignal.zkgroup.profiles.ServerZkProfileOperations;
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation;
import org.signal.libsignal.zkgroup.receipts.ServerZkReceiptOperations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.dispatch.DispatchManager;
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.CertificateGenerator;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAccountAuthenticator;
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
import org.whispersystems.textsecuregcm.auth.PhoneVerificationTokenManager;
import org.whispersystems.textsecuregcm.auth.RegistrationLockVerificationManager;
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
import org.whispersystems.textsecuregcm.auth.WebsocketRefreshApplicationEventListener;
import org.whispersystems.textsecuregcm.badges.ConfiguredProfileBadgeConverter;
import org.whispersystems.textsecuregcm.badges.ResourceBundleLevelTranslator;
import org.whispersystems.textsecuregcm.captcha.CaptchaChecker;
import org.whispersystems.textsecuregcm.captcha.HCaptchaClient;
import org.whispersystems.textsecuregcm.captcha.RecaptchaClient;
import org.whispersystems.textsecuregcm.configuration.DirectoryServerConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.controllers.AccountController;
import org.whispersystems.textsecuregcm.controllers.AccountControllerV2;
import org.whispersystems.textsecuregcm.controllers.ArtController;
import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV2;
import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV3;
import org.whispersystems.textsecuregcm.controllers.CertificateController;
import org.whispersystems.textsecuregcm.controllers.ChallengeController;
import org.whispersystems.textsecuregcm.controllers.DeviceController;
import org.whispersystems.textsecuregcm.controllers.DirectoryController;
import org.whispersystems.textsecuregcm.controllers.DirectoryV2Controller;
import org.whispersystems.textsecuregcm.controllers.DonationController;
import org.whispersystems.textsecuregcm.controllers.KeepAliveController;
import org.whispersystems.textsecuregcm.controllers.KeysController;
import org.whispersystems.textsecuregcm.controllers.MessageController;
import org.whispersystems.textsecuregcm.controllers.PaymentsController;
import org.whispersystems.textsecuregcm.controllers.ProfileController;
import org.whispersystems.textsecuregcm.controllers.ProvisioningController;
import org.whispersystems.textsecuregcm.controllers.RegistrationController;
import org.whispersystems.textsecuregcm.controllers.RemoteConfigController;
import org.whispersystems.textsecuregcm.controllers.SecureBackupController;
import org.whispersystems.textsecuregcm.controllers.SecureStorageController;
import org.whispersystems.textsecuregcm.controllers.SecureValueRecovery2Controller;
import org.whispersystems.textsecuregcm.controllers.StickerController;
import org.whispersystems.textsecuregcm.controllers.SubscriptionController;
import org.whispersystems.textsecuregcm.currency.CoinMarketCapClient;
import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager;
import org.whispersystems.textsecuregcm.currency.FixerClient;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.filters.RemoteDeprecationFilter;
import org.whispersystems.textsecuregcm.filters.RequestStatisticsFilter;
import org.whispersystems.textsecuregcm.filters.TimestampResponseFilter;
import org.whispersystems.textsecuregcm.limits.DynamicRateLimiters;
import org.whispersystems.textsecuregcm.limits.PushChallengeManager;
import org.whispersystems.textsecuregcm.limits.RateLimitChallengeManager;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.mappers.CompletionExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.IOExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.ImpossiblePhoneNumberExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.InvalidWebsocketAddressExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.JsonMappingExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.NonNormalizedPhoneNumberExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.ServerRejectedExceptionMapper;
import org.whispersystems.textsecuregcm.metrics.ApplicationShutdownMonitor;
import org.whispersystems.textsecuregcm.metrics.BufferPoolGauges;
import org.whispersystems.textsecuregcm.metrics.CpuUsageGauge;
import org.whispersystems.textsecuregcm.metrics.FileDescriptorGauge;
import org.whispersystems.textsecuregcm.metrics.FreeMemoryGauge;
import org.whispersystems.textsecuregcm.metrics.GarbageCollectionGauges;
import org.whispersystems.textsecuregcm.metrics.LettuceMetricsMeterFilter;
import org.whispersystems.textsecuregcm.metrics.MaxFileDescriptorGauge;
import org.whispersystems.textsecuregcm.metrics.MetricsApplicationEventListener;
import org.whispersystems.textsecuregcm.metrics.MetricsRequestEventListener;
import org.whispersystems.textsecuregcm.metrics.MicrometerRegistryManager;
import org.whispersystems.textsecuregcm.metrics.NetworkReceivedGauge;
import org.whispersystems.textsecuregcm.metrics.NetworkSentGauge;
import org.whispersystems.textsecuregcm.metrics.OperatingSystemMemoryGauge;
import org.whispersystems.textsecuregcm.metrics.ReportedMessageMetricsListener;
import org.whispersystems.textsecuregcm.metrics.TrafficSource;
import org.whispersystems.textsecuregcm.providers.MultiRecipientMessageProvider;
import org.whispersystems.textsecuregcm.providers.RedisClientFactory;
import org.whispersystems.textsecuregcm.providers.RedisClusterHealthCheck;
import org.whispersystems.textsecuregcm.push.APNSender;
import org.whispersystems.textsecuregcm.push.ApnPushNotificationScheduler;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.FcmSender;
import org.whispersystems.textsecuregcm.push.MessageSender;
import org.whispersystems.textsecuregcm.push.ProvisioningManager;
import org.whispersystems.textsecuregcm.push.PushLatencyManager;
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.redis.ConnectionEventLogger;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
import org.whispersystems.textsecuregcm.s3.PolicySigner;
import org.whispersystems.textsecuregcm.s3.PostPolicyGenerator;
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
import org.whispersystems.textsecuregcm.spam.FilterSpam;
import org.whispersystems.textsecuregcm.spam.RateLimitChallengeListener;
import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider;
import org.whispersystems.textsecuregcm.spam.SpamFilter;
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
import org.whispersystems.textsecuregcm.storage.AccountCleaner;
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawler;
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerCache;
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerListener;
import org.whispersystems.textsecuregcm.storage.Accounts;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.ChangeNumberManager;
import org.whispersystems.textsecuregcm.storage.ContactDiscoveryWriter;
import org.whispersystems.textsecuregcm.storage.DeletedAccounts;
import org.whispersystems.textsecuregcm.storage.DeletedAccountsDirectoryReconciler;
import org.whispersystems.textsecuregcm.storage.DeletedAccountsManager;
import org.whispersystems.textsecuregcm.storage.DeletedAccountsTableCrawler;
import org.whispersystems.textsecuregcm.storage.DirectoryReconciler;
import org.whispersystems.textsecuregcm.storage.DirectoryReconciliationClient;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.storage.IssuedReceiptsManager;
import org.whispersystems.textsecuregcm.storage.Keys;
import org.whispersystems.textsecuregcm.storage.MessagePersister;
import org.whispersystems.textsecuregcm.storage.MessagesCache;
import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb;
import org.whispersystems.textsecuregcm.storage.MessagesManager;
import org.whispersystems.textsecuregcm.storage.NonNormalizedAccountCrawlerListener;
import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
import org.whispersystems.textsecuregcm.storage.Profiles;
import org.whispersystems.textsecuregcm.storage.ProfilesManager;
import org.whispersystems.textsecuregcm.storage.PubSubManager;
import org.whispersystems.textsecuregcm.storage.PushChallengeDynamoDb;
import org.whispersystems.textsecuregcm.storage.PushFeedbackProcessor;
import org.whispersystems.textsecuregcm.storage.RedeemedReceiptsManager;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswords;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
import org.whispersystems.textsecuregcm.storage.RemoteConfigs;
import org.whispersystems.textsecuregcm.storage.RemoteConfigsManager;
import org.whispersystems.textsecuregcm.storage.ReportMessageDynamoDb;
import org.whispersystems.textsecuregcm.storage.ReportMessageManager;
import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager;
import org.whispersystems.textsecuregcm.storage.SubscriptionManager;
import org.whispersystems.textsecuregcm.storage.VerificationCodeStore;
import org.whispersystems.textsecuregcm.subscriptions.BraintreeManager;
import org.whispersystems.textsecuregcm.subscriptions.StripeManager;
import org.whispersystems.textsecuregcm.util.Constants;
import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig;
import org.whispersystems.textsecuregcm.util.HostnameUtil;
import org.whispersystems.textsecuregcm.util.UsernameHashZkProofVerifier;
import org.whispersystems.textsecuregcm.util.logging.LoggingUnhandledExceptionMapper;
import org.whispersystems.textsecuregcm.util.logging.UncaughtExceptionHandler;
import org.whispersystems.textsecuregcm.websocket.AuthenticatedConnectListener;
import org.whispersystems.textsecuregcm.websocket.ProvisioningConnectListener;
import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator;
import org.whispersystems.textsecuregcm.workers.AssignUsernameCommand;
import org.whispersystems.textsecuregcm.workers.CertificateCommand;
import org.whispersystems.textsecuregcm.workers.CheckDynamicConfigurationCommand;
import org.whispersystems.textsecuregcm.workers.DeleteUserCommand;
import org.whispersystems.textsecuregcm.workers.ReserveUsernameCommand;
import org.whispersystems.textsecuregcm.workers.ServerVersionCommand;
import org.whispersystems.textsecuregcm.workers.SetCrawlerAccelerationTask;
import org.whispersystems.textsecuregcm.workers.SetRequestLoggingEnabledTask;
import org.whispersystems.textsecuregcm.workers.SetUserDiscoverabilityCommand;
import org.whispersystems.textsecuregcm.workers.ZkParamsCommand;
import org.whispersystems.websocket.WebSocketResourceProviderFactory;
import org.whispersystems.websocket.setup.WebSocketEnvironment;
import reactor.core.scheduler.Schedulers;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.s3.S3Client;
public class WhisperServerService extends Application<WhisperServerConfiguration> {
private static final Logger log = LoggerFactory.getLogger(WhisperServerService.class);
@Override
public void initialize(Bootstrap<WhisperServerConfiguration> bootstrap) {
bootstrap.addCommand(new DeleteUserCommand());
bootstrap.addCommand(new CertificateCommand());
bootstrap.addCommand(new ZkParamsCommand());
bootstrap.addCommand(new ServerVersionCommand());
bootstrap.addCommand(new CheckDynamicConfigurationCommand());
bootstrap.addCommand(new SetUserDiscoverabilityCommand());
bootstrap.addCommand(new ReserveUsernameCommand());
bootstrap.addCommand(new AssignUsernameCommand());
}
@Override
public String getName() {
return "whisper-server";
}
@Override
public void run(WhisperServerConfiguration config, Environment environment) throws Exception {
final Clock clock = Clock.systemUTC();
final int availableProcessors = Runtime.getRuntime().availableProcessors();
UncaughtExceptionHandler.register();
SharedMetricRegistries.add(Constants.METRICS_NAME, environment.metrics());
final DistributionStatisticConfig defaultDistributionStatisticConfig = DistributionStatisticConfig.builder()
.percentiles(.75, .95, .99, .999)
.build();
{
final DatadogMeterRegistry datadogMeterRegistry = new DatadogMeterRegistry(
config.getDatadogConfiguration(), io.micrometer.core.instrument.Clock.SYSTEM);
datadogMeterRegistry.config().commonTags(
Tags.of(
"service", "chat",
"host", HostnameUtil.getLocalHostname(),
"version", WhisperServerVersion.getServerVersion(),
"env", config.getDatadogConfiguration().getEnvironment()))
.meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.REQUEST_COUNTER_NAME))
.meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.ANDROID_REQUEST_COUNTER_NAME))
.meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.DESKTOP_REQUEST_COUNTER_NAME))
.meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.IOS_REQUEST_COUNTER_NAME))
.meterFilter(new LettuceMetricsMeterFilter())
.meterFilter(new MeterFilter() {
@Override
public DistributionStatisticConfig configure(final Id id, final DistributionStatisticConfig config) {
return defaultDistributionStatisticConfig.merge(config);
}
});
Metrics.addRegistry(datadogMeterRegistry);
}
environment.lifecycle().manage(new MicrometerRegistryManager(Metrics.globalRegistry));
environment.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
environment.getObjectMapper().setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
environment.getObjectMapper().setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup =
new HeaderControlledResourceBundleLookup();
ConfiguredProfileBadgeConverter profileBadgeConverter = new ConfiguredProfileBadgeConverter(
clock, config.getBadges(), headerControlledResourceBundleLookup);
ResourceBundleLevelTranslator resourceBundleLevelTranslator = new ResourceBundleLevelTranslator(
headerControlledResourceBundleLookup);
DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient(
config.getDynamoDbClientConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
DynamoDbClient dynamoDbClient = DynamoDbFromConfig.client(
config.getDynamoDbClientConfiguration(),
software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider.create());
AmazonDynamoDB deletedAccountsLockDynamoDbClient = AmazonDynamoDBClientBuilder.standard()
.withRegion(config.getDynamoDbClientConfiguration().getRegion())
.withClientConfiguration(new ClientConfiguration().withClientExecutionTimeout(
((int) config.getDynamoDbClientConfiguration().getClientExecutionTimeout().toMillis()))
.withRequestTimeout(
(int) config.getDynamoDbClientConfiguration().getClientRequestTimeout().toMillis()))
.withCredentials(InstanceProfileCredentialsProvider.getInstance())
.build();
DeletedAccounts deletedAccounts = new DeletedAccounts(dynamoDbClient,
config.getDynamoDbTables().getDeletedAccounts().getTableName(),
config.getDynamoDbTables().getDeletedAccounts().getNeedsReconciliationIndexName());
DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager =
new DynamicConfigurationManager<>(config.getAppConfig().getApplication(),
config.getAppConfig().getEnvironment(),
config.getAppConfig().getConfigurationName(),
DynamicConfiguration.class);
BlockingQueue<Runnable> messageDeletionQueue = new LinkedBlockingQueue<>();
Metrics.gaugeCollectionSize(name(getClass(), "messageDeletionQueueSize"), Collections.emptyList(),
messageDeletionQueue);
ExecutorService messageDeletionAsyncExecutor = environment.lifecycle()
.executorService(name(getClass(), "messageDeletionAsyncExecutor-%d")).maxThreads(16)
.workQueue(messageDeletionQueue).build();
Accounts accounts = new Accounts(
dynamoDbClient,
dynamoDbAsyncClient,
config.getDynamoDbTables().getAccounts().getTableName(),
config.getDynamoDbTables().getAccounts().getPhoneNumberTableName(),
config.getDynamoDbTables().getAccounts().getPhoneNumberIdentifierTableName(),
config.getDynamoDbTables().getAccounts().getUsernamesTableName(),
config.getDynamoDbTables().getAccounts().getScanPageSize());
PhoneNumberIdentifiers phoneNumberIdentifiers = new PhoneNumberIdentifiers(dynamoDbClient,
config.getDynamoDbTables().getPhoneNumberIdentifiers().getTableName());
Profiles profiles = new Profiles(dynamoDbClient, dynamoDbAsyncClient,
config.getDynamoDbTables().getProfiles().getTableName());
Keys keys = new Keys(dynamoDbClient, config.getDynamoDbTables().getKeys().getTableName());
MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(dynamoDbClient, dynamoDbAsyncClient,
config.getDynamoDbTables().getMessages().getTableName(),
config.getDynamoDbTables().getMessages().getExpiration(),
messageDeletionAsyncExecutor);
RemoteConfigs remoteConfigs = new RemoteConfigs(dynamoDbClient,
config.getDynamoDbTables().getRemoteConfig().getTableName());
PushChallengeDynamoDb pushChallengeDynamoDb = new PushChallengeDynamoDb(dynamoDbClient,
config.getDynamoDbTables().getPushChallenge().getTableName());
ReportMessageDynamoDb reportMessageDynamoDb = new ReportMessageDynamoDb(dynamoDbClient,
config.getDynamoDbTables().getReportMessage().getTableName(),
config.getReportMessageConfiguration().getReportTtl());
VerificationCodeStore pendingAccounts = new VerificationCodeStore(dynamoDbClient,
config.getDynamoDbTables().getPendingAccounts().getTableName());
VerificationCodeStore pendingDevices = new VerificationCodeStore(dynamoDbClient,
config.getDynamoDbTables().getPendingDevices().getTableName());
RegistrationRecoveryPasswords registrationRecoveryPasswords = new RegistrationRecoveryPasswords(
config.getDynamoDbTables().getRegistrationRecovery().getTableName(),
config.getDynamoDbTables().getRegistrationRecovery().getExpiration(),
dynamoDbClient,
dynamoDbAsyncClient
);
reactor.util.Metrics.MicrometerConfiguration.useRegistry(Metrics.globalRegistry);
Schedulers.enableMetrics();
RedisClientFactory pubSubClientFactory = new RedisClientFactory("pubsub_cache",
config.getPubsubCacheConfiguration().getUrl(), config.getPubsubCacheConfiguration().getReplicaUrls(),
config.getPubsubCacheConfiguration().getCircuitBreakerConfiguration());
ReplicatedJedisPool pubsubClient = pubSubClientFactory.getRedisClientPool();
MicrometerOptions options = MicrometerOptions.builder().build();
ClientResources redisClientResources = ClientResources.builder()
.commandLatencyRecorder(new MicrometerCommandLatencyRecorder(Metrics.globalRegistry, options)).build();
ConnectionEventLogger.logConnectionEvents(redisClientResources);
FaultTolerantRedisCluster cacheCluster = new FaultTolerantRedisCluster("main_cache_cluster", config.getCacheClusterConfiguration(), redisClientResources);
FaultTolerantRedisCluster messagesCluster = new FaultTolerantRedisCluster("messages_cluster", config.getMessageCacheConfiguration().getRedisClusterConfiguration(), redisClientResources);
FaultTolerantRedisCluster clientPresenceCluster = new FaultTolerantRedisCluster("client_presence_cluster", config.getClientPresenceClusterConfiguration(), redisClientResources);
FaultTolerantRedisCluster metricsCluster = new FaultTolerantRedisCluster("metrics_cluster", config.getMetricsClusterConfiguration(), redisClientResources);
FaultTolerantRedisCluster pushSchedulerCluster = new FaultTolerantRedisCluster("push_scheduler", config.getPushSchedulerCluster(), redisClientResources);
FaultTolerantRedisCluster rateLimitersCluster = new FaultTolerantRedisCluster("rate_limiters", config.getRateLimitersCluster(), redisClientResources);
final BlockingQueue<Runnable> keyspaceNotificationDispatchQueue = new ArrayBlockingQueue<>(100_000);
Metrics.gaugeCollectionSize(name(getClass(), "keyspaceNotificationDispatchQueueSize"), Collections.emptyList(), keyspaceNotificationDispatchQueue);
final BlockingQueue<Runnable> receiptSenderQueue = new LinkedBlockingQueue<>();
Metrics.gaugeCollectionSize(name(getClass(), "receiptSenderQueue"), Collections.emptyList(), receiptSenderQueue);
final BlockingQueue<Runnable> fcmSenderQueue = new LinkedBlockingQueue<>();
Metrics.gaugeCollectionSize(name(getClass(), "fcmSenderQueue"), Collections.emptyList(), fcmSenderQueue);
ScheduledExecutorService recurringJobExecutor = environment.lifecycle()
.scheduledExecutorService(name(getClass(), "recurringJob-%d")).threads(6).build();
ScheduledExecutorService websocketScheduledExecutor = environment.lifecycle().scheduledExecutorService(name(getClass(), "websocket-%d")).threads(8).build();
ExecutorService keyspaceNotificationDispatchExecutor = environment.lifecycle().executorService(name(getClass(), "keyspaceNotification-%d")).maxThreads(16).workQueue(keyspaceNotificationDispatchQueue).build();
ExecutorService apnSenderExecutor = environment.lifecycle().executorService(name(getClass(), "apnSender-%d")).maxThreads(1).minThreads(1).build();
ExecutorService fcmSenderExecutor = environment.lifecycle().executorService(name(getClass(), "fcmSender-%d")).maxThreads(32).minThreads(32).workQueue(fcmSenderQueue).build();
ExecutorService backupServiceExecutor = environment.lifecycle().executorService(name(getClass(), "backupService-%d")).maxThreads(1).minThreads(1).build();
ExecutorService storageServiceExecutor = environment.lifecycle().executorService(name(getClass(), "storageService-%d")).maxThreads(1).minThreads(1).build();
ExecutorService accountDeletionExecutor = environment.lifecycle().executorService(name(getClass(), "accountCleaner-%d")).maxThreads(16).minThreads(16).build();
// TODO: generally speaking this is a DynamoDB I/O executor for the accounts table; we should eventually have a general executor for speaking to the accounts table, but most of the server is still synchronous so this isn't widely useful yet
ExecutorService batchIdentityCheckExecutor = environment.lifecycle().executorService(name(getClass(), "batchIdentityCheck-%d")).minThreads(32).maxThreads(32).build();
ExecutorService multiRecipientMessageExecutor = environment.lifecycle()
.executorService(name(getClass(), "multiRecipientMessage-%d")).minThreads(64).maxThreads(64).build();
ExecutorService subscriptionProcessorExecutor = environment.lifecycle()
.executorService(name(getClass(), "subscriptionProcessor-%d"))
.maxThreads(availableProcessors) // mostly this is IO bound so tying to number of processors is tenuous at best
.minThreads(availableProcessors) // mostly this is IO bound so tying to number of processors is tenuous at best
.allowCoreThreadTimeOut(true).
build();
ExecutorService receiptSenderExecutor = environment.lifecycle()
.executorService(name(getClass(), "receiptSender-%d"))
.maxThreads(2)
.minThreads(2)
.workQueue(receiptSenderQueue)
.rejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy())
.build();
ExecutorService registrationCallbackExecutor = environment.lifecycle()
.executorService(name(getClass(), "registration-%d"))
.maxThreads(2)
.minThreads(2)
.build();
final AdminEventLogger adminEventLogger = new GoogleCloudAdminEventLogger(
LoggingOptions.newBuilder().setProjectId(config.getAdminEventLoggingConfiguration().projectId())
.setCredentials(GoogleCredentials.fromStream(new ByteArrayInputStream(
config.getAdminEventLoggingConfiguration().credentials().getBytes(StandardCharsets.UTF_8))))
.build().getService(),
config.getAdminEventLoggingConfiguration().projectId(),
config.getAdminEventLoggingConfiguration().logName());
StripeManager stripeManager = new StripeManager(config.getStripe().apiKey(), subscriptionProcessorExecutor,
config.getStripe().idempotencyKeyGenerator(), config.getStripe().boostDescription(), config.getStripe()
.supportedCurrencies());
BraintreeManager braintreeManager = new BraintreeManager(config.getBraintree().merchantId(),
config.getBraintree().publicKey(), config.getBraintree().privateKey(), config.getBraintree().environment(),
config.getBraintree().supportedCurrencies(), config.getBraintree().merchantAccounts(),
config.getBraintree().graphqlUrl(), config.getBraintree().circuitBreaker(), subscriptionProcessorExecutor);
ExternalServiceCredentialsGenerator directoryCredentialsGenerator = DirectoryController.credentialsGenerator(
config.getDirectoryConfiguration().getDirectoryClientConfiguration());
ExternalServiceCredentialsGenerator directoryV2CredentialsGenerator = DirectoryV2Controller.credentialsGenerator(
config.getDirectoryV2Configuration().getDirectoryV2ClientConfiguration());
ExternalServiceCredentialsGenerator storageCredentialsGenerator = SecureStorageController.credentialsGenerator(
config.getSecureStorageServiceConfiguration());
ExternalServiceCredentialsGenerator backupCredentialsGenerator = SecureBackupController.credentialsGenerator(
config.getSecureBackupServiceConfiguration());
ExternalServiceCredentialsGenerator paymentsCredentialsGenerator = PaymentsController.credentialsGenerator(
config.getPaymentsServiceConfiguration());
ExternalServiceCredentialsGenerator artCredentialsGenerator = ArtController.credentialsGenerator(
config.getArtServiceConfiguration());
ExternalServiceCredentialsGenerator svr2CredentialsGenerator = SecureValueRecovery2Controller.credentialsGenerator(
config.getSvr2Configuration());
dynamicConfigurationManager.start();
ExperimentEnrollmentManager experimentEnrollmentManager = new ExperimentEnrollmentManager(dynamicConfigurationManager);
RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager = new RegistrationRecoveryPasswordsManager(registrationRecoveryPasswords);
UsernameHashZkProofVerifier usernameHashZkProofVerifier = new UsernameHashZkProofVerifier();
RegistrationServiceClient registrationServiceClient = new RegistrationServiceClient(config.getRegistrationServiceConfiguration().getHost(), config.getRegistrationServiceConfiguration().getPort(), config.getRegistrationServiceConfiguration().getApiKey(), config.getRegistrationServiceConfiguration().getRegistrationCaCertificate(), registrationCallbackExecutor);
SecureBackupClient secureBackupClient = new SecureBackupClient(backupCredentialsGenerator, backupServiceExecutor, config.getSecureBackupServiceConfiguration());
SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator, storageServiceExecutor, config.getSecureStorageServiceConfiguration());
ClientPresenceManager clientPresenceManager = new ClientPresenceManager(clientPresenceCluster, recurringJobExecutor, keyspaceNotificationDispatchExecutor);
DirectoryQueue directoryQueue = new DirectoryQueue(config.getDirectoryConfiguration().getSqsConfiguration());
StoredVerificationCodeManager pendingAccountsManager = new StoredVerificationCodeManager(pendingAccounts);
StoredVerificationCodeManager pendingDevicesManager = new StoredVerificationCodeManager(pendingDevices);
ProfilesManager profilesManager = new ProfilesManager(profiles, cacheCluster);
MessagesCache messagesCache = new MessagesCache(messagesCluster, messagesCluster, Clock.systemUTC(),
keyspaceNotificationDispatchExecutor, messageDeletionAsyncExecutor);
PushLatencyManager pushLatencyManager = new PushLatencyManager(metricsCluster, dynamicConfigurationManager);
ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster,
config.getReportMessageConfiguration().getCounterTtl());
MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, reportMessageManager,
messageDeletionAsyncExecutor);
DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts,
deletedAccountsLockDynamoDbClient, config.getDynamoDbTables().getDeletedAccountsLock().getTableName());
AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster,
deletedAccountsManager, directoryQueue, keys, messagesManager, profilesManager,
pendingAccountsManager, secureStorageClient, secureBackupClient, clientPresenceManager,
experimentEnrollmentManager, registrationRecoveryPasswordsManager, clock);
RemoteConfigsManager remoteConfigsManager = new RemoteConfigsManager(remoteConfigs);
DispatchManager dispatchManager = new DispatchManager(pubSubClientFactory, Optional.empty());
PubSubManager pubSubManager = new PubSubManager(pubsubClient, dispatchManager);
APNSender apnSender = new APNSender(apnSenderExecutor, config.getApnConfiguration());
FcmSender fcmSender = new FcmSender(fcmSenderExecutor, config.getFcmConfiguration().credentials());
ApnPushNotificationScheduler apnPushNotificationScheduler = new ApnPushNotificationScheduler(pushSchedulerCluster, apnSender, accountsManager);
PushNotificationManager pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender, apnPushNotificationScheduler, pushLatencyManager, dynamicConfigurationManager);
RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), rateLimitersCluster);
DynamicRateLimiters dynamicRateLimiters = new DynamicRateLimiters(rateLimitersCluster, dynamicConfigurationManager);
ProvisioningManager provisioningManager = new ProvisioningManager(pubSubManager);
IssuedReceiptsManager issuedReceiptsManager = new IssuedReceiptsManager(
config.getDynamoDbTables().getIssuedReceipts().getTableName(),
config.getDynamoDbTables().getIssuedReceipts().getExpiration(),
dynamoDbAsyncClient,
config.getDynamoDbTables().getIssuedReceipts().getGenerator());
RedeemedReceiptsManager redeemedReceiptsManager = new RedeemedReceiptsManager(
clock,
config.getDynamoDbTables().getRedeemedReceipts().getTableName(),
dynamoDbAsyncClient,
config.getDynamoDbTables().getRedeemedReceipts().getExpiration());
SubscriptionManager subscriptionManager = new SubscriptionManager(
config.getDynamoDbTables().getSubscriptions().getTableName(), dynamoDbAsyncClient);
final RegistrationLockVerificationManager registrationLockVerificationManager = new RegistrationLockVerificationManager(
accountsManager, clientPresenceManager, backupCredentialsGenerator, rateLimiters);
final PhoneVerificationTokenManager phoneVerificationTokenManager = new PhoneVerificationTokenManager(
registrationServiceClient, registrationRecoveryPasswordsManager);
final ReportedMessageMetricsListener reportedMessageMetricsListener = new ReportedMessageMetricsListener(
accountsManager);
reportMessageManager.addListener(reportedMessageMetricsListener);
final AccountAuthenticator accountAuthenticator = new AccountAuthenticator(accountsManager);
final DisabledPermittedAccountAuthenticator disabledPermittedAccountAuthenticator = new DisabledPermittedAccountAuthenticator(
accountsManager);
final MessageSender messageSender = new MessageSender(clientPresenceManager, messagesManager,
pushNotificationManager,
pushLatencyManager);
final ReceiptSender receiptSender = new ReceiptSender(accountsManager, messageSender, receiptSenderExecutor);
final TurnTokenGenerator turnTokenGenerator = new TurnTokenGenerator(dynamicConfigurationManager);
RecaptchaClient recaptchaClient = new RecaptchaClient(
config.getRecaptchaConfiguration().getProjectPath(),
config.getRecaptchaConfiguration().getCredentialConfigurationJson(),
dynamicConfigurationManager);
HttpClient hcaptchaHttpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build();
HCaptchaClient hCaptchaClient = new HCaptchaClient(config.getHCaptchaConfiguration().apiKey(), hcaptchaHttpClient, dynamicConfigurationManager);
CaptchaChecker captchaChecker = new CaptchaChecker(List.of(recaptchaClient, hCaptchaClient));
PushChallengeManager pushChallengeManager = new PushChallengeManager(pushNotificationManager, pushChallengeDynamoDb);
RateLimitChallengeManager rateLimitChallengeManager = new RateLimitChallengeManager(pushChallengeManager,
captchaChecker, dynamicRateLimiters);
MessagePersister messagePersister = new MessagePersister(messagesCache, messagesManager, accountsManager, dynamicConfigurationManager, Duration.ofMinutes(config.getMessageCacheConfiguration().getPersistDelayMinutes()));
ChangeNumberManager changeNumberManager = new ChangeNumberManager(messageSender, accountsManager);
final List<AccountDatabaseCrawlerListener> directoryReconciliationAccountDatabaseCrawlerListeners = new ArrayList<>();
final List<DeletedAccountsDirectoryReconciler> deletedAccountsDirectoryReconcilers = new ArrayList<>();
for (DirectoryServerConfiguration directoryServerConfiguration : config.getDirectoryConfiguration()
.getDirectoryServerConfiguration()) {
final DirectoryReconciliationClient directoryReconciliationClient = new DirectoryReconciliationClient(
directoryServerConfiguration);
final DirectoryReconciler directoryReconciler = new DirectoryReconciler(
directoryServerConfiguration.getReplicationName(), directoryReconciliationClient,
dynamicConfigurationManager);
// reconcilers are read-only
directoryReconciliationAccountDatabaseCrawlerListeners.add(directoryReconciler);
final DeletedAccountsDirectoryReconciler deletedAccountsDirectoryReconciler = new DeletedAccountsDirectoryReconciler(
directoryServerConfiguration.getReplicationName(), directoryReconciliationClient);
deletedAccountsDirectoryReconcilers.add(deletedAccountsDirectoryReconciler);
}
AccountDatabaseCrawlerCache directoryReconciliationAccountDatabaseCrawlerCache = new AccountDatabaseCrawlerCache(
cacheCluster, AccountDatabaseCrawlerCache.DIRECTORY_RECONCILER_PREFIX);
AccountDatabaseCrawler directoryReconciliationAccountDatabaseCrawler = new AccountDatabaseCrawler(
"Reconciliation crawler",
accountsManager,
directoryReconciliationAccountDatabaseCrawlerCache, directoryReconciliationAccountDatabaseCrawlerListeners,
config.getAccountDatabaseCrawlerConfiguration().getChunkSize(),
config.getAccountDatabaseCrawlerConfiguration().getChunkIntervalMs()
);
AccountDatabaseCrawlerCache accountCleanerAccountDatabaseCrawlerCache =
new AccountDatabaseCrawlerCache(cacheCluster, AccountDatabaseCrawlerCache.ACCOUNT_CLEANER_PREFIX);
AccountDatabaseCrawler accountCleanerAccountDatabaseCrawler = new AccountDatabaseCrawler("Account cleaner crawler",
accountsManager,
accountCleanerAccountDatabaseCrawlerCache, List.of(new AccountCleaner(accountsManager, accountDeletionExecutor)),
config.getAccountDatabaseCrawlerConfiguration().getChunkSize(),
config.getAccountDatabaseCrawlerConfiguration().getChunkIntervalMs()
);
// TODO listeners must be ordered so that ones that directly update accounts come last, so that read-only ones are not working with stale data
final List<AccountDatabaseCrawlerListener> accountDatabaseCrawlerListeners = List.of(
new NonNormalizedAccountCrawlerListener(accountsManager, metricsCluster),
new ContactDiscoveryWriter(accountsManager),
// PushFeedbackProcessor may update device properties
new PushFeedbackProcessor(accountsManager));
AccountDatabaseCrawlerCache accountDatabaseCrawlerCache = new AccountDatabaseCrawlerCache(cacheCluster,
AccountDatabaseCrawlerCache.GENERAL_PURPOSE_PREFIX);
AccountDatabaseCrawler accountDatabaseCrawler = new AccountDatabaseCrawler("General-purpose account crawler",
accountsManager,
accountDatabaseCrawlerCache, accountDatabaseCrawlerListeners,
config.getAccountDatabaseCrawlerConfiguration().getChunkSize(),
config.getAccountDatabaseCrawlerConfiguration().getChunkIntervalMs()
);
DeletedAccountsTableCrawler deletedAccountsTableCrawler = new DeletedAccountsTableCrawler(deletedAccountsManager, deletedAccountsDirectoryReconcilers, cacheCluster, recurringJobExecutor);
HttpClient currencyClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build();
FixerClient fixerClient = new FixerClient(currencyClient, config.getPaymentsServiceConfiguration().getFixerApiKey());
CoinMarketCapClient coinMarketCapClient = new CoinMarketCapClient(currencyClient, config.getPaymentsServiceConfiguration().getCoinMarketCapApiKey(), config.getPaymentsServiceConfiguration().getCoinMarketCapCurrencyIds());
CurrencyConversionManager currencyManager = new CurrencyConversionManager(fixerClient, coinMarketCapClient,
cacheCluster, config.getPaymentsServiceConfiguration().getPaymentCurrencies(), Clock.systemUTC());
environment.lifecycle().manage(apnSender);
environment.lifecycle().manage(apnPushNotificationScheduler);
environment.lifecycle().manage(pubSubManager);
environment.lifecycle().manage(accountDatabaseCrawler);
environment.lifecycle().manage(directoryReconciliationAccountDatabaseCrawler);
environment.lifecycle().manage(accountCleanerAccountDatabaseCrawler);
environment.lifecycle().manage(deletedAccountsTableCrawler);
environment.lifecycle().manage(messagesCache);
environment.lifecycle().manage(messagePersister);
environment.lifecycle().manage(clientPresenceManager);
environment.lifecycle().manage(currencyManager);
environment.lifecycle().manage(directoryQueue);
environment.lifecycle().manage(registrationServiceClient);
StaticCredentialsProvider cdnCredentialsProvider = StaticCredentialsProvider
.create(AwsBasicCredentials.create(
config.getCdnConfiguration().getAccessKey(),
config.getCdnConfiguration().getAccessSecret()));
S3Client cdnS3Client = S3Client.builder()
.credentialsProvider(cdnCredentialsProvider)
.region(Region.of(config.getCdnConfiguration().getRegion()))
.build();
PostPolicyGenerator profileCdnPolicyGenerator = new PostPolicyGenerator(config.getCdnConfiguration().getRegion(),
config.getCdnConfiguration().getBucket(), config.getCdnConfiguration().getAccessKey());
PolicySigner profileCdnPolicySigner = new PolicySigner(config.getCdnConfiguration().getAccessSecret(),
config.getCdnConfiguration().getRegion());
ServerSecretParams zkSecretParams = new ServerSecretParams(config.getZkConfig().getServerSecret());
ServerZkProfileOperations zkProfileOperations = new ServerZkProfileOperations(zkSecretParams);
ServerZkAuthOperations zkAuthOperations = new ServerZkAuthOperations(zkSecretParams);
ServerZkReceiptOperations zkReceiptOperations = new ServerZkReceiptOperations(zkSecretParams);
AuthFilter<BasicCredentials, AuthenticatedAccount> accountAuthFilter = new BasicCredentialAuthFilter.Builder<AuthenticatedAccount>().setAuthenticator(
accountAuthenticator).buildAuthFilter();
AuthFilter<BasicCredentials, DisabledPermittedAuthenticatedAccount> disabledPermittedAccountAuthFilter = new BasicCredentialAuthFilter.Builder<DisabledPermittedAuthenticatedAccount>().setAuthenticator(
disabledPermittedAccountAuthenticator).buildAuthFilter();
environment.servlets()
.addFilter("RemoteDeprecationFilter", new RemoteDeprecationFilter(dynamicConfigurationManager))
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
environment.jersey().register(new RequestStatisticsFilter(TrafficSource.HTTP));
environment.jersey().register(MultiRecipientMessageProvider.class);
environment.jersey().register(new MetricsApplicationEventListener(TrafficSource.HTTP));
environment.jersey()
.register(new PolymorphicAuthDynamicFeature<>(ImmutableMap.of(AuthenticatedAccount.class, accountAuthFilter,
DisabledPermittedAuthenticatedAccount.class, disabledPermittedAccountAuthFilter)));
environment.jersey().register(new PolymorphicAuthValueFactoryProvider.Binder<>(
ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class)));
environment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager));
environment.jersey().register(new TimestampResponseFilter());
///
WebSocketEnvironment<AuthenticatedAccount> webSocketEnvironment = new WebSocketEnvironment<>(environment,
config.getWebSocketConfiguration(), 90000);
webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(accountAuthenticator));
webSocketEnvironment.setConnectListener(
new AuthenticatedConnectListener(receiptSender, messagesManager, pushNotificationManager,
clientPresenceManager, websocketScheduledExecutor));
webSocketEnvironment.jersey()
.register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager));
webSocketEnvironment.jersey().register(new RequestStatisticsFilter(TrafficSource.WEBSOCKET));
webSocketEnvironment.jersey().register(MultiRecipientMessageProvider.class);
webSocketEnvironment.jersey().register(new MetricsApplicationEventListener(TrafficSource.WEBSOCKET));
webSocketEnvironment.jersey().register(new KeepAliveController(clientPresenceManager));
// these should be common, but use @Auth DisabledPermittedAccount, which isnt supported yet on websocket
environment.jersey().register(
new AccountController(pendingAccountsManager, accountsManager, rateLimiters,
registrationServiceClient, dynamicConfigurationManager, turnTokenGenerator, config.getTestDevices(),
captchaChecker, pushNotificationManager, changeNumberManager, registrationLockVerificationManager,
registrationRecoveryPasswordsManager, usernameHashZkProofVerifier, clock));
environment.jersey().register(new KeysController(rateLimiters, keys, accountsManager));
boolean registeredSpamFilter = false;
ReportSpamTokenProvider reportSpamTokenProvider = null;
for (final SpamFilter filter : ServiceLoader.load(SpamFilter.class)) {
if (filter.getClass().isAnnotationPresent(FilterSpam.class)) {
try {
filter.configure(config.getSpamFilterConfiguration().getEnvironment());
ReportSpamTokenProvider thisProvider = filter.getReportSpamTokenProvider();
if (reportSpamTokenProvider == null) {
reportSpamTokenProvider = thisProvider;
} else if (thisProvider != null) {
log.info("Multiple spam report token providers found. Using the first.");
}
filter.getReportedMessageListeners().forEach(reportMessageManager::addListener);
environment.lifecycle().manage(filter);
environment.jersey().register(filter);
webSocketEnvironment.jersey().register(filter);
log.info("Registered spam filter: {}", filter.getClass().getName());
registeredSpamFilter = true;
} catch (final Exception e) {
log.warn("Failed to register spam filter: {}", filter.getClass().getName(), e);
}
} else {
log.warn("Spam filter {} not annotated with @FilterSpam and will not be installed",
filter.getClass().getName());
}
if (filter instanceof RateLimitChallengeListener) {
log.info("Registered rate limit challenge listener: {}", filter.getClass().getName());
rateLimitChallengeManager.addListener((RateLimitChallengeListener) filter);
}
}
if (!registeredSpamFilter) {
log.warn("No spam filters installed");
}
if (reportSpamTokenProvider == null) {
log.warn("No spam-reporting token providers found; using default (no-op) provider as a default");
reportSpamTokenProvider = ReportSpamTokenProvider.noop();
}
final List<Object> commonControllers = Lists.newArrayList(
new AccountControllerV2(accountsManager, changeNumberManager, phoneVerificationTokenManager,
registrationLockVerificationManager, rateLimiters),
new ArtController(rateLimiters, artCredentialsGenerator),
new AttachmentControllerV2(rateLimiters, config.getAwsAttachmentsConfiguration().getAccessKey(), config.getAwsAttachmentsConfiguration().getAccessSecret(), config.getAwsAttachmentsConfiguration().getRegion(), config.getAwsAttachmentsConfiguration().getBucket()),
new AttachmentControllerV3(rateLimiters, config.getGcpAttachmentsConfiguration().getDomain(), config.getGcpAttachmentsConfiguration().getEmail(), config.getGcpAttachmentsConfiguration().getMaxSizeInBytes(), config.getGcpAttachmentsConfiguration().getPathPrefix(), config.getGcpAttachmentsConfiguration().getRsaSigningKey()),
new CertificateController(new CertificateGenerator(config.getDeliveryCertificate().getCertificate(), config.getDeliveryCertificate().getPrivateKey(), config.getDeliveryCertificate().getExpiresDays()), zkAuthOperations, clock),
new ChallengeController(rateLimitChallengeManager),
new DeviceController(pendingDevicesManager, accountsManager, messagesManager, keys, rateLimiters, config.getMaxDevices()),
new DirectoryController(directoryCredentialsGenerator),
new DirectoryV2Controller(directoryV2CredentialsGenerator),
new DonationController(clock, zkReceiptOperations, redeemedReceiptsManager, accountsManager, config.getBadges(),
ReceiptCredentialPresentation::new),
new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccountsManager, messagesManager, pushNotificationManager, reportMessageManager, multiRecipientMessageExecutor,
reportSpamTokenProvider),
new PaymentsController(currencyManager, paymentsCredentialsGenerator),
new ProfileController(clock, rateLimiters, accountsManager, profilesManager, dynamicConfigurationManager,
profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner,
config.getCdnConfiguration().getBucket(), zkProfileOperations, batchIdentityCheckExecutor),
new ProvisioningController(rateLimiters, provisioningManager),
new RegistrationController(accountsManager, phoneVerificationTokenManager, registrationLockVerificationManager,
rateLimiters),
new RemoteConfigController(remoteConfigsManager, adminEventLogger,
config.getRemoteConfigConfiguration().getAuthorizedTokens(),
config.getRemoteConfigConfiguration().getGlobalConfig()),
new SecureBackupController(backupCredentialsGenerator, accountsManager),
new SecureStorageController(storageCredentialsGenerator),
new SecureValueRecovery2Controller(svr2CredentialsGenerator),
new StickerController(rateLimiters, config.getCdnConfiguration().getAccessKey(),
config.getCdnConfiguration().getAccessSecret(), config.getCdnConfiguration().getRegion(),
config.getCdnConfiguration().getBucket())
);
if (config.getSubscription() != null && config.getOneTimeDonations() != null) {
commonControllers.add(new SubscriptionController(clock, config.getSubscription(), config.getOneTimeDonations(),
subscriptionManager, stripeManager, braintreeManager, zkReceiptOperations, issuedReceiptsManager, profileBadgeConverter,
resourceBundleLevelTranslator));
}
for (Object controller : commonControllers) {
environment.jersey().register(controller);
webSocketEnvironment.jersey().register(controller);
}
WebSocketEnvironment<AuthenticatedAccount> provisioningEnvironment = new WebSocketEnvironment<>(environment,
webSocketEnvironment.getRequestLog(), 60000);
provisioningEnvironment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager));
provisioningEnvironment.setConnectListener(new ProvisioningConnectListener(pubSubManager));
provisioningEnvironment.jersey().register(new MetricsApplicationEventListener(TrafficSource.WEBSOCKET));
provisioningEnvironment.jersey().register(new KeepAliveController(clientPresenceManager));
registerCorsFilter(environment);
registerExceptionMappers(environment, webSocketEnvironment, provisioningEnvironment);
environment.jersey().property(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE);
webSocketEnvironment.jersey().property(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE);
provisioningEnvironment.jersey().property(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE);
WebSocketResourceProviderFactory<AuthenticatedAccount> webSocketServlet = new WebSocketResourceProviderFactory<>(
webSocketEnvironment, AuthenticatedAccount.class, config.getWebSocketConfiguration());
WebSocketResourceProviderFactory<AuthenticatedAccount> provisioningServlet = new WebSocketResourceProviderFactory<>(
provisioningEnvironment, AuthenticatedAccount.class, config.getWebSocketConfiguration());
ServletRegistration.Dynamic websocket = environment.servlets().addServlet("WebSocket", webSocketServlet);
ServletRegistration.Dynamic provisioning = environment.servlets().addServlet("Provisioning", provisioningServlet);
websocket.addMapping("/v1/websocket/");
websocket.setAsyncSupported(true);
provisioning.addMapping("/v1/websocket/provisioning/");
provisioning.setAsyncSupported(true);
environment.admin().addTask(new SetRequestLoggingEnabledTask());
environment.admin().addTask(new SetCrawlerAccelerationTask(accountDatabaseCrawlerCache));
environment.healthChecks().register("cacheCluster", new RedisClusterHealthCheck(cacheCluster));
environment.lifecycle().manage(new ApplicationShutdownMonitor(Metrics.globalRegistry));
environment.metrics().register(name(CpuUsageGauge.class, "cpu"), new CpuUsageGauge(3, TimeUnit.SECONDS));
environment.metrics().register(name(FreeMemoryGauge.class, "free_memory"), new FreeMemoryGauge());
environment.metrics().register(name(NetworkSentGauge.class, "bytes_sent"), new NetworkSentGauge());
environment.metrics().register(name(NetworkReceivedGauge.class, "bytes_received"), new NetworkReceivedGauge());
environment.metrics().register(name(FileDescriptorGauge.class, "fd_count"), new FileDescriptorGauge());
environment.metrics().register(name(MaxFileDescriptorGauge.class, "max_fd_count"), new MaxFileDescriptorGauge());
environment.metrics()
.register(name(OperatingSystemMemoryGauge.class, "buffers"), new OperatingSystemMemoryGauge("Buffers"));
environment.metrics()
.register(name(OperatingSystemMemoryGauge.class, "cached"), new OperatingSystemMemoryGauge("Cached"));
BufferPoolGauges.registerMetrics();
GarbageCollectionGauges.registerMetrics();
}
private void registerExceptionMappers(Environment environment,
WebSocketEnvironment<AuthenticatedAccount> webSocketEnvironment,
WebSocketEnvironment<AuthenticatedAccount> provisioningEnvironment) {
List.of(
new LoggingUnhandledExceptionMapper(),
new CompletionExceptionMapper(),
new IOExceptionMapper(),
new RateLimitExceededExceptionMapper(),
new InvalidWebsocketAddressExceptionMapper(),
new DeviceLimitExceededExceptionMapper(),
new ServerRejectedExceptionMapper(),
new ImpossiblePhoneNumberExceptionMapper(),
new NonNormalizedPhoneNumberExceptionMapper(),
new JsonMappingExceptionMapper()
).forEach(exceptionMapper -> {
environment.jersey().register(exceptionMapper);
webSocketEnvironment.jersey().register(exceptionMapper);
provisioningEnvironment.jersey().register(exceptionMapper);
});
}
private void registerCorsFilter(Environment environment) {
FilterRegistration.Dynamic filter = environment.servlets().addFilter("CORS", CrossOriginFilter.class);
filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
filter.setInitParameter("allowedOrigins", "*");
filter.setInitParameter("allowedHeaders", "Content-Type,Authorization,X-Requested-With,Content-Length,Accept,Origin,X-Signal-Agent");
filter.setInitParameter("allowedMethods", "GET,PUT,POST,DELETE,OPTIONS");
filter.setInitParameter("preflightMaxAge", "5184000");
filter.setInitParameter("allowCredentials", "true");
}
public static void main(String[] args) throws Exception {
new WhisperServerService().run(args);
}
}

View File

@ -1,16 +0,0 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
public interface AccountAndAuthenticatedDeviceHolder {
Account getAccount();
Device getAuthenticatedDevice();
}

View File

@ -1,25 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;
import java.util.Optional;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
public class AccountAuthenticator extends BaseAccountAuthenticator implements
Authenticator<BasicCredentials, AuthenticatedAccount> {
public AccountAuthenticator(AccountsManager accountsManager) {
super(accountsManager);
}
@Override
public Optional<AuthenticatedAccount> authenticate(BasicCredentials basicCredentials) {
return super.authenticate(basicCredentials, true);
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.util.Base64;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
public class Anonymous {
private final byte[] unidentifiedSenderAccessKey;
public Anonymous(String header) {
try {
this.unidentifiedSenderAccessKey = Base64.getDecoder().decode(header);
} catch (IllegalArgumentException e) {
throw new WebApplicationException(e, Response.Status.UNAUTHORIZED);
}
}
public byte[] getAccessKey() {
return unidentifiedSenderAccessKey;
}
}

View File

@ -1,100 +0,0 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import com.google.common.annotations.VisibleForTesting;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.Pair;
/**
* This {@link WebsocketRefreshRequirementProvider} observes intra-request changes in {@link Account#isEnabled()} and
* {@link Device#isEnabled()}.
* <p>
* If a change in {@link Account#isEnabled()} or any associated {@link Device#isEnabled()} is observed, then any active
* WebSocket connections for the account must be closed in order for clients to get a refreshed
* {@link io.dropwizard.auth.Auth} object with a current device list.
*
* @see AuthenticatedAccount
* @see DisabledPermittedAuthenticatedAccount
*/
public class AuthEnablementRefreshRequirementProvider implements WebsocketRefreshRequirementProvider {
private final AccountsManager accountsManager;
private static final Logger logger = LoggerFactory.getLogger(AuthEnablementRefreshRequirementProvider.class);
private static final String ACCOUNT_UUID = AuthEnablementRefreshRequirementProvider.class.getName() + ".accountUuid";
private static final String DEVICES_ENABLED = AuthEnablementRefreshRequirementProvider.class.getName() + ".devicesEnabled";
public AuthEnablementRefreshRequirementProvider(final AccountsManager accountsManager) {
this.accountsManager = accountsManager;
}
@VisibleForTesting
static Map<Long, Boolean> buildDevicesEnabledMap(final Account account) {
return account.getDevices().stream().collect(Collectors.toMap(Device::getId, Device::isEnabled));
}
@Override
public void handleRequestFiltered(final RequestEvent requestEvent) {
if (requestEvent.getUriInfo().getMatchedResourceMethod().getInvocable().getHandlingMethod().getAnnotation(ChangesDeviceEnabledState.class) != null) {
// The authenticated principal, if any, will be available after filters have run.
// Now that the account is known, capture a snapshot of `isEnabled` for the account's devices before carrying out
// the requests business logic.
ContainerRequestUtil.getAuthenticatedAccount(requestEvent.getContainerRequest()).ifPresent(account ->
setAccount(requestEvent.getContainerRequest(), account));
}
}
public static void setAccount(final ContainerRequest containerRequest, final Account account) {
containerRequest.setProperty(ACCOUNT_UUID, account.getUuid());
containerRequest.setProperty(DEVICES_ENABLED, buildDevicesEnabledMap(account));
}
@Override
public List<Pair<UUID, Long>> handleRequestFinished(final RequestEvent requestEvent) {
// Now that the request is finished, check whether `isEnabled` changed for any of the devices. If the value did
// change or if a devices was added or removed, all devices must disconnect and reauthenticate.
if (requestEvent.getContainerRequest().getProperty(DEVICES_ENABLED) != null) {
@SuppressWarnings("unchecked") final Map<Long, Boolean> initialDevicesEnabled =
(Map<Long, Boolean>) requestEvent.getContainerRequest().getProperty(DEVICES_ENABLED);
return accountsManager.getByAccountIdentifier((UUID) requestEvent.getContainerRequest().getProperty(ACCOUNT_UUID)).map(account -> {
final Set<Long> deviceIdsToDisplace;
final Map<Long, Boolean> currentDevicesEnabled = buildDevicesEnabledMap(account);
if (!initialDevicesEnabled.equals(currentDevicesEnabled)) {
deviceIdsToDisplace = new HashSet<>(initialDevicesEnabled.keySet());
deviceIdsToDisplace.addAll(currentDevicesEnabled.keySet());
} else {
deviceIdsToDisplace = Collections.emptySet();
}
return deviceIdsToDisplace.stream()
.map(deviceId -> new Pair<>(account.getUuid(), deviceId))
.collect(Collectors.toList());
}).orElseGet(() -> {
logger.error("Request had account, but it is no longer present");
return Collections.emptyList();
});
} else
return Collections.emptyList();
}
}

View File

@ -1,44 +0,0 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.security.Principal;
import java.util.function.Supplier;
import javax.security.auth.Subject;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.Pair;
public class AuthenticatedAccount implements Principal, AccountAndAuthenticatedDeviceHolder {
private final Supplier<Pair<Account, Device>> accountAndDevice;
public AuthenticatedAccount(final Supplier<Pair<Account, Device>> accountAndDevice) {
this.accountAndDevice = accountAndDevice;
}
@Override
public Account getAccount() {
return accountAndDevice.get().first();
}
@Override
public Device getAuthenticatedDevice() {
return accountAndDevice.get().second();
}
// Principal implementation
@Override
public String getName() {
return null;
}
@Override
public boolean implies(final Subject subject) {
return false;
}
}

View File

@ -1,182 +0,0 @@
/*
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import static com.codahale.metrics.MetricRegistry.name;
import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.auth.basic.BasicCredentials;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tags;
import java.time.Clock;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.RefreshingAccountAndDeviceSupplier;
import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.textsecuregcm.util.Util;
public class BaseAccountAuthenticator {
private static final String AUTHENTICATION_COUNTER_NAME = name(BaseAccountAuthenticator.class, "authentication");
private static final String ENABLED_NOT_REQUIRED_AUTHENTICATION_COUNTER_NAME = name(BaseAccountAuthenticator.class,
"enabledNotRequiredAuthentication");
private static final String AUTHENTICATION_SUCCEEDED_TAG_NAME = "succeeded";
private static final String AUTHENTICATION_FAILURE_REASON_TAG_NAME = "reason";
private static final String ENABLED_TAG_NAME = "enabled";
private static final String AUTHENTICATION_HAS_STORY_CAPABILITY = "hasStoryCapability";
private static final String STORY_ADOPTION_COUNTER_NAME = name(BaseAccountAuthenticator.class, "storyAdoption");
private static final String DAYS_SINCE_LAST_SEEN_DISTRIBUTION_NAME = name(BaseAccountAuthenticator.class, "daysSinceLastSeen");
private static final String IS_PRIMARY_DEVICE_TAG = "isPrimary";
@VisibleForTesting
static final char DEVICE_ID_SEPARATOR = '.';
private final AccountsManager accountsManager;
private final Clock clock;
public BaseAccountAuthenticator(AccountsManager accountsManager) {
this(accountsManager, Clock.systemUTC());
}
@VisibleForTesting
public BaseAccountAuthenticator(AccountsManager accountsManager, Clock clock) {
this.accountsManager = accountsManager;
this.clock = clock;
}
static Pair<String, Long> getIdentifierAndDeviceId(final String basicUsername) {
final String identifier;
final long deviceId;
final int deviceIdSeparatorIndex = basicUsername.indexOf(DEVICE_ID_SEPARATOR);
if (deviceIdSeparatorIndex == -1) {
identifier = basicUsername;
deviceId = Device.MASTER_ID;
} else {
identifier = basicUsername.substring(0, deviceIdSeparatorIndex);
deviceId = Long.parseLong(basicUsername.substring(deviceIdSeparatorIndex + 1));
}
return new Pair<>(identifier, deviceId);
}
public Optional<AuthenticatedAccount> authenticate(BasicCredentials basicCredentials, boolean enabledRequired) {
boolean succeeded = false;
String failureReason = null;
boolean hasStoryCapability = false;
try {
final UUID accountUuid;
final long deviceId;
{
final Pair<String, Long> identifierAndDeviceId = getIdentifierAndDeviceId(basicCredentials.getUsername());
accountUuid = UUID.fromString(identifierAndDeviceId.first());
deviceId = identifierAndDeviceId.second();
}
Optional<Account> account = accountsManager.getByAccountIdentifier(accountUuid);
if (account.isEmpty()) {
failureReason = "noSuchAccount";
return Optional.empty();
}
hasStoryCapability = account.map(Account::isStoriesSupported).orElse(false);
Optional<Device> device = account.get().getDevice(deviceId);
if (device.isEmpty()) {
failureReason = "noSuchDevice";
return Optional.empty();
}
if (enabledRequired) {
final boolean deviceDisabled = !device.get().isEnabled();
if (deviceDisabled) {
failureReason = "deviceDisabled";
}
final boolean accountDisabled = !account.get().isEnabled();
if (accountDisabled) {
failureReason = "accountDisabled";
}
if (accountDisabled || deviceDisabled) {
return Optional.empty();
}
} else {
Metrics.counter(ENABLED_NOT_REQUIRED_AUTHENTICATION_COUNTER_NAME,
ENABLED_TAG_NAME, String.valueOf(device.get().isEnabled() && account.get().isEnabled()),
IS_PRIMARY_DEVICE_TAG, String.valueOf(device.get().isMaster()))
.increment();
}
SaltedTokenHash deviceSaltedTokenHash = device.get().getAuthTokenHash();
if (deviceSaltedTokenHash.verify(basicCredentials.getPassword())) {
succeeded = true;
Account authenticatedAccount = updateLastSeen(account.get(), device.get());
if (deviceSaltedTokenHash.getVersion() != SaltedTokenHash.CURRENT_VERSION) {
authenticatedAccount = accountsManager.updateDeviceAuthentication(
authenticatedAccount,
device.get(),
SaltedTokenHash.generateFor(basicCredentials.getPassword())); // new credentials have current version
}
return Optional.of(new AuthenticatedAccount(
new RefreshingAccountAndDeviceSupplier(authenticatedAccount, device.get().getId(), accountsManager)));
}
return Optional.empty();
} catch (IllegalArgumentException | InvalidAuthorizationHeaderException iae) {
failureReason = "invalidHeader";
return Optional.empty();
} finally {
Tags tags = Tags.of(
AUTHENTICATION_SUCCEEDED_TAG_NAME, String.valueOf(succeeded));
if (StringUtils.isNotBlank(failureReason)) {
tags = tags.and(AUTHENTICATION_FAILURE_REASON_TAG_NAME, failureReason);
}
Metrics.counter(AUTHENTICATION_COUNTER_NAME, tags).increment();
Tags storyTags = Tags.of(AUTHENTICATION_HAS_STORY_CAPABILITY, String.valueOf(hasStoryCapability));
Metrics.counter(STORY_ADOPTION_COUNTER_NAME, storyTags).increment();
}
}
@VisibleForTesting
public Account updateLastSeen(Account account, Device device) {
// compute a non-negative integer between 0 and 86400.
long n = Util.ensureNonNegativeLong(account.getUuid().getLeastSignificantBits());
final long lastSeenOffsetSeconds = n % ChronoUnit.DAYS.getDuration().toSeconds();
// produce a truncated timestamp which is either today at UTC midnight
// or yesterday at UTC midnight, based on per-user randomized offset used.
final long todayInMillisWithOffset = Util.todayInMillisGivenOffsetFromNow(clock, Duration.ofSeconds(lastSeenOffsetSeconds).negated());
// only update the device's last seen time when it falls behind the truncated timestamp.
// this ensure a few things:
// (1) each account will only update last-seen at most once per day
// (2) these updates will occur throughout the day rather than all occurring at UTC midnight.
if (device.getLastSeen() < todayInMillisWithOffset) {
Metrics.summary(DAYS_SINCE_LAST_SEEN_DISTRIBUTION_NAME, IS_PRIMARY_DEVICE_TAG, String.valueOf(device.isMaster()))
.record(Duration.ofMillis(todayInMillisWithOffset - device.getLastSeen()).toDays());
return accountsManager.updateDeviceLastSeen(account, device, Util.todayInMillis(clock));
}
return account;
}
}

View File

@ -1,94 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.util.Base64;
import org.apache.commons.lang3.StringUtils;
import org.whispersystems.textsecuregcm.util.Pair;
public class BasicAuthorizationHeader {
private final String username;
private final long deviceId;
private final String password;
private BasicAuthorizationHeader(final String username, final long deviceId, final String password) {
this.username = username;
this.deviceId = deviceId;
this.password = password;
}
public static BasicAuthorizationHeader fromString(final String header) throws InvalidAuthorizationHeaderException {
try {
if (StringUtils.isBlank(header)) {
throw new InvalidAuthorizationHeaderException("Blank header");
}
final int spaceIndex = header.indexOf(' ');
if (spaceIndex == -1) {
throw new InvalidAuthorizationHeaderException("Invalid authorization header: " + header);
}
final String authorizationType = header.substring(0, spaceIndex);
if (!"Basic".equals(authorizationType)) {
throw new InvalidAuthorizationHeaderException("Unsupported authorization method: " + authorizationType);
}
final String credentials;
try {
credentials = new String(Base64.getDecoder().decode(header.substring(spaceIndex + 1)));
} catch (final IndexOutOfBoundsException e) {
throw new InvalidAuthorizationHeaderException("Missing credentials");
}
if (StringUtils.isEmpty(credentials)) {
throw new InvalidAuthorizationHeaderException("Bad decoded value: " + credentials);
}
final int credentialSeparatorIndex = credentials.indexOf(':');
if (credentialSeparatorIndex == -1) {
throw new InvalidAuthorizationHeaderException("Badly-formatted credentials: " + credentials);
}
final String usernameComponent = credentials.substring(0, credentialSeparatorIndex);
final String username;
final long deviceId;
{
final Pair<String, Long> identifierAndDeviceId =
BaseAccountAuthenticator.getIdentifierAndDeviceId(usernameComponent);
username = identifierAndDeviceId.first();
deviceId = identifierAndDeviceId.second();
}
final String password = credentials.substring(credentialSeparatorIndex + 1);
if (StringUtils.isAnyBlank(username, password)) {
throw new InvalidAuthorizationHeaderException("Username or password were blank");
}
return new BasicAuthorizationHeader(username, deviceId, password);
} catch (final IllegalArgumentException | IndexOutOfBoundsException e) {
throw new InvalidAuthorizationHeaderException(e);
}
}
public String getUsername() {
return username;
}
public long getDeviceId() {
return deviceId;
}
public String getPassword() {
return password;
}
}

View File

@ -1,61 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import java.security.InvalidKeyException;
import java.util.Base64;
import java.util.concurrent.TimeUnit;
import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECPrivateKey;
import org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate;
import org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
public class CertificateGenerator {
private final ECPrivateKey privateKey;
private final int expiresDays;
private final ServerCertificate serverCertificate;
public CertificateGenerator(byte[] serverCertificate, ECPrivateKey privateKey, int expiresDays)
throws InvalidProtocolBufferException
{
this.privateKey = privateKey;
this.expiresDays = expiresDays;
this.serverCertificate = ServerCertificate.parseFrom(serverCertificate);
}
public byte[] createFor(Account account, Device device, boolean includeE164) throws InvalidKeyException {
SenderCertificate.Certificate.Builder builder = SenderCertificate.Certificate.newBuilder()
.setSenderDevice(Math.toIntExact(device.getId()))
.setExpires(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(expiresDays))
.setIdentityKey(ByteString.copyFrom(Base64.getDecoder().decode(account.getIdentityKey())))
.setSigner(serverCertificate)
.setSenderUuid(account.getUuid().toString());
if (includeE164) {
builder.setSender(account.getNumber());
}
byte[] certificate = builder.build().toByteArray();
byte[] signature;
try {
signature = Curve.calculateSignature(privateKey, certificate);
} catch (org.signal.libsignal.protocol.InvalidKeyException e) {
throw new InvalidKeyException(e);
}
return SenderCertificate.newBuilder()
.setCertificate(ByteString.copyFrom(certificate))
.setSignature(ByteString.copyFrom(signature))
.build()
.toByteArray();
}
}

View File

@ -1,20 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that an endpoint may change the "enabled" state of one or more devices associated with an account, and that
* any websockets associated with the account may need to be refreshed after a call to that endpoint.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ChangesDeviceEnabledState {
}

View File

@ -1,30 +0,0 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.util.Base64;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
public class CombinedUnidentifiedSenderAccessKeys {
private final byte[] combinedUnidentifiedSenderAccessKeys;
public CombinedUnidentifiedSenderAccessKeys(String header) {
try {
this.combinedUnidentifiedSenderAccessKeys = Base64.getDecoder().decode(header);
if (this.combinedUnidentifiedSenderAccessKeys == null || this.combinedUnidentifiedSenderAccessKeys.length != 16) {
throw new WebApplicationException("Invalid combined unidentified sender access keys", Status.UNAUTHORIZED);
}
} catch (IllegalArgumentException e) {
throw new WebApplicationException(e, Response.Status.UNAUTHORIZED);
}
}
public byte[] getAccessKeys() {
return combinedUnidentifiedSenderAccessKeys;
}
}

View File

@ -1,21 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import org.glassfish.jersey.server.ContainerRequest;
import org.whispersystems.textsecuregcm.storage.Account;
import javax.ws.rs.core.SecurityContext;
import java.util.Optional;
class ContainerRequestUtil {
static Optional<Account> getAuthenticatedAccount(final ContainerRequest request) {
return Optional.ofNullable(request.getSecurityContext())
.map(SecurityContext::getUserPrincipal)
.map(principal -> principal instanceof AccountAndAuthenticatedDeviceHolder
? ((AccountAndAuthenticatedDeviceHolder) principal).getAccount() : null);
}
}

View File

@ -1,26 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;
import java.util.Optional;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
public class DisabledPermittedAccountAuthenticator extends BaseAccountAuthenticator implements
Authenticator<BasicCredentials, DisabledPermittedAuthenticatedAccount> {
public DisabledPermittedAccountAuthenticator(AccountsManager accountsManager) {
super(accountsManager);
}
@Override
public Optional<DisabledPermittedAuthenticatedAccount> authenticate(BasicCredentials credentials) {
Optional<AuthenticatedAccount> account = super.authenticate(credentials, false);
return account.map(DisabledPermittedAuthenticatedAccount::new);
}
}

View File

@ -1,42 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.security.Principal;
import javax.security.auth.Subject;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
public class DisabledPermittedAuthenticatedAccount implements Principal, AccountAndAuthenticatedDeviceHolder {
private final AuthenticatedAccount authenticatedAccount;
public DisabledPermittedAuthenticatedAccount(final AuthenticatedAccount authenticatedAccount) {
this.authenticatedAccount = authenticatedAccount;
}
@Override
public Account getAccount() {
return authenticatedAccount.getAccount();
}
@Override
public Device getAuthenticatedDevice() {
return authenticatedAccount.getAuthenticatedDevice();
}
// Principal implementation
@Override
public String getName() {
return null;
}
@Override
public boolean implies(Subject subject) {
return false;
}
}

View File

@ -1,11 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
public record ExternalServiceCredentials(String username, String password) {
}

View File

@ -1,207 +0,0 @@
/*
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import static java.util.Objects.requireNonNull;
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmac256ToHexString;
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmac256TruncatedToHexString;
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmacHexStringsEqual;
import java.time.Clock;
import java.util.Optional;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
public class ExternalServiceCredentialsGenerator {
private static final int TRUNCATE_LENGTH = 10;
private static final String DELIMITER = ":";
private final byte[] key;
private final byte[] userDerivationKey;
private final boolean prependUsername;
private final boolean truncateSignature;
private final Clock clock;
public static ExternalServiceCredentialsGenerator.Builder builder(final byte[] key) {
return new Builder(key);
}
private ExternalServiceCredentialsGenerator(
final byte[] key,
final byte[] userDerivationKey,
final boolean prependUsername,
final boolean truncateSignature,
final Clock clock) {
this.key = requireNonNull(key);
this.userDerivationKey = requireNonNull(userDerivationKey);
this.prependUsername = prependUsername;
this.truncateSignature = truncateSignature;
this.clock = requireNonNull(clock);
}
/**
* A convenience method for the case of identity in the form of {@link UUID}.
* @param uuid identity to generate credentials for
* @return an instance of {@link ExternalServiceCredentials}
*/
public ExternalServiceCredentials generateForUuid(final UUID uuid) {
return generateFor(uuid.toString());
}
/**
* Generates `ExternalServiceCredentials` for the given identity following this generator's configuration.
* @param identity identity string to generate credentials for
* @return an instance of {@link ExternalServiceCredentials}
*/
public ExternalServiceCredentials generateFor(final String identity) {
final String username = shouldDeriveUsername()
? hmac256TruncatedToHexString(userDerivationKey, identity, TRUNCATE_LENGTH)
: identity;
final long currentTimeSeconds = currentTimeSeconds();
final String dataToSign = username + DELIMITER + currentTimeSeconds;
final String signature = truncateSignature
? hmac256TruncatedToHexString(key, dataToSign, TRUNCATE_LENGTH)
: hmac256ToHexString(key, dataToSign);
final String token = (prependUsername ? dataToSign : currentTimeSeconds) + DELIMITER + signature;
return new ExternalServiceCredentials(username, token);
}
/**
* In certain cases, identity (as it was passed to `generateFor` method)
* is a part of the signature (`password`, in terms of `ExternalServiceCredentials`) string itself.
* For such cases, this method returns the value of the identity string.
* @param password `password` part of `ExternalServiceCredentials`
* @return non-empty optional with an identity string value, or empty if value can't be extracted.
*/
public Optional<String> identityFromSignature(final String password) {
// for some generators, identity in the clear is just not a part of the password
if (!prependUsername || shouldDeriveUsername() || StringUtils.isBlank(password)) {
return Optional.empty();
}
// checking for the case of unexpected format
return StringUtils.countMatches(password, DELIMITER) == 2
? Optional.of(password.substring(0, password.indexOf(DELIMITER)))
: Optional.empty();
}
/**
* Given an instance of {@link ExternalServiceCredentials} object, checks that the password
* matches the username taking into accound this generator's configuration.
* @param credentials an instance of {@link ExternalServiceCredentials}
* @return An optional with a timestamp (seconds) of when the credentials were generated,
* or an empty optional if the password doesn't match the username for any reason (including malformed data)
*/
public Optional<Long> validateAndGetTimestamp(final ExternalServiceCredentials credentials) {
final String[] parts = requireNonNull(credentials).password().split(DELIMITER);
final String timestampSeconds;
final String actualSignature;
// making sure password format matches our expectations based on the generator configuration
if (parts.length == 3 && prependUsername) {
final String username = parts[0];
// username has to match the one from `credentials`
if (!credentials.username().equals(username)) {
return Optional.empty();
}
timestampSeconds = parts[1];
actualSignature = parts[2];
} else if (parts.length == 2 && !prependUsername) {
timestampSeconds = parts[0];
actualSignature = parts[1];
} else {
// unexpected password format
return Optional.empty();
}
final String signedData = credentials.username() + DELIMITER + timestampSeconds;
final String expectedSignature = truncateSignature
? hmac256TruncatedToHexString(key, signedData, TRUNCATE_LENGTH)
: hmac256ToHexString(key, signedData);
// if the signature is valid it's safe to parse the `timestampSeconds` string into Long
return hmacHexStringsEqual(expectedSignature, actualSignature)
? Optional.of(Long.valueOf(timestampSeconds))
: Optional.empty();
}
/**
* Given an instance of {@link ExternalServiceCredentials} object and the max allowed age for those credentials,
* checks if credentials are valid and not expired.
* @param credentials an instance of {@link ExternalServiceCredentials}
* @param maxAgeSeconds age in seconds
* @return An optional with a timestamp (seconds) of when the credentials were generated,
* or an empty optional if the password doesn't match the username for any reason (including malformed data)
*/
public Optional<Long> validateAndGetTimestamp(final ExternalServiceCredentials credentials, final long maxAgeSeconds) {
return validateAndGetTimestamp(credentials)
.filter(ts -> currentTimeSeconds() - ts <= maxAgeSeconds);
}
private boolean shouldDeriveUsername() {
return userDerivationKey.length > 0;
}
private long currentTimeSeconds() {
return clock.instant().getEpochSecond();
}
public static class Builder {
private final byte[] key;
private byte[] userDerivationKey = new byte[0];
private boolean prependUsername = true;
private boolean truncateSignature = true;
private Clock clock = Clock.systemUTC();
private Builder(final byte[] key) {
this.key = requireNonNull(key);
}
public Builder withUserDerivationKey(final byte[] userDerivationKey) {
Validate.isTrue(requireNonNull(userDerivationKey).length > 0, "userDerivationKey must not be empty");
this.userDerivationKey = userDerivationKey;
return this;
}
public Builder withClock(final Clock clock) {
this.clock = requireNonNull(clock);
return this;
}
public Builder prependUsername(final boolean prependUsername) {
this.prependUsername = prependUsername;
return this;
}
public Builder truncateSignature(final boolean truncateSignature) {
this.truncateSignature = truncateSignature;
return this;
}
public ExternalServiceCredentialsGenerator build() {
return new ExternalServiceCredentialsGenerator(
key, userDerivationKey, prependUsername, truncateSignature, clock);
}
}
}

View File

@ -1,19 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;
public class InvalidAuthorizationHeaderException extends WebApplicationException {
public InvalidAuthorizationHeaderException(String s) {
super(s, Status.UNAUTHORIZED);
}
public InvalidAuthorizationHeaderException(Exception e) {
super(e, Status.UNAUTHORIZED);
}
}

View File

@ -1,80 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.security.MessageDigest;
import java.util.Optional;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class OptionalAccess {
public static final String UNIDENTIFIED = "Unidentified-Access-Key";
public static void verify(Optional<Account> requestAccount,
Optional<Anonymous> accessKey,
Optional<Account> targetAccount,
String deviceSelector)
{
try {
verify(requestAccount, accessKey, targetAccount);
if (!deviceSelector.equals("*")) {
long deviceId = Long.parseLong(deviceSelector);
Optional<Device> targetDevice = targetAccount.get().getDevice(deviceId);
if (targetDevice.isPresent() && targetDevice.get().isEnabled()) {
return;
}
if (requestAccount.isPresent()) {
throw new NotFoundException();
} else {
throw new NotAuthorizedException(Response.Status.UNAUTHORIZED);
}
}
} catch (NumberFormatException e) {
throw new WebApplicationException(Response.status(422).build());
}
}
public static void verify(Optional<Account> requestAccount,
Optional<Anonymous> accessKey,
Optional<Account> targetAccount)
{
if (requestAccount.isPresent() && targetAccount.isPresent() && targetAccount.get().isEnabled()) {
return;
}
//noinspection ConstantConditions
if (requestAccount.isPresent() && (targetAccount.isEmpty() || (targetAccount.isPresent() && !targetAccount.get().isEnabled()))) {
throw new NotFoundException();
}
if (accessKey.isPresent() && targetAccount.isPresent() && targetAccount.get().isEnabled() && targetAccount.get().isUnrestrictedUnidentifiedAccess()) {
return;
}
if (accessKey.isPresent() &&
targetAccount.isPresent() &&
targetAccount.get().getUnidentifiedAccessKey().isPresent() &&
targetAccount.get().isEnabled() &&
MessageDigest.isEqual(accessKey.get().getAccessKey(), targetAccount.get().getUnidentifiedAccessKey().get()))
{
return;
}
throw new NotAuthorizedException(Response.Status.UNAUTHORIZED);
}
}

View File

@ -1,46 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.util.Pair;
public class PhoneNumberChangeRefreshRequirementProvider implements WebsocketRefreshRequirementProvider {
private static final String INITIAL_NUMBER_KEY =
PhoneNumberChangeRefreshRequirementProvider.class.getName() + ".initialNumber";
@Override
public void handleRequestFiltered(final RequestEvent requestEvent) {
ContainerRequestUtil.getAuthenticatedAccount(requestEvent.getContainerRequest())
.ifPresent(account -> requestEvent.getContainerRequest().setProperty(INITIAL_NUMBER_KEY, account.getNumber()));
}
@Override
public List<Pair<UUID, Long>> handleRequestFinished(final RequestEvent requestEvent) {
final String initialNumber = (String) requestEvent.getContainerRequest().getProperty(INITIAL_NUMBER_KEY);
if (initialNumber != null) {
final Optional<Account> maybeAuthenticatedAccount =
ContainerRequestUtil.getAuthenticatedAccount(requestEvent.getContainerRequest());
return maybeAuthenticatedAccount
.filter(account -> !initialNumber.equals(account.getNumber()))
.map(account -> account.getDevices().stream()
.map(device -> new Pair<>(account.getUuid(), device.getId()))
.collect(Collectors.toList()))
.orElse(Collections.emptyList());
} else {
return Collections.emptyList();
}
}
}

View File

@ -1,98 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.security.MessageDigest;
import java.time.Duration;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.ServerErrorException;
import javax.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.entities.PhoneVerificationRequest;
import org.whispersystems.textsecuregcm.entities.RegistrationSession;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
import org.whispersystems.textsecuregcm.storage.RegistrationRecoveryPasswordsManager;
public class PhoneVerificationTokenManager {
private static final Logger logger = LoggerFactory.getLogger(PhoneVerificationTokenManager.class);
private static final Duration REGISTRATION_RPC_TIMEOUT = Duration.ofSeconds(15);
private static final long VERIFICATION_TIMEOUT_SECONDS = REGISTRATION_RPC_TIMEOUT.plusSeconds(1).getSeconds();
private final RegistrationServiceClient registrationServiceClient;
private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager;
public PhoneVerificationTokenManager(final RegistrationServiceClient registrationServiceClient,
final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager) {
this.registrationServiceClient = registrationServiceClient;
this.registrationRecoveryPasswordsManager = registrationRecoveryPasswordsManager;
}
/**
* Checks if a {@link PhoneVerificationRequest} has a token that verifies the caller has confirmed access to the e164
* number
*
* @param number the e164 presented for verification
* @param request the request with exactly one verification token (RegistrationService sessionId or registration
* recovery password)
* @return if verification was successful, returns the verification type
* @throws BadRequestException if the number does not match the sessionIds number
* @throws NotAuthorizedException if the session is not verified
* @throws ForbiddenException if the recovery password is not valid
* @throws InterruptedException if verification did not complete before a timeout
*/
public PhoneVerificationRequest.VerificationType verify(final String number, final PhoneVerificationRequest request)
throws InterruptedException {
final PhoneVerificationRequest.VerificationType verificationType = request.verificationType();
switch (verificationType) {
case SESSION -> verifyBySessionId(number, request.decodeSessionId());
case RECOVERY_PASSWORD -> verifyByRecoveryPassword(number, request.recoveryPassword());
}
return verificationType;
}
private void verifyBySessionId(final String number, final byte[] sessionId) throws InterruptedException {
try {
final RegistrationSession session = registrationServiceClient
.getSession(sessionId, REGISTRATION_RPC_TIMEOUT)
.get(VERIFICATION_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.orElseThrow(() -> new NotAuthorizedException("session not verified"));
if (!MessageDigest.isEqual(number.getBytes(), session.number().getBytes())) {
throw new BadRequestException("number does not match session");
}
if (!session.verified()) {
throw new NotAuthorizedException("session not verified");
}
} catch (final CancellationException | ExecutionException | TimeoutException e) {
logger.error("Registration service failure", e);
throw new ServerErrorException(Response.Status.SERVICE_UNAVAILABLE);
}
}
private void verifyByRecoveryPassword(final String number, final byte[] recoveryPassword)
throws InterruptedException {
try {
final boolean verified = registrationRecoveryPasswordsManager.verify(number, recoveryPassword)
.get(VERIFICATION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
if (!verified) {
throw new ForbiddenException("recoveryPassword couldn't be verified");
}
} catch (final ExecutionException | TimeoutException e) {
throw new ServerErrorException(Response.Status.SERVICE_UNAVAILABLE);
}
}
}

View File

@ -1,103 +0,0 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
import com.google.common.annotations.VisibleForTesting;
import javax.annotation.Nullable;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.util.Util;
public class RegistrationLockVerificationManager {
@VisibleForTesting
public static final int FAILURE_HTTP_STATUS = 423;
private static final String LOCKED_ACCOUNT_COUNTER_NAME =
name(RegistrationLockVerificationManager.class, "lockedAccount");
private static final String LOCK_REASON_TAG_NAME = "lockReason";
private static final String ALREADY_LOCKED_TAG_NAME = "alreadyLocked";
private final AccountsManager accounts;
private final ClientPresenceManager clientPresenceManager;
private final ExternalServiceCredentialsGenerator backupServiceCredentialGenerator;
private final RateLimiters rateLimiters;
public RegistrationLockVerificationManager(
final AccountsManager accounts, final ClientPresenceManager clientPresenceManager,
final ExternalServiceCredentialsGenerator backupServiceCredentialGenerator, final RateLimiters rateLimiters) {
this.accounts = accounts;
this.clientPresenceManager = clientPresenceManager;
this.backupServiceCredentialGenerator = backupServiceCredentialGenerator;
this.rateLimiters = rateLimiters;
}
/**
* Verifies the given registration lock credentials against the accounts current registration lock, if any
*
* @param account
* @param clientRegistrationLock
* @throws RateLimitExceededException
* @throws WebApplicationException
*/
public void verifyRegistrationLock(final Account account, @Nullable final String clientRegistrationLock)
throws RateLimitExceededException, WebApplicationException {
final StoredRegistrationLock existingRegistrationLock = account.getRegistrationLock();
final ExternalServiceCredentials existingBackupCredentials =
backupServiceCredentialGenerator.generateForUuid(account.getUuid());
if (!existingRegistrationLock.requiresClientRegistrationLock()) {
return;
}
if (!Util.isEmpty(clientRegistrationLock)) {
rateLimiters.getPinLimiter().validate(account.getNumber());
}
final String phoneNumber = account.getNumber();
if (!existingRegistrationLock.verify(clientRegistrationLock)) {
// At this point, the client verified ownership of the phone number but doesnt have the reglock PIN.
// Freezing the existing account credentials will definitively start the reglock timeout.
// Until the timeout, the current reglock can still be supplied,
// along with phone number verification, to restore access.
/*
boolean alreadyLocked = existingAccount.hasLockedCredentials();
Metrics.counter(LOCKED_ACCOUNT_COUNTER_NAME,
LOCK_REASON_TAG_NAME, "verifiedNumberFailedReglock",
ALREADY_LOCKED_TAG_NAME, Boolean.toString(alreadyLocked))
.increment();
final Account updatedAccount;
if (!alreadyLocked) {
updatedAccount = accounts.update(existingAccount, Account::lockAuthenticationCredentials);
} else {
updatedAccount = existingAccount;
}
List<Long> deviceIds = updatedAccount.getDevices().stream().map(Device::getId).toList();
clientPresenceManager.disconnectAllPresences(updatedAccount.getUuid(), deviceIds);
*/
throw new WebApplicationException(Response.status(FAILURE_HTTP_STATUS)
.entity(new RegistrationLockFailure(existingRegistrationLock.getTimeRemaining(),
existingRegistrationLock.needsFailureCredentials() ? existingBackupCredentials : null))
.build());
}
rateLimiters.getPinLimiter().clear(phoneNumber);
}
}

View File

@ -1,75 +0,0 @@
/*
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HexFormat;
import org.signal.libsignal.protocol.kdf.HKDF;
public record SaltedTokenHash(String hash, String salt) {
public enum Version {
V1,
V2,
}
public static final Version CURRENT_VERSION = Version.V2;
private static final String V2_PREFIX = "2.";
private static final byte[] AUTH_TOKEN_HKDF_INFO = "authtoken".getBytes(StandardCharsets.UTF_8);
private static final int SALT_SIZE = 16;
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
public static SaltedTokenHash generateFor(final String token) {
final String salt = generateSalt();
final String hash = calculateV2Hash(salt, token);
return new SaltedTokenHash(hash, salt);
}
public Version getVersion() {
return hash.startsWith(V2_PREFIX) ? Version.V2 : Version.V1;
}
public boolean verify(final String token) {
final String theirValue = switch (getVersion()) {
case V1 -> calculateV1Hash(salt, token);
case V2 -> calculateV2Hash(salt, token);
};
return MessageDigest.isEqual(
theirValue.getBytes(StandardCharsets.UTF_8),
hash.getBytes(StandardCharsets.UTF_8));
}
private static String generateSalt() {
final byte[] salt = new byte[SALT_SIZE];
SECURE_RANDOM.nextBytes(salt);
return HexFormat.of().formatHex(salt);
}
private static String calculateV1Hash(final String salt, final String token) {
try {
return HexFormat.of()
.formatHex(MessageDigest.getInstance("SHA1").digest((salt + token).getBytes(StandardCharsets.UTF_8)));
} catch (final NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
private static String calculateV2Hash(final String salt, final String token) {
final byte[] secret = HKDF.deriveSecrets(
token.getBytes(StandardCharsets.UTF_8), // key
salt.getBytes(StandardCharsets.UTF_8), // salt
AUTH_TOKEN_HKDF_INFO,
32);
return V2_PREFIX + HexFormat.of().formatHex(secret);
}
}

View File

@ -1,69 +0,0 @@
/*
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import com.google.common.annotations.VisibleForTesting;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import org.whispersystems.textsecuregcm.util.Util;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class StoredRegistrationLock {
private final Optional<String> registrationLock;
private final Optional<String> registrationLockSalt;
private final long lastSeen;
/**
* @return milliseconds since the last time the account was seen.
*/
private long timeSinceLastSeen() {
return System.currentTimeMillis() - lastSeen;
}
/**
* @return true if the registration lock and salt are both set.
*/
private boolean hasLockAndSalt() {
return registrationLock.isPresent() && registrationLockSalt.isPresent();
}
public StoredRegistrationLock(Optional<String> registrationLock, Optional<String> registrationLockSalt, long lastSeen) {
this.registrationLock = registrationLock;
this.registrationLockSalt = registrationLockSalt;
this.lastSeen = lastSeen;
}
public boolean requiresClientRegistrationLock() {
boolean hasTimeRemaining = getTimeRemaining() >= 0;
return hasLockAndSalt() && hasTimeRemaining;
}
public boolean needsFailureCredentials() {
return hasLockAndSalt();
}
public long getTimeRemaining() {
return TimeUnit.DAYS.toMillis(7) - timeSinceLastSeen();
}
public boolean verify(@Nullable String clientRegistrationLock) {
if (hasLockAndSalt() && Util.nonEmpty(clientRegistrationLock)) {
SaltedTokenHash credentials = new SaltedTokenHash(registrationLock.get(), registrationLockSalt.get());
return credentials.verify(clientRegistrationLock);
} else {
return false;
}
}
@VisibleForTesting
public StoredRegistrationLock forTime(long timestamp) {
return new StoredRegistrationLock(registrationLock, registrationLockSalt, timestamp);
}
}

View File

@ -1,30 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import java.security.MessageDigest;
import java.time.Duration;
import javax.annotation.Nullable;
import org.whispersystems.textsecuregcm.util.Util;
public record StoredVerificationCode(String code,
long timestamp,
String pushCode,
@Nullable byte[] sessionId) {
public static final Duration EXPIRATION = Duration.ofMinutes(10);
public boolean isValid(String theirCodeString) {
if (Util.isEmpty(code) || Util.isEmpty(theirCodeString)) {
return false;
}
byte[] ourCode = code.getBytes();
byte[] theirCode = theirCodeString.getBytes();
return MessageDigest.isEqual(ourCode, theirCode);
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright 2013-2020 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import java.util.List;
public class TurnToken {
@JsonProperty
private String username;
@JsonProperty
private String password;
@JsonProperty
private List<String> urls;
public TurnToken(String username, String password, List<String> urls) {
this.username = username;
this.password = password;
this.urls = urls;
}
@VisibleForTesting
List<String> getUrls() {
return urls;
}
}

Some files were not shown because too many files have changed in this diff Show More