| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 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 "net/quic/crypto/quic_crypto_server_config.h" | 5 #include "net/quic/crypto/quic_crypto_server_config.h" |
| 6 | 6 |
| 7 #include <stdarg.h> | 7 #include <stdarg.h> |
| 8 | 8 |
| 9 #include "base/stl_util.h" | 9 #include "base/stl_util.h" |
| 10 #include "net/quic/crypto/aes_128_gcm_12_encrypter.h" | 10 #include "net/quic/crypto/aes_128_gcm_12_encrypter.h" |
| (...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 144 server_config_->SelectNewPrimaryConfig( | 144 server_config_->SelectNewPrimaryConfig( |
| 145 QuicWallTime::FromUNIXSeconds(seconds)); | 145 QuicWallTime::FromUNIXSeconds(seconds)); |
| 146 } | 146 } |
| 147 | 147 |
| 148 private: | 148 private: |
| 149 const QuicCryptoServerConfig* server_config_; | 149 const QuicCryptoServerConfig* server_config_; |
| 150 }; | 150 }; |
| 151 | 151 |
| 152 class TestStrikeRegisterClient : public StrikeRegisterClient { | 152 class TestStrikeRegisterClient : public StrikeRegisterClient { |
| 153 public: | 153 public: |
| 154 TestStrikeRegisterClient(QuicCryptoServerConfig* config, string orbit) | 154 explicit TestStrikeRegisterClient(QuicCryptoServerConfig* config) |
| 155 : config_(config), orbit_(orbit), orbit_called_(false) {} | 155 : config_(config), is_known_orbit_called_(false) {} |
| 156 | 156 |
| 157 virtual string orbit() OVERRIDE { | 157 virtual bool IsKnownOrbit(StringPiece orbit) const OVERRIDE { |
| 158 // Ensure that the strike register client lock is not held. | 158 // Ensure that the strike register client lock is not held. |
| 159 QuicCryptoServerConfigPeer peer(config_); | 159 QuicCryptoServerConfigPeer peer(config_); |
| 160 base::Lock* m = peer.GetStrikeRegisterClientLock(); | 160 base::Lock* m = peer.GetStrikeRegisterClientLock(); |
| 161 // In Chromium, we will dead lock if the lock is held by the current thread. | 161 // In Chromium, we will dead lock if the lock is held by the current thread. |
| 162 // Chromium doesn't have AssertNotHeld API call. | 162 // Chromium doesn't have AssertNotHeld API call. |
| 163 // m->AssertNotHeld(); | 163 // m->AssertNotHeld(); |
| 164 base::AutoLock lock(*m); | 164 base::AutoLock lock(*m); |
| 165 | 165 |
| 166 orbit_called_ = true; | 166 is_known_orbit_called_ = true; |
| 167 return orbit_; | 167 return true; |
| 168 } | 168 } |
| 169 | 169 |
| 170 virtual void VerifyNonceIsValidAndUnique( | 170 virtual void VerifyNonceIsValidAndUnique( |
| 171 StringPiece nonce, | 171 StringPiece nonce, |
| 172 QuicWallTime now, | 172 QuicWallTime now, |
| 173 ResultCallback* cb) OVERRIDE { | 173 ResultCallback* cb) OVERRIDE { |
| 174 LOG(FATAL) << "Not implemented"; | 174 LOG(FATAL) << "Not implemented"; |
| 175 } | 175 } |
| 176 | 176 |
| 177 bool orbit_called() { return orbit_called_; } | 177 bool is_known_orbit_called() { return is_known_orbit_called_; } |
| 178 | 178 |
| 179 private: | 179 private: |
| 180 QuicCryptoServerConfig* config_; | 180 QuicCryptoServerConfig* config_; |
| 181 string orbit_; | 181 mutable bool is_known_orbit_called_; |
| 182 bool orbit_called_; | |
| 183 }; | 182 }; |
| 184 | 183 |
| 185 TEST(QuicCryptoServerConfigTest, ServerConfig) { | 184 TEST(QuicCryptoServerConfigTest, ServerConfig) { |
| 186 QuicRandom* rand = QuicRandom::GetInstance(); | 185 QuicRandom* rand = QuicRandom::GetInstance(); |
| 187 QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand); | 186 QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand); |
| 188 MockClock clock; | 187 MockClock clock; |
| 189 | 188 |
| 190 scoped_ptr<CryptoHandshakeMessage>( | 189 scoped_ptr<CryptoHandshakeMessage>( |
| 191 server.AddDefaultConfig(rand, &clock, | 190 server.AddDefaultConfig(rand, &clock, |
| 192 QuicCryptoServerConfig::ConfigOptions())); | 191 QuicCryptoServerConfig::ConfigOptions())); |
| 193 } | 192 } |
| 194 | 193 |
| 195 TEST(QuicCryptoServerConfigTest, GetOrbitIsCalledWithoutTheStrikeRegisterLock) { | 194 TEST(QuicCryptoServerConfigTest, GetOrbitIsCalledWithoutTheStrikeRegisterLock) { |
| 196 QuicRandom* rand = QuicRandom::GetInstance(); | 195 QuicRandom* rand = QuicRandom::GetInstance(); |
| 197 QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand); | 196 QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand); |
| 198 MockClock clock; | 197 MockClock clock; |
| 199 | 198 |
| 200 const char kOrbit[] = "12345678"; | |
| 201 | |
| 202 TestStrikeRegisterClient* strike_register = | 199 TestStrikeRegisterClient* strike_register = |
| 203 new TestStrikeRegisterClient(&server, kOrbit); | 200 new TestStrikeRegisterClient(&server); |
| 204 server.SetStrikeRegisterClient(strike_register); | 201 server.SetStrikeRegisterClient(strike_register); |
| 205 | 202 |
| 206 QuicCryptoServerConfig::ConfigOptions options; | 203 QuicCryptoServerConfig::ConfigOptions options; |
| 207 options.orbit = kOrbit; | |
| 208 scoped_ptr<CryptoHandshakeMessage>( | 204 scoped_ptr<CryptoHandshakeMessage>( |
| 209 server.AddDefaultConfig(rand, &clock, options)); | 205 server.AddDefaultConfig(rand, &clock, options)); |
| 210 EXPECT_TRUE(strike_register->orbit_called()); | 206 EXPECT_TRUE(strike_register->is_known_orbit_called()); |
| 211 } | 207 } |
| 212 | 208 |
| 213 TEST(QuicCryptoServerConfigTest, SourceAddressTokens) { | 209 TEST(QuicCryptoServerConfigTest, SourceAddressTokens) { |
| 214 QuicRandom* rand = QuicRandom::GetInstance(); | 210 QuicRandom* rand = QuicRandom::GetInstance(); |
| 215 QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand); | 211 QuicCryptoServerConfig server(QuicCryptoServerConfig::TESTING, rand); |
| 216 IPAddressNumber ip; | 212 IPAddressNumber ip; |
| 217 CHECK(ParseIPLiteralToNumber("192.0.2.33", &ip)); | 213 CHECK(ParseIPLiteralToNumber("192.0.2.33", &ip)); |
| 218 IPEndPoint ip4 = IPEndPoint(ip, 1); | 214 IPEndPoint ip4 = IPEndPoint(ip, 1); |
| 219 CHECK(ParseIPLiteralToNumber("2001:db8:0::42", &ip)); | 215 CHECK(ParseIPLiteralToNumber("2001:db8:0::42", &ip)); |
| 220 IPEndPoint ip6 = IPEndPoint(ip, 2); | 216 IPEndPoint ip6 = IPEndPoint(ip, 2); |
| (...skipping 30 matching lines...) Expand all Loading... |
| 251 | 247 |
| 252 // SetConfigs constructs suitable config protobufs and calls SetConfigs on | 248 // SetConfigs constructs suitable config protobufs and calls SetConfigs on |
| 253 // |config_|. The arguments are given as NULL-terminated pairs. The first of | 249 // |config_|. The arguments are given as NULL-terminated pairs. The first of |
| 254 // each pair is the server config ID of a Config. The second is the | 250 // each pair is the server config ID of a Config. The second is the |
| 255 // |primary_time| of that Config, given in epoch seconds. (Although note | 251 // |primary_time| of that Config, given in epoch seconds. (Although note |
| 256 // that, in these tests, time is set to 1000 seconds since the epoch.) For | 252 // that, in these tests, time is set to 1000 seconds since the epoch.) For |
| 257 // example: | 253 // example: |
| 258 // SetConfigs(NULL); // calls |config_.SetConfigs| with no protobufs. | 254 // SetConfigs(NULL); // calls |config_.SetConfigs| with no protobufs. |
| 259 // | 255 // |
| 260 // // Calls |config_.SetConfigs| with two protobufs: one for a Config with | 256 // // Calls |config_.SetConfigs| with two protobufs: one for a Config with |
| 261 // // a |primary_time| of 900, and another with a |primary_time| of 1000. | 257 // // a |primary_time| of 900 and priority 1, and another with |
| 258 // // a |primary_time| of 1000 and priority 2. |
| 259 |
| 262 // CheckConfigs( | 260 // CheckConfigs( |
| 263 // "id1", 900, | 261 // "id1", 900, 1, |
| 264 // "id2", 1000, | 262 // "id2", 1000, 2, |
| 265 // NULL); | 263 // NULL); |
| 266 // | 264 // |
| 267 // If the server config id starts with "INVALID" then the generated protobuf | 265 // If the server config id starts with "INVALID" then the generated protobuf |
| 268 // will be invalid. | 266 // will be invalid. |
| 269 void SetConfigs(const char* server_config_id1, ...) { | 267 void SetConfigs(const char* server_config_id1, ...) { |
| 268 const char kOrbit[] = "12345678"; |
| 269 |
| 270 va_list ap; | 270 va_list ap; |
| 271 va_start(ap, server_config_id1); | 271 va_start(ap, server_config_id1); |
| 272 bool has_invalid = false; | 272 bool has_invalid = false; |
| 273 bool is_empty = true; |
| 273 | 274 |
| 274 vector<QuicServerConfigProtobuf*> protobufs; | 275 vector<QuicServerConfigProtobuf*> protobufs; |
| 275 bool first = true; | 276 bool first = true; |
| 276 for (;;) { | 277 for (;;) { |
| 277 const char* server_config_id; | 278 const char* server_config_id; |
| 278 if (first) { | 279 if (first) { |
| 279 server_config_id = server_config_id1; | 280 server_config_id = server_config_id1; |
| 280 first = false; | 281 first = false; |
| 281 } else { | 282 } else { |
| 282 server_config_id = va_arg(ap, const char*); | 283 server_config_id = va_arg(ap, const char*); |
| 283 } | 284 } |
| 284 | 285 |
| 285 if (!server_config_id) { | 286 if (!server_config_id) { |
| 286 break; | 287 break; |
| 287 } | 288 } |
| 288 | 289 |
| 290 is_empty = false; |
| 289 int primary_time = va_arg(ap, int); | 291 int primary_time = va_arg(ap, int); |
| 292 int priority = va_arg(ap, int); |
| 290 | 293 |
| 291 QuicCryptoServerConfig::ConfigOptions options; | 294 QuicCryptoServerConfig::ConfigOptions options; |
| 292 options.id = server_config_id; | 295 options.id = server_config_id; |
| 296 options.orbit = kOrbit; |
| 293 QuicServerConfigProtobuf* protobuf( | 297 QuicServerConfigProtobuf* protobuf( |
| 294 QuicCryptoServerConfig::DefaultConfig(rand_, &clock_, options)); | 298 QuicCryptoServerConfig::GenerateConfig(rand_, &clock_, options)); |
| 295 protobuf->set_primary_time(primary_time); | 299 protobuf->set_primary_time(primary_time); |
| 300 protobuf->set_priority(priority); |
| 296 if (string(server_config_id).find("INVALID") == 0) { | 301 if (string(server_config_id).find("INVALID") == 0) { |
| 297 protobuf->clear_key(); | 302 protobuf->clear_key(); |
| 298 has_invalid = true; | 303 has_invalid = true; |
| 299 } | 304 } |
| 300 protobufs.push_back(protobuf); | 305 protobufs.push_back(protobuf); |
| 301 } | 306 } |
| 302 | 307 |
| 303 ASSERT_EQ(!has_invalid, config_.SetConfigs(protobufs, clock_.WallNow())); | 308 ASSERT_EQ(!has_invalid && !is_empty, |
| 309 config_.SetConfigs(protobufs, clock_.WallNow())); |
| 304 STLDeleteElements(&protobufs); | 310 STLDeleteElements(&protobufs); |
| 305 } | 311 } |
| 306 | 312 |
| 307 protected: | 313 protected: |
| 308 QuicRandom* const rand_; | 314 QuicRandom* const rand_; |
| 309 MockClock clock_; | 315 MockClock clock_; |
| 310 QuicCryptoServerConfig config_; | 316 QuicCryptoServerConfig config_; |
| 311 QuicCryptoServerConfigPeer test_peer_; | 317 QuicCryptoServerConfigPeer test_peer_; |
| 312 }; | 318 }; |
| 313 | 319 |
| 314 TEST_F(CryptoServerConfigsTest, NoConfigs) { | 320 TEST_F(CryptoServerConfigsTest, NoConfigs) { |
| 315 test_peer_.CheckConfigs(NULL); | 321 test_peer_.CheckConfigs(NULL); |
| 316 } | 322 } |
| 317 | 323 |
| 318 TEST_F(CryptoServerConfigsTest, MakePrimaryFirst) { | 324 TEST_F(CryptoServerConfigsTest, MakePrimaryFirst) { |
| 319 // Make sure that "b" is primary even though "a" comes first. | 325 // Make sure that "b" is primary even though "a" comes first. |
| 320 SetConfigs("a", 1100, | 326 SetConfigs("a", 1100, 1, |
| 321 "b", 900, | 327 "b", 900, 1, |
| 322 NULL); | 328 NULL); |
| 323 test_peer_.CheckConfigs( | 329 test_peer_.CheckConfigs( |
| 324 "a", false, | 330 "a", false, |
| 325 "b", true, | 331 "b", true, |
| 326 NULL); | 332 NULL); |
| 327 } | 333 } |
| 328 | 334 |
| 329 TEST_F(CryptoServerConfigsTest, MakePrimarySecond) { | 335 TEST_F(CryptoServerConfigsTest, MakePrimarySecond) { |
| 330 // Make sure that a remains primary after b is added. | 336 // Make sure that a remains primary after b is added. |
| 331 SetConfigs("a", 900, | 337 SetConfigs("a", 900, 1, |
| 332 "b", 1100, | 338 "b", 1100, 1, |
| 333 NULL); | 339 NULL); |
| 334 test_peer_.CheckConfigs( | 340 test_peer_.CheckConfigs( |
| 335 "a", true, | 341 "a", true, |
| 336 "b", false, | 342 "b", false, |
| 337 NULL); | 343 NULL); |
| 338 } | 344 } |
| 339 | 345 |
| 340 TEST_F(CryptoServerConfigsTest, Delete) { | 346 TEST_F(CryptoServerConfigsTest, Delete) { |
| 341 // Ensure that configs get deleted when removed. | 347 // Ensure that configs get deleted when removed. |
| 342 SetConfigs("a", 800, | 348 SetConfigs("a", 800, 1, |
| 343 "b", 900, | 349 "b", 900, 1, |
| 344 "c", 1100, | 350 "c", 1100, 1, |
| 345 NULL); | 351 NULL); |
| 346 SetConfigs("b", 900, | 352 test_peer_.CheckConfigs( |
| 347 "c", 1100, | 353 "a", false, |
| 354 "b", true, |
| 355 "c", false, |
| 356 NULL); |
| 357 SetConfigs("b", 900, 1, |
| 358 "c", 1100, 1, |
| 348 NULL); | 359 NULL); |
| 349 test_peer_.CheckConfigs( | 360 test_peer_.CheckConfigs( |
| 350 "b", true, | 361 "b", true, |
| 351 "c", false, | 362 "c", false, |
| 352 NULL); | 363 NULL); |
| 353 } | 364 } |
| 354 | 365 |
| 355 TEST_F(CryptoServerConfigsTest, DontDeletePrimary) { | 366 TEST_F(CryptoServerConfigsTest, DeletePrimary) { |
| 356 // Ensure that the primary config isn't deleted when removed. | 367 // Ensure that deleting the primary config works. |
| 357 SetConfigs("a", 800, | 368 SetConfigs("a", 800, 1, |
| 358 "b", 900, | 369 "b", 900, 1, |
| 359 "c", 1100, | 370 "c", 1100, 1, |
| 360 NULL); | |
| 361 SetConfigs("a", 800, | |
| 362 "c", 1100, | |
| 363 NULL); | 371 NULL); |
| 364 test_peer_.CheckConfigs( | 372 test_peer_.CheckConfigs( |
| 365 "a", false, | 373 "a", false, |
| 366 "b", true, | 374 "b", true, |
| 367 "c", false, | 375 "c", false, |
| 368 NULL); | 376 NULL); |
| 377 SetConfigs("a", 800, 1, |
| 378 "c", 1100, 1, |
| 379 NULL); |
| 380 test_peer_.CheckConfigs( |
| 381 "a", true, |
| 382 "c", false, |
| 383 NULL); |
| 384 } |
| 385 |
| 386 TEST_F(CryptoServerConfigsTest, FailIfDeletingAllConfigs) { |
| 387 // Ensure that configs get deleted when removed. |
| 388 SetConfigs("a", 800, 1, |
| 389 "b", 900, 1, |
| 390 NULL); |
| 391 test_peer_.CheckConfigs( |
| 392 "a", false, |
| 393 "b", true, |
| 394 NULL); |
| 395 SetConfigs(NULL); |
| 396 // Config change is rejected, still using old configs. |
| 397 test_peer_.CheckConfigs( |
| 398 "a", false, |
| 399 "b", true, |
| 400 NULL); |
| 401 } |
| 402 |
| 403 TEST_F(CryptoServerConfigsTest, ChangePrimaryTime) { |
| 404 // Check that updates to primary time get picked up. |
| 405 SetConfigs("a", 400, 1, |
| 406 "b", 800, 1, |
| 407 "c", 1200, 1, |
| 408 NULL); |
| 409 test_peer_.SelectNewPrimaryConfig(500); |
| 410 test_peer_.CheckConfigs( |
| 411 "a", true, |
| 412 "b", false, |
| 413 "c", false, |
| 414 NULL); |
| 415 SetConfigs("a", 1200, 1, |
| 416 "b", 800, 1, |
| 417 "c", 400, 1, |
| 418 NULL); |
| 419 test_peer_.SelectNewPrimaryConfig(500); |
| 420 test_peer_.CheckConfigs( |
| 421 "a", false, |
| 422 "b", false, |
| 423 "c", true, |
| 424 NULL); |
| 425 } |
| 426 |
| 427 TEST_F(CryptoServerConfigsTest, AllConfigsInThePast) { |
| 428 // Check that the most recent config is selected. |
| 429 SetConfigs("a", 400, 1, |
| 430 "b", 800, 1, |
| 431 "c", 1200, 1, |
| 432 NULL); |
| 433 test_peer_.SelectNewPrimaryConfig(1500); |
| 434 test_peer_.CheckConfigs( |
| 435 "a", false, |
| 436 "b", false, |
| 437 "c", true, |
| 438 NULL); |
| 439 } |
| 440 |
| 441 TEST_F(CryptoServerConfigsTest, AllConfigsInTheFuture) { |
| 442 // Check that the first config is selected. |
| 443 SetConfigs("a", 400, 1, |
| 444 "b", 800, 1, |
| 445 "c", 1200, 1, |
| 446 NULL); |
| 447 test_peer_.SelectNewPrimaryConfig(100); |
| 448 test_peer_.CheckConfigs( |
| 449 "a", true, |
| 450 "b", false, |
| 451 "c", false, |
| 452 NULL); |
| 453 } |
| 454 |
| 455 TEST_F(CryptoServerConfigsTest, SortByPriority) { |
| 456 // Check that priority is used to decide on a primary config when |
| 457 // configs have the same primary time. |
| 458 SetConfigs("a", 900, 1, |
| 459 "b", 900, 2, |
| 460 "c", 900, 3, |
| 461 NULL); |
| 462 test_peer_.CheckConfigs( |
| 463 "a", true, |
| 464 "b", false, |
| 465 "c", false, |
| 466 NULL); |
| 467 test_peer_.SelectNewPrimaryConfig(800); |
| 468 test_peer_.CheckConfigs( |
| 469 "a", true, |
| 470 "b", false, |
| 471 "c", false, |
| 472 NULL); |
| 473 test_peer_.SelectNewPrimaryConfig(1000); |
| 474 test_peer_.CheckConfigs( |
| 475 "a", true, |
| 476 "b", false, |
| 477 "c", false, |
| 478 NULL); |
| 479 |
| 480 // Change priorities and expect sort order to change. |
| 481 SetConfigs("a", 900, 2, |
| 482 "b", 900, 1, |
| 483 "c", 900, 0, |
| 484 NULL); |
| 485 test_peer_.CheckConfigs( |
| 486 "a", false, |
| 487 "b", false, |
| 488 "c", true, |
| 489 NULL); |
| 490 test_peer_.SelectNewPrimaryConfig(800); |
| 491 test_peer_.CheckConfigs( |
| 492 "a", false, |
| 493 "b", false, |
| 494 "c", true, |
| 495 NULL); |
| 496 test_peer_.SelectNewPrimaryConfig(1000); |
| 497 test_peer_.CheckConfigs( |
| 498 "a", false, |
| 499 "b", false, |
| 500 "c", true, |
| 501 NULL); |
| 369 } | 502 } |
| 370 | 503 |
| 371 TEST_F(CryptoServerConfigsTest, AdvancePrimary) { | 504 TEST_F(CryptoServerConfigsTest, AdvancePrimary) { |
| 372 // Check that a new primary config is enabled at the right time. | 505 // Check that a new primary config is enabled at the right time. |
| 373 SetConfigs("a", 900, | 506 SetConfigs("a", 900, 1, |
| 374 "b", 1100, | 507 "b", 1100, 1, |
| 375 NULL); | 508 NULL); |
| 376 test_peer_.SelectNewPrimaryConfig(1000); | 509 test_peer_.SelectNewPrimaryConfig(1000); |
| 377 test_peer_.CheckConfigs( | 510 test_peer_.CheckConfigs( |
| 378 "a", true, | 511 "a", true, |
| 379 "b", false, | 512 "b", false, |
| 380 NULL); | 513 NULL); |
| 381 test_peer_.SelectNewPrimaryConfig(1101); | 514 test_peer_.SelectNewPrimaryConfig(1101); |
| 382 test_peer_.CheckConfigs( | 515 test_peer_.CheckConfigs( |
| 383 "a", false, | 516 "a", false, |
| 384 "b", true, | 517 "b", true, |
| 385 NULL); | 518 NULL); |
| 386 } | 519 } |
| 387 | 520 |
| 388 TEST_F(CryptoServerConfigsTest, InvalidConfigs) { | 521 TEST_F(CryptoServerConfigsTest, InvalidConfigs) { |
| 389 // Ensure that invalid configs don't change anything. | 522 // Ensure that invalid configs don't change anything. |
| 390 SetConfigs("a", 800, | 523 SetConfigs("a", 800, 1, |
| 391 "b", 900, | 524 "b", 900, 1, |
| 392 "c", 1100, | 525 "c", 1100, 1, |
| 393 NULL); | 526 NULL); |
| 394 test_peer_.CheckConfigs( | 527 test_peer_.CheckConfigs( |
| 395 "a", false, | 528 "a", false, |
| 396 "b", true, | 529 "b", true, |
| 397 "c", false, | 530 "c", false, |
| 398 NULL); | 531 NULL); |
| 399 SetConfigs("a", 800, | 532 SetConfigs("a", 800, 1, |
| 400 "c", 1100, | 533 "c", 1100, 1, |
| 401 "INVALID1", 1000, | 534 "INVALID1", 1000, 1, |
| 402 NULL); | 535 NULL); |
| 403 test_peer_.CheckConfigs( | 536 test_peer_.CheckConfigs( |
| 404 "a", false, | 537 "a", false, |
| 405 "b", true, | 538 "b", true, |
| 406 "c", false, | 539 "c", false, |
| 407 NULL); | 540 NULL); |
| 408 } | 541 } |
| 409 | 542 |
| 410 } // namespace test | 543 } // namespace test |
| 411 } // namespace net | 544 } // namespace net |
| OLD | NEW |