OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "components/ssl_errors/error_classification.h" | 5 #include "components/ssl_errors/error_classification.h" |
6 | 6 |
7 #include "base/files/file_path.h" | 7 #include "base/files/file_path.h" |
8 #include "base/memory/ptr_util.h" | 8 #include "base/memory/ptr_util.h" |
9 #include "base/message_loop/message_loop.h" | 9 #include "base/message_loop/message_loop.h" |
10 #include "base/strings/string_split.h" | 10 #include "base/strings/string_split.h" |
| 11 #include "base/test/histogram_tester.h" |
| 12 #include "base/test/simple_test_clock.h" |
| 13 #include "base/test/simple_test_tick_clock.h" |
11 #include "base/time/default_clock.h" | 14 #include "base/time/default_clock.h" |
12 #include "base/time/default_tick_clock.h" | 15 #include "base/time/default_tick_clock.h" |
| 16 #include "components/network_time/network_time_test_utils.h" |
13 #include "components/network_time/network_time_tracker.h" | 17 #include "components/network_time/network_time_tracker.h" |
14 #include "components/prefs/testing_pref_service.h" | 18 #include "components/prefs/testing_pref_service.h" |
15 #include "net/base/net_errors.h" | 19 #include "net/base/net_errors.h" |
16 #include "net/cert/x509_cert_types.h" | 20 #include "net/cert/x509_cert_types.h" |
17 #include "net/cert/x509_certificate.h" | 21 #include "net/cert/x509_certificate.h" |
18 #include "net/test/cert_test_util.h" | 22 #include "net/test/cert_test_util.h" |
| 23 #include "net/test/embedded_test_server/embedded_test_server.h" |
| 24 #include "net/test/embedded_test_server/http_response.h" |
19 #include "net/test/test_certificate_data.h" | 25 #include "net/test/test_certificate_data.h" |
20 #include "net/test/test_data_directory.h" | 26 #include "net/test/test_data_directory.h" |
21 #include "net/url_request/url_request_test_util.h" | 27 #include "net/url_request/url_request_test_util.h" |
22 #include "testing/gtest/include/gtest/gtest.h" | 28 #include "testing/gtest/include/gtest/gtest.h" |
23 #include "url/gurl.h" | 29 #include "url/gurl.h" |
24 | 30 |
25 class SSLErrorClassificationTest : public testing::Test {}; | 31 namespace { |
| 32 const char kNetworkTimeHistogram[] = "interstitial.ssl.clockstate.network3"; |
| 33 |
| 34 static std::unique_ptr<net::test_server::HttpResponse> |
| 35 NetworkErrorResponseHandler(const net::test_server::HttpRequest& request) { |
| 36 return std::unique_ptr<net::test_server::HttpResponse>( |
| 37 new net::test_server::RawHttpResponse("", "")); |
| 38 } |
| 39 |
| 40 } // namespace |
| 41 |
| 42 class SSLErrorClassificationTest : public network_time::FieldTrialTest { |
| 43 public: |
| 44 SSLErrorClassificationTest() : network_time::FieldTrialTest() {} |
| 45 }; |
26 | 46 |
27 TEST_F(SSLErrorClassificationTest, TestNameMismatch) { | 47 TEST_F(SSLErrorClassificationTest, TestNameMismatch) { |
28 scoped_refptr<net::X509Certificate> google_cert( | 48 scoped_refptr<net::X509Certificate> google_cert( |
29 net::X509Certificate::CreateFromBytes( | 49 net::X509Certificate::CreateFromBytes( |
30 reinterpret_cast<const char*>(google_der), sizeof(google_der))); | 50 reinterpret_cast<const char*>(google_der), sizeof(google_der))); |
31 ASSERT_TRUE(google_cert.get()); | 51 ASSERT_TRUE(google_cert.get()); |
32 std::vector<std::string> dns_names_google; | 52 std::vector<std::string> dns_names_google; |
33 google_cert->GetDNSNames(&dns_names_google); | 53 google_cert->GetDNSNames(&dns_names_google); |
34 ASSERT_EQ(1u, dns_names_google.size()); // ["www.google.com"] | 54 ASSERT_EQ(1u, dns_names_google.size()); // ["www.google.com"] |
35 std::vector<std::string> hostname_tokens_google = | 55 std::vector<std::string> hostname_tokens_google = |
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
182 EXPECT_EQ(4u, ssl_errors::GetLevenshteinDistance("xxx", "xxxxxxx")); | 202 EXPECT_EQ(4u, ssl_errors::GetLevenshteinDistance("xxx", "xxxxxxx")); |
183 EXPECT_EQ(4u, ssl_errors::GetLevenshteinDistance("xxxxxxx", "xxx")); | 203 EXPECT_EQ(4u, ssl_errors::GetLevenshteinDistance("xxxxxxx", "xxx")); |
184 | 204 |
185 EXPECT_EQ(7u, ssl_errors::GetLevenshteinDistance("yyy", "xxxxxxx")); | 205 EXPECT_EQ(7u, ssl_errors::GetLevenshteinDistance("yyy", "xxxxxxx")); |
186 EXPECT_EQ(7u, ssl_errors::GetLevenshteinDistance("xxxxxxx", "yyy")); | 206 EXPECT_EQ(7u, ssl_errors::GetLevenshteinDistance("xxxxxxx", "yyy")); |
187 } | 207 } |
188 | 208 |
189 TEST_F(SSLErrorClassificationTest, GetClockState) { | 209 TEST_F(SSLErrorClassificationTest, GetClockState) { |
190 // This test aims to obtain all possible return values of | 210 // This test aims to obtain all possible return values of |
191 // |GetClockState|. | 211 // |GetClockState|. |
| 212 const char kBuildTimeHistogram[] = "interstitial.ssl.clockstate.build_time"; |
| 213 base::HistogramTester histograms; |
| 214 histograms.ExpectTotalCount(kBuildTimeHistogram, 0); |
| 215 histograms.ExpectTotalCount(kNetworkTimeHistogram, 0); |
192 TestingPrefServiceSimple pref_service; | 216 TestingPrefServiceSimple pref_service; |
193 network_time::NetworkTimeTracker::RegisterPrefs(pref_service.registry()); | 217 network_time::NetworkTimeTracker::RegisterPrefs(pref_service.registry()); |
194 base::MessageLoop loop; | 218 base::MessageLoop loop; |
195 network_time::NetworkTimeTracker network_time_tracker( | 219 network_time::NetworkTimeTracker network_time_tracker( |
196 base::MakeUnique<base::DefaultClock>(), | 220 base::MakeUnique<base::DefaultClock>(), |
197 base::MakeUnique<base::DefaultTickClock>(), &pref_service, | 221 base::MakeUnique<base::DefaultTickClock>(), &pref_service, |
198 new net::TestURLRequestContextGetter( | 222 new net::TestURLRequestContextGetter( |
199 base::ThreadTaskRunnerHandle::Get())); | 223 base::ThreadTaskRunnerHandle::Get())); |
200 | 224 |
201 EXPECT_EQ( | 225 EXPECT_EQ( |
202 ssl_errors::ClockState::CLOCK_STATE_UNKNOWN, | 226 ssl_errors::ClockState::CLOCK_STATE_UNKNOWN, |
203 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); | 227 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); |
| 228 histograms.ExpectTotalCount(kBuildTimeHistogram, 1); |
| 229 histograms.ExpectBucketCount(kBuildTimeHistogram, |
| 230 ssl_errors::ClockState::CLOCK_STATE_UNKNOWN, 1); |
| 231 histograms.ExpectTotalCount(kNetworkTimeHistogram, 1); |
| 232 histograms.ExpectBucketCount( |
| 233 kNetworkTimeHistogram, |
| 234 ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_NO_SYNC_ATTEMPT, 1); |
204 | 235 |
205 ssl_errors::SetBuildTimeForTesting(base::Time::Now() - | 236 ssl_errors::SetBuildTimeForTesting(base::Time::Now() - |
206 base::TimeDelta::FromDays(367)); | 237 base::TimeDelta::FromDays(367)); |
207 EXPECT_EQ( | 238 EXPECT_EQ( |
208 ssl_errors::ClockState::CLOCK_STATE_FUTURE, | 239 ssl_errors::ClockState::CLOCK_STATE_FUTURE, |
209 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); | 240 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); |
| 241 histograms.ExpectTotalCount(kBuildTimeHistogram, 2); |
| 242 histograms.ExpectBucketCount(kBuildTimeHistogram, |
| 243 ssl_errors::ClockState::CLOCK_STATE_FUTURE, 1); |
| 244 histograms.ExpectTotalCount(kNetworkTimeHistogram, 2); |
| 245 histograms.ExpectBucketCount( |
| 246 kNetworkTimeHistogram, |
| 247 ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_NO_SYNC_ATTEMPT, 2); |
210 | 248 |
211 ssl_errors::SetBuildTimeForTesting(base::Time::Now() + | 249 ssl_errors::SetBuildTimeForTesting(base::Time::Now() + |
212 base::TimeDelta::FromDays(3)); | 250 base::TimeDelta::FromDays(3)); |
213 EXPECT_EQ( | 251 EXPECT_EQ( |
214 ssl_errors::ClockState::CLOCK_STATE_PAST, | 252 ssl_errors::ClockState::CLOCK_STATE_PAST, |
215 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); | 253 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); |
| 254 histograms.ExpectTotalCount(kBuildTimeHistogram, 3); |
| 255 histograms.ExpectBucketCount(kBuildTimeHistogram, |
| 256 ssl_errors::ClockState::CLOCK_STATE_FUTURE, 1); |
| 257 histograms.ExpectTotalCount(kNetworkTimeHistogram, 3); |
| 258 histograms.ExpectBucketCount( |
| 259 kNetworkTimeHistogram, |
| 260 ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_NO_SYNC_ATTEMPT, 3); |
216 | 261 |
217 // Intentionally leave the build time alone. It should be ignored | 262 // Intentionally leave the build time alone. It should be ignored |
218 // in favor of network time. | 263 // in favor of network time. |
219 network_time_tracker.UpdateNetworkTime( | 264 network_time_tracker.UpdateNetworkTime( |
220 base::Time::Now() + base::TimeDelta::FromHours(1), | 265 base::Time::Now() + base::TimeDelta::FromHours(1), |
221 base::TimeDelta::FromSeconds(1), // resolution | 266 base::TimeDelta::FromSeconds(1), // resolution |
222 base::TimeDelta::FromMilliseconds(250), // latency | 267 base::TimeDelta::FromMilliseconds(250), // latency |
223 base::TimeTicks::Now()); // posting time | 268 base::TimeTicks::Now()); // posting time |
224 EXPECT_EQ( | 269 EXPECT_EQ( |
225 ssl_errors::ClockState::CLOCK_STATE_PAST, | 270 ssl_errors::ClockState::CLOCK_STATE_PAST, |
226 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); | 271 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); |
| 272 histograms.ExpectTotalCount(kBuildTimeHistogram, 4); |
| 273 histograms.ExpectTotalCount(kNetworkTimeHistogram, 4); |
| 274 histograms.ExpectBucketCount( |
| 275 kNetworkTimeHistogram, ssl_errors::NETWORK_CLOCK_STATE_CLOCK_IN_PAST, 1); |
227 | 276 |
228 network_time_tracker.UpdateNetworkTime( | 277 network_time_tracker.UpdateNetworkTime( |
229 base::Time::Now() - base::TimeDelta::FromHours(1), | 278 base::Time::Now() - base::TimeDelta::FromHours(1), |
230 base::TimeDelta::FromSeconds(1), // resolution | 279 base::TimeDelta::FromSeconds(1), // resolution |
231 base::TimeDelta::FromMilliseconds(250), // latency | 280 base::TimeDelta::FromMilliseconds(250), // latency |
232 base::TimeTicks::Now()); // posting time | 281 base::TimeTicks::Now()); // posting time |
233 EXPECT_EQ( | 282 EXPECT_EQ( |
234 ssl_errors::ClockState::CLOCK_STATE_FUTURE, | 283 ssl_errors::ClockState::CLOCK_STATE_FUTURE, |
235 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); | 284 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); |
| 285 histograms.ExpectTotalCount(kBuildTimeHistogram, 5); |
| 286 histograms.ExpectTotalCount(kNetworkTimeHistogram, 5); |
| 287 histograms.ExpectBucketCount(kNetworkTimeHistogram, |
| 288 ssl_errors::NETWORK_CLOCK_STATE_CLOCK_IN_FUTURE, |
| 289 1); |
236 | 290 |
237 network_time_tracker.UpdateNetworkTime( | 291 network_time_tracker.UpdateNetworkTime( |
238 base::Time::Now(), | 292 base::Time::Now(), |
239 base::TimeDelta::FromSeconds(1), // resolution | 293 base::TimeDelta::FromSeconds(1), // resolution |
240 base::TimeDelta::FromMilliseconds(250), // latency | 294 base::TimeDelta::FromMilliseconds(250), // latency |
241 base::TimeTicks::Now()); // posting time | 295 base::TimeTicks::Now()); // posting time |
242 EXPECT_EQ( | 296 EXPECT_EQ( |
243 ssl_errors::ClockState::CLOCK_STATE_OK, | 297 ssl_errors::ClockState::CLOCK_STATE_OK, |
244 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); | 298 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); |
| 299 histograms.ExpectTotalCount(kBuildTimeHistogram, 6); |
| 300 histograms.ExpectTotalCount(kNetworkTimeHistogram, 6); |
| 301 histograms.ExpectBucketCount(kNetworkTimeHistogram, |
| 302 ssl_errors::NETWORK_CLOCK_STATE_OK, 1); |
245 | 303 |
246 // Now clear the network time. The build time should reassert | 304 // Now clear the network time. The build time should reassert |
247 // itself. | 305 // itself. |
248 network_time_tracker.UpdateNetworkTime( | 306 network_time_tracker.UpdateNetworkTime( |
249 base::Time(), | 307 base::Time(), |
250 base::TimeDelta::FromSeconds(1), // resolution | 308 base::TimeDelta::FromSeconds(1), // resolution |
251 base::TimeDelta::FromMilliseconds(250), // latency | 309 base::TimeDelta::FromMilliseconds(250), // latency |
252 base::TimeTicks::Now()); // posting time | 310 base::TimeTicks::Now()); // posting time |
253 ssl_errors::SetBuildTimeForTesting(base::Time::Now() + | 311 ssl_errors::SetBuildTimeForTesting(base::Time::Now() + |
254 base::TimeDelta::FromDays(3)); | 312 base::TimeDelta::FromDays(3)); |
255 EXPECT_EQ( | 313 EXPECT_EQ( |
256 ssl_errors::ClockState::CLOCK_STATE_PAST, | 314 ssl_errors::ClockState::CLOCK_STATE_PAST, |
257 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); | 315 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); |
258 | 316 |
259 // Now set the build time to something reasonable. We should be | 317 // Now set the build time to something reasonable. We should be |
260 // back to the know-nothing state. | 318 // back to the know-nothing state. |
261 ssl_errors::SetBuildTimeForTesting(base::Time::Now()); | 319 ssl_errors::SetBuildTimeForTesting(base::Time::Now()); |
262 EXPECT_EQ( | 320 EXPECT_EQ( |
263 ssl_errors::ClockState::CLOCK_STATE_UNKNOWN, | 321 ssl_errors::ClockState::CLOCK_STATE_UNKNOWN, |
264 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); | 322 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); |
265 } | 323 } |
| 324 |
| 325 // Tests that all possible NetworkClockState histogram values are recorded |
| 326 // appropriately. |
| 327 TEST_F(SSLErrorClassificationTest, NetworkClockStateHistogram) { |
| 328 base::Thread io_thread("IO thread"); |
| 329 base::Thread::Options thread_options; |
| 330 thread_options.message_loop_type = base::MessageLoop::TYPE_IO; |
| 331 EXPECT_TRUE(io_thread.StartWithOptions(thread_options)); |
| 332 |
| 333 net::EmbeddedTestServer test_server; |
| 334 ASSERT_TRUE(test_server.Start()); |
| 335 |
| 336 base::HistogramTester histograms; |
| 337 histograms.ExpectTotalCount(kNetworkTimeHistogram, 0); |
| 338 TestingPrefServiceSimple pref_service; |
| 339 network_time::NetworkTimeTracker::RegisterPrefs(pref_service.registry()); |
| 340 base::SimpleTestTickClock* tick_clock = new base::SimpleTestTickClock; |
| 341 base::SimpleTestClock* clock = new base::SimpleTestClock; |
| 342 // Do this to be sure that |is_null| returns false. |
| 343 clock->Advance(base::TimeDelta::FromDays(111)); |
| 344 tick_clock->Advance(base::TimeDelta::FromDays(222)); |
| 345 |
| 346 base::MessageLoop loop; |
| 347 network_time::NetworkTimeTracker network_time_tracker( |
| 348 std::unique_ptr<base::Clock>(clock), |
| 349 std::unique_ptr<base::TickClock>(tick_clock), &pref_service, |
| 350 new net::TestURLRequestContextGetter(io_thread.task_runner())); |
| 351 network_time_tracker.SetTimeServerURLForTesting(test_server.GetURL("/")); |
| 352 SetNetworkQueriesWithVariationsService(true, 0.0); |
| 353 |
| 354 // No sync attempt. |
| 355 EXPECT_EQ( |
| 356 ssl_errors::ClockState::CLOCK_STATE_UNKNOWN, |
| 357 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); |
| 358 histograms.ExpectTotalCount(kNetworkTimeHistogram, 1); |
| 359 histograms.ExpectBucketCount( |
| 360 kNetworkTimeHistogram, |
| 361 ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_NO_SYNC_ATTEMPT, 1); |
| 362 |
| 363 // First sync attempt is pending. |
| 364 test_server.RegisterRequestHandler(base::Bind(&NetworkErrorResponseHandler)); |
| 365 EXPECT_TRUE(network_time_tracker.QueryTimeServiceForTesting()); |
| 366 EXPECT_EQ( |
| 367 ssl_errors::ClockState::CLOCK_STATE_UNKNOWN, |
| 368 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); |
| 369 histograms.ExpectTotalCount(kNetworkTimeHistogram, 2); |
| 370 histograms.ExpectBucketCount( |
| 371 kNetworkTimeHistogram, |
| 372 ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_FIRST_SYNC_PENDING, 1); |
| 373 network_time_tracker.WaitForFetchForTesting(123123123); |
| 374 |
| 375 // No successful sync. |
| 376 EXPECT_EQ( |
| 377 ssl_errors::ClockState::CLOCK_STATE_UNKNOWN, |
| 378 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); |
| 379 histograms.ExpectTotalCount(kNetworkTimeHistogram, 3); |
| 380 histograms.ExpectBucketCount( |
| 381 kNetworkTimeHistogram, |
| 382 ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_NO_SUCCESSFUL_SYNC, 1); |
| 383 |
| 384 // Subsequent sync attempt is pending. |
| 385 EXPECT_TRUE(network_time_tracker.QueryTimeServiceForTesting()); |
| 386 EXPECT_EQ( |
| 387 ssl_errors::ClockState::CLOCK_STATE_UNKNOWN, |
| 388 ssl_errors::GetClockState(base::Time::Now(), &network_time_tracker)); |
| 389 histograms.ExpectTotalCount(kNetworkTimeHistogram, 4); |
| 390 histograms.ExpectBucketCount( |
| 391 kNetworkTimeHistogram, |
| 392 ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_SUBSEQUENT_SYNC_PENDING, 1); |
| 393 network_time_tracker.WaitForFetchForTesting(123123123); |
| 394 |
| 395 // System clock is correct. |
| 396 network_time_tracker.UpdateNetworkTime( |
| 397 clock->Now(), |
| 398 base::TimeDelta::FromSeconds(1), // resolution |
| 399 base::TimeDelta::FromMilliseconds(250), // latency |
| 400 tick_clock->NowTicks()); // posting time |
| 401 EXPECT_EQ(ssl_errors::ClockState::CLOCK_STATE_OK, |
| 402 ssl_errors::GetClockState(clock->Now(), &network_time_tracker)); |
| 403 histograms.ExpectTotalCount(kNetworkTimeHistogram, 5); |
| 404 histograms.ExpectBucketCount(kNetworkTimeHistogram, |
| 405 ssl_errors::NETWORK_CLOCK_STATE_OK, 1); |
| 406 |
| 407 // System clock is in the past. |
| 408 network_time_tracker.UpdateNetworkTime( |
| 409 clock->Now() + base::TimeDelta::FromHours(1), |
| 410 base::TimeDelta::FromSeconds(1), // resolution |
| 411 base::TimeDelta::FromMilliseconds(250), // latency |
| 412 tick_clock->NowTicks()); // posting time |
| 413 EXPECT_EQ(ssl_errors::ClockState::CLOCK_STATE_PAST, |
| 414 ssl_errors::GetClockState(clock->Now(), &network_time_tracker)); |
| 415 histograms.ExpectTotalCount(kNetworkTimeHistogram, 6); |
| 416 histograms.ExpectBucketCount( |
| 417 kNetworkTimeHistogram, ssl_errors::NETWORK_CLOCK_STATE_CLOCK_IN_PAST, 1); |
| 418 |
| 419 // System clock is in the future. |
| 420 network_time_tracker.UpdateNetworkTime( |
| 421 clock->Now() - base::TimeDelta::FromHours(1), |
| 422 base::TimeDelta::FromSeconds(1), // resolution |
| 423 base::TimeDelta::FromMilliseconds(250), // latency |
| 424 tick_clock->NowTicks()); // posting time |
| 425 EXPECT_EQ(ssl_errors::ClockState::CLOCK_STATE_FUTURE, |
| 426 ssl_errors::GetClockState(clock->Now(), &network_time_tracker)); |
| 427 histograms.ExpectTotalCount(kNetworkTimeHistogram, 7); |
| 428 histograms.ExpectBucketCount(kNetworkTimeHistogram, |
| 429 ssl_errors::NETWORK_CLOCK_STATE_CLOCK_IN_FUTURE, |
| 430 1); |
| 431 |
| 432 // Sync has been lost. |
| 433 tick_clock->Advance(base::TimeDelta::FromSeconds(1)); |
| 434 clock->Advance(base::TimeDelta::FromDays(1)); |
| 435 // GetClockState() will fall back to the build time heuristic. |
| 436 ssl_errors::GetClockState(clock->Now(), &network_time_tracker); |
| 437 histograms.ExpectTotalCount(kNetworkTimeHistogram, 8); |
| 438 histograms.ExpectBucketCount( |
| 439 kNetworkTimeHistogram, ssl_errors::NETWORK_CLOCK_STATE_UNKNOWN_SYNC_LOST, |
| 440 1); |
| 441 |
| 442 io_thread.Stop(); |
| 443 } |
OLD | NEW |