5
5
*/
6
6
7
7
#include < boost/redis/connection.hpp>
8
+ #include < boost/redis/ignore.hpp>
8
9
9
10
#include < boost/asio/ssl/host_name_verification.hpp>
11
+ #include < boost/asio/steady_timer.hpp>
10
12
#include < boost/system/error_code.hpp>
13
+
14
+ #include < cstddef>
15
+ #include < string_view>
11
16
#define BOOST_TEST_MODULE conn_tls
12
17
#include < boost/test/included/unit_test.hpp>
13
18
14
19
#include " common.hpp"
15
20
16
21
namespace net = boost::asio;
17
-
18
- using connection = boost::redis::connection;
19
- using boost::redis::request;
20
- using boost::redis::response;
21
- using boost::redis::config;
22
+ using namespace boost ::redis;
23
+ using namespace std ::chrono_literals;
22
24
using boost::system::error_code;
23
25
26
+ namespace {
27
+
24
28
// CA certificate that signed the test server's certificate.
25
29
// This is a self-signed CA created for testing purposes.
26
30
// This must match tools/tls/ca.crt contents
27
31
static constexpr const char * ca_certificate = R"%( -----BEGIN CERTIFICATE-----
28
- MIIFSzCCAzOgAwIBAgIUNd7VUuGK4+ylzCOrmeckg2+TqX8wDQYJKoZIhvcNAQEL
29
- BQAwNTETMBEGA1UECgwKUmVkaXMgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNhdGUg
30
- QXV0aG9yaXR5MB4XDTI0MDMzMTE0MjUyM1oXDTM0MDMyOTE0MjUyM1owNTETMBEG
31
- A1UECgwKUmVkaXMgVGVzdDEeMBwGA1UEAwwVQ2VydGlmaWNhdGUgQXV0aG9yaXR5
32
- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5AMV5V66wt+MM4+oCzH0
33
- xPi++j23p8AOa0o3dxNd4tm5y++gAdKfoxj7oh32ZuYHA5V+sGNEalN/b3GlKXMm
34
- ThdVPSwqOQduny19wrb126ZeQXCfqwgSZQ+rgzaIYpw8/GRRuLDunmsdaR2eiptp
35
- dbv6g6P/aIF6P9mfuekwCC9KBCV6ftqOEnzulNLVw4JjY0rKB9NZqONKVMfWpNyC
36
- zJLCkGmza7BOpybhloZIxGJz033yCjDvIQr9GUWsA5rU9LdUiL+F1W0pWkIel1qo
37
- Evo0EIl3+EOcSSzETI7NPHgnSzNau39ZShV4UBj2lw0DWeNcobeMBQ8ItmqEU6V0
38
- gCEqfUnt10bGIDdmV3D5FKPgvhFvEjQULnblLeLDQ6XDFf+xbGEVjvTzVkLjvyKm
39
- H2D+SKw2O+eDU/0+xhpAf+QsWlm6pmvKWjXI5wK1rh2yssBK2pmY3LuuZCdGrvXb
40
- KX4j/4S9qMr43Hmyoyz0gE5I5rplqot8TvT9O/JsgQYd9fYSvdB+HbqAlJzpBZFl
41
- xbVBXxl0AlDFwQtNMX5ylEQPvYVDKA1M+DTqRTgQKctTfccwvovY3YMV7m5YoODZ
42
- ya2YSBRfQim6VsC+QPYs7p2dk1larIoMMaTaU02oMY+qT2d/eyhWKBv5W9LuowTQ
43
- bWa3ZhWN8lXriPgJOQnZ6iUCAwEAAaNTMFEwHQYDVR0OBBYEFCpEPlClLrgu1zFN
44
- Fmas5G4ybNRJMB8GA1UdIwQYMBaAFCpEPlClLrgu1zFNFmas5G4ybNRJMA8GA1Ud
45
- EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAFLl1NZHp0NT5Av4GKmsJFeI
46
- cJOgcIygjR4SBGDAxyPqVpZk0x1q64gJsfOe1ARyI4olQPqO08FZMeB+VBYuqR3S
47
- fEVQZz2FT5U7IVAEZwWHOcWkrrVpEZC6PZktYJ7Yqju6+ic93inoPrHhGNZ5XA/Y
48
- GSfwriWkyWm2SOk35ChFH67MbPWmve8CRAXRmrOCByXwXF87wdqVYZUvH9xDe6WU
49
- snFWXVHr2NA7Re8ZIGp7yJOwwW+CZagepNCPUDwnI0fWOahtOTzonIjq8bfgTZPx
50
- 2e7lBuAr9tVMpoeyUytVOlNJDojZAtKOpfMwhAG8ydhk+78aK07VVbnSYVhv7ctU
51
- kkkldqP/S3lBlWo44oOxenwLc9vDQNh64py7eQTD7Qv+TjqAG0ljHIDbVqlkQsgR
52
- pQsu7keG9O1xASSTLZVZN2/alNewpqE/eFRfPM3mtUiTiIZvSxiQnWQMbKofAZH5
53
- HwhVli4RKWRWPqpof4GFNkB8XwfBE+gdlFuWtyg0oRyV3sJ6Zn7E+lUpbQX4CFx3
54
- 97vekaFNBchNYMcP3TZ9LwxTx1xOWZ5HHrHyzASG3uz2rqwAsEmdRbmK03KfEQyQ
55
- YpNY718btZ1D6lLino9VMgzaPhUs79bHC64O4ncl7hRclK9qa3KLQdCG1cbIR7G0
56
- 2XVYrfsnPHX0CsPDIy7L
32
+ MIIDhzCCAm+gAwIBAgIUZGttu4o/Exs08EHCneeD3gHw7KkwDQYJKoZIhvcNAQEL
33
+ BQAwUjELMAkGA1UEBhMCRVMxGjAYBgNVBAoMEUJvb3N0LlJlZGlzIENJIENBMQsw
34
+ CQYDVQQLDAJJVDEaMBgGA1UEAwwRYm9vc3QtcmVkaXMtY2ktY2EwIBcNMjUwNjA3
35
+ MTI0NzUwWhgPMjA4MDAzMTAxMjQ3NTBaMFIxCzAJBgNVBAYTAkVTMRowGAYDVQQK
36
+ DBFCb29zdC5SZWRpcyBDSSBDQTELMAkGA1UECwwCSVQxGjAYBgNVBAMMEWJvb3N0
37
+ LXJlZGlzLWNpLWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu7XV
38
+ sOoHB2J/5VtyJmMOzxhBbHKyQgW1YnMvYIb1JqIm7VuICA831SUw76n3j8mIK3zz
39
+ FfK2eYyUWf4Uo2j3uxmXDyjujqzIaUJNLcB53CQXkmIbqDigNhzUTPZ5A2MQ7xT+
40
+ t1eDbjsZ7XIM+aTShgtrpyxiccsgPJ3/XXme2RrqKeNvYsTYY6pquWZdyLOg/LOH
41
+ IeSJyL1/eQDRu/GsZjnR8UOE6uHfbjrLWls7Tifj/1IueVYCEhQZpJSWS8aUMLBZ
42
+ fi+t9YMCCK4DGy+6QlznGgVqdFFbTUt2C7tzqz+iF5dxJ8ogKMUPEeFrWiZpozoS
43
+ t60jV8fKwdXz854jLQIDAQABo1MwUTAdBgNVHQ4EFgQU2SoWvvZUW8JiDXtyuXZK
44
+ deaYYBswHwYDVR0jBBgwFoAU2SoWvvZUW8JiDXtyuXZKdeaYYBswDwYDVR0TAQH/
45
+ BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAqY4hGcdCFFPL4zveSDhR9H/akjae
46
+ uXbpo/9sHZd8e3Y4BtD8K05xa3417H9u5+S2XtyLQg5MON6J2LZueQEtE3wiR3ja
47
+ QIWbizqp8W54O5hTLQs6U/mWggfuL2R/HUw7ab4M8JobwHNEMK/WKZW71z0So/kk
48
+ W3wC0+1RH2PjMOZrCIflsD7EXYKIIr9afypAbhCQmCfu/GELuNx+LmaPi5JP4TTE
49
+ tDdhzWL04JLcZnA0uXb2Mren1AR9yKYH2I5tg5kQ3Bn/6v9+JiUhiejP3Vcbw84D
50
+ yFwRzN54bLanrJNILJhHPwnNIABXOtGUV05SZbYazJpiMst1a6eqDZhv/Q==
57
51
-----END CERTIFICATE-----)%" ;
58
52
59
53
static config make_tls_config ()
@@ -65,13 +59,14 @@ static config make_tls_config()
65
59
return cfg;
66
60
}
67
61
68
- BOOST_AUTO_TEST_CASE (ping_internal_ssl_context)
62
+ // Using the default TLS context allows establishing TLS connections and execute requests
63
+ BOOST_AUTO_TEST_CASE (exec_default_ssl_context)
69
64
{
70
65
auto const cfg = make_tls_config ();
71
- std::string const in = " Kabuf" ;
66
+ constexpr std::string_view ping_value = " Kabuf" ;
72
67
73
68
request req;
74
- req.push (" PING" , in );
69
+ req.push (" PING" , ping_value );
75
70
76
71
response<std::string> resp;
77
72
@@ -82,30 +77,39 @@ BOOST_AUTO_TEST_CASE(ping_internal_ssl_context)
82
77
// that is not trusted by default - skip verification.
83
78
conn.next_layer ().set_verify_mode (net::ssl::verify_none);
84
79
85
- conn.async_exec (req, resp, [&](error_code ec, auto ) {
86
- BOOST_TEST (ec == std::error_code ());
80
+ bool exec_finished = false , run_finished = false ;
81
+
82
+ conn.async_exec (req, resp, [&](error_code ec, std::size_t ) {
83
+ exec_finished = true ;
84
+ BOOST_TEST (ec == error_code ());
87
85
conn.cancel ();
88
86
});
89
87
90
- conn.async_run (cfg, {}, [](auto ) { });
88
+ conn.async_run (cfg, {}, [&](error_code ec) {
89
+ run_finished = true ;
90
+ BOOST_TEST (ec == net::error::operation_aborted);
91
+ });
91
92
92
- ioc.run ( );
93
+ ioc.run_for (test_timeout );
93
94
94
- BOOST_CHECK_EQUAL (in, std::get<0 >(resp).value ());
95
+ BOOST_TEST (exec_finished);
96
+ BOOST_TEST (run_finished);
97
+ BOOST_TEST (std::get<0 >(resp).value () == ping_value);
95
98
}
96
99
97
- BOOST_AUTO_TEST_CASE (ping_custom_ssl_context)
100
+ // Users can pass a custom context with TLS config
101
+ BOOST_AUTO_TEST_CASE (exec_custom_ssl_context)
98
102
{
99
103
auto const cfg = make_tls_config ();
100
- std::string const in = " Kabuf" ;
104
+ constexpr std::string_view ping_value = " Kabuf" ;
101
105
102
106
request req;
103
- req.push (" PING" , in );
107
+ req.push (" PING" , ping_value );
104
108
105
109
response<std::string> resp;
106
110
107
111
net::io_context ioc;
108
- net::ssl::context ctx{boost::asio ::ssl::context::tls_client};
112
+ net::ssl::context ctx{net ::ssl::context::tls_client};
109
113
110
114
// Configure the SSL context to trust the CA that signed the server's certificate.
111
115
// The test certificate uses "redis" as its common name, regardless of the actual server's hostname
@@ -115,14 +119,74 @@ BOOST_AUTO_TEST_CASE(ping_custom_ssl_context)
115
119
116
120
connection conn{ioc, std::move (ctx)};
117
121
118
- conn.async_exec (req, resp, [&](auto ec, auto ) {
119
- BOOST_TEST (ec == std::error_code ());
122
+ bool exec_finished = false , run_finished = false ;
123
+
124
+ conn.async_exec (req, resp, [&](error_code ec, std::size_t ) {
125
+ exec_finished = true ;
126
+ BOOST_TEST (ec == error_code ());
120
127
conn.cancel ();
121
128
});
122
129
123
- conn.async_run (cfg, {}, [](auto ) { });
130
+ conn.async_run (cfg, {}, [&](error_code ec) {
131
+ run_finished = true ;
132
+ BOOST_TEST (ec == net::error::operation_aborted);
133
+ });
134
+
135
+ ioc.run_for (test_timeout);
136
+
137
+ BOOST_TEST (exec_finished);
138
+ BOOST_TEST (run_finished);
139
+ BOOST_TEST (std::get<0 >(resp).value () == ping_value);
140
+ }
141
+
142
+ // After an error, a TLS connection can recover.
143
+ // Force an error using QUIT, then issue a regular request to verify that we could reconnect
144
+ BOOST_AUTO_TEST_CASE (reconnection)
145
+ {
146
+ // Setup
147
+ net::io_context ioc;
148
+ net::steady_timer timer{ioc};
149
+ connection conn{ioc};
150
+ auto const cfg = make_tls_config ();
151
+
152
+ request ping_request;
153
+ ping_request.push (" PING" , " some_value" );
154
+
155
+ request quit_request;
156
+ quit_request.push (" QUIT" );
157
+
158
+ bool exec_finished = false , run_finished = false ;
159
+
160
+ // Run the connection
161
+ conn.async_run (cfg, {}, [&](error_code ec) {
162
+ run_finished = true ;
163
+ BOOST_TEST (ec == net::error::operation_aborted);
164
+ });
165
+
166
+ // The PING is the end of the callback chain
167
+ auto ping_callback = [&](error_code ec, std::size_t ) {
168
+ exec_finished = true ;
169
+ BOOST_TEST (ec == error_code ());
170
+ conn.cancel ();
171
+ };
172
+
173
+ auto quit_callback = [&](error_code ec, std::size_t ) {
174
+ BOOST_TEST (ec == error_code ());
124
175
125
- ioc.run ();
176
+ // If a request is issued immediately after QUIT, the request sometimes
177
+ // fails, probably due to a race condition. This dispatches any pending
178
+ // handlers, triggering the reconnection process.
179
+ // TODO: this should not be required.
180
+ ioc.poll ();
181
+ conn.async_exec (ping_request, ignore, ping_callback);
182
+ };
126
183
127
- BOOST_CHECK_EQUAL (in, std::get<0 >(resp).value ());
184
+ conn.async_exec (quit_request, ignore, quit_callback);
185
+
186
+ ioc.run_for (test_timeout);
187
+
188
+ BOOST_TEST (exec_finished);
189
+ BOOST_TEST (run_finished);
128
190
}
191
+
192
+ } // namespace
0 commit comments