| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 "base/bind.h" | 5 #include "base/bind.h" |
| 6 #include "base/file_util.h" | 6 #include "base/file_util.h" |
| 7 #include "base/memory/ref_counted.h" | 7 #include "base/memory/ref_counted.h" |
| 8 #include "base/memory/scoped_vector.h" | 8 #include "base/memory/scoped_vector.h" |
| 9 #include "base/message_loop.h" | 9 #include "base/message_loop.h" |
| 10 #include "base/scoped_temp_dir.h" | 10 #include "base/scoped_temp_dir.h" |
| 11 #include "base/stl_util.h" | 11 #include "base/stl_util.h" |
| 12 #include "base/test/thread_test_helper.h" | 12 #include "base/test/thread_test_helper.h" |
| 13 #include "chrome/browser/net/sqlite_origin_bound_cert_store.h" | 13 #include "chrome/browser/net/sqlite_origin_bound_cert_store.h" |
| 14 #include "chrome/common/chrome_constants.h" | 14 #include "chrome/common/chrome_constants.h" |
| 15 #include "content/test/test_browser_thread.h" | 15 #include "content/test/test_browser_thread.h" |
| 16 #include "net/base/cert_test_util.h" | 16 #include "net/base/cert_test_util.h" |
| 17 #include "sql/statement.h" | 17 #include "sql/statement.h" |
| 18 #include "testing/gtest/include/gtest/gtest.h" | 18 #include "testing/gtest/include/gtest/gtest.h" |
| 19 | 19 |
| 20 using content::BrowserThread; | 20 using content::BrowserThread; |
| 21 | 21 |
| 22 class SQLiteOriginBoundCertStoreTest : public testing::Test { | 22 class SQLiteServerBoundCertStoreTest : public testing::Test { |
| 23 public: | 23 public: |
| 24 SQLiteOriginBoundCertStoreTest() | 24 SQLiteServerBoundCertStoreTest() |
| 25 : db_thread_(BrowserThread::DB) { | 25 : db_thread_(BrowserThread::DB) { |
| 26 } | 26 } |
| 27 | 27 |
| 28 protected: | 28 protected: |
| 29 static void ReadTestKeyAndCert(std::string* key, std::string* cert) { | 29 static void ReadTestKeyAndCert(std::string* key, std::string* cert) { |
| 30 FilePath key_path = net::GetTestCertsDirectory().AppendASCII( | 30 FilePath key_path = net::GetTestCertsDirectory().AppendASCII( |
| 31 "unittest.originbound.key.der"); | 31 "unittest.originbound.key.der"); |
| 32 FilePath cert_path = net::GetTestCertsDirectory().AppendASCII( | 32 FilePath cert_path = net::GetTestCertsDirectory().AppendASCII( |
| 33 "unittest.originbound.der"); | 33 "unittest.originbound.der"); |
| 34 ASSERT_TRUE(file_util::ReadFileToString(key_path, key)); | 34 ASSERT_TRUE(file_util::ReadFileToString(key_path, key)); |
| (...skipping 18 matching lines...) Expand all Loading... |
| 53 exploded_time.hour = 2; | 53 exploded_time.hour = 2; |
| 54 exploded_time.minute = 23; | 54 exploded_time.minute = 23; |
| 55 exploded_time.second = 45; | 55 exploded_time.second = 45; |
| 56 exploded_time.millisecond = 0; | 56 exploded_time.millisecond = 0; |
| 57 return base::Time::FromUTCExploded(exploded_time); | 57 return base::Time::FromUTCExploded(exploded_time); |
| 58 } | 58 } |
| 59 | 59 |
| 60 virtual void SetUp() { | 60 virtual void SetUp() { |
| 61 db_thread_.Start(); | 61 db_thread_.Start(); |
| 62 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); | 62 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| 63 store_ = new SQLiteOriginBoundCertStore( | 63 store_ = new SQLiteServerBoundCertStore( |
| 64 temp_dir_.path().Append(chrome::kOBCertFilename)); | 64 temp_dir_.path().Append(chrome::kOBCertFilename)); |
| 65 ScopedVector<net::DefaultOriginBoundCertStore::OriginBoundCert> certs; | 65 ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert> certs; |
| 66 ASSERT_TRUE(store_->Load(&certs.get())); | 66 ASSERT_TRUE(store_->Load(&certs.get())); |
| 67 ASSERT_EQ(0u, certs.size()); | 67 ASSERT_EQ(0u, certs.size()); |
| 68 // Make sure the store gets written at least once. | 68 // Make sure the store gets written at least once. |
| 69 store_->AddOriginBoundCert( | 69 store_->AddServerBoundCert( |
| 70 net::DefaultOriginBoundCertStore::OriginBoundCert( | 70 net::DefaultServerBoundCertStore::ServerBoundCert( |
| 71 "https://encrypted.google.com:8443", | 71 "google.com", |
| 72 net::CLIENT_CERT_RSA_SIGN, | 72 net::CLIENT_CERT_RSA_SIGN, |
| 73 base::Time::FromInternalValue(1), | 73 base::Time::FromInternalValue(1), |
| 74 base::Time::FromInternalValue(2), | 74 base::Time::FromInternalValue(2), |
| 75 "a", "b")); | 75 "a", "b")); |
| 76 } | 76 } |
| 77 | 77 |
| 78 content::TestBrowserThread db_thread_; | 78 content::TestBrowserThread db_thread_; |
| 79 ScopedTempDir temp_dir_; | 79 ScopedTempDir temp_dir_; |
| 80 scoped_refptr<SQLiteOriginBoundCertStore> store_; | 80 scoped_refptr<SQLiteServerBoundCertStore> store_; |
| 81 }; | 81 }; |
| 82 | 82 |
| 83 TEST_F(SQLiteOriginBoundCertStoreTest, KeepOnDestruction) { | 83 TEST_F(SQLiteServerBoundCertStoreTest, KeepOnDestruction) { |
| 84 store_->SetClearLocalStateOnExit(false); | 84 store_->SetClearLocalStateOnExit(false); |
| 85 store_ = NULL; | 85 store_ = NULL; |
| 86 // Make sure we wait until the destructor has run. | 86 // Make sure we wait until the destructor has run. |
| 87 scoped_refptr<base::ThreadTestHelper> helper( | 87 scoped_refptr<base::ThreadTestHelper> helper( |
| 88 new base::ThreadTestHelper( | 88 new base::ThreadTestHelper( |
| 89 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); | 89 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); |
| 90 ASSERT_TRUE(helper->Run()); | 90 ASSERT_TRUE(helper->Run()); |
| 91 | 91 |
| 92 ASSERT_TRUE(file_util::PathExists( | 92 ASSERT_TRUE(file_util::PathExists( |
| 93 temp_dir_.path().Append(chrome::kOBCertFilename))); | 93 temp_dir_.path().Append(chrome::kOBCertFilename))); |
| 94 ASSERT_TRUE(file_util::Delete( | 94 ASSERT_TRUE(file_util::Delete( |
| 95 temp_dir_.path().Append(chrome::kOBCertFilename), false)); | 95 temp_dir_.path().Append(chrome::kOBCertFilename), false)); |
| 96 } | 96 } |
| 97 | 97 |
| 98 TEST_F(SQLiteOriginBoundCertStoreTest, RemoveOnDestruction) { | 98 TEST_F(SQLiteServerBoundCertStoreTest, RemoveOnDestruction) { |
| 99 store_->SetClearLocalStateOnExit(true); | 99 store_->SetClearLocalStateOnExit(true); |
| 100 // Replace the store effectively destroying the current one and forcing it | 100 // Replace the store effectively destroying the current one and forcing it |
| 101 // to write it's data to disk. Then we can see if after loading it again it | 101 // to write it's data to disk. Then we can see if after loading it again it |
| 102 // is still there. | 102 // is still there. |
| 103 store_ = NULL; | 103 store_ = NULL; |
| 104 // Make sure we wait until the destructor has run. | 104 // Make sure we wait until the destructor has run. |
| 105 scoped_refptr<base::ThreadTestHelper> helper( | 105 scoped_refptr<base::ThreadTestHelper> helper( |
| 106 new base::ThreadTestHelper( | 106 new base::ThreadTestHelper( |
| 107 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); | 107 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); |
| 108 ASSERT_TRUE(helper->Run()); | 108 ASSERT_TRUE(helper->Run()); |
| 109 | 109 |
| 110 ASSERT_FALSE(file_util::PathExists( | 110 ASSERT_FALSE(file_util::PathExists( |
| 111 temp_dir_.path().Append(chrome::kOBCertFilename))); | 111 temp_dir_.path().Append(chrome::kOBCertFilename))); |
| 112 } | 112 } |
| 113 | 113 |
| 114 // Test if data is stored as expected in the SQLite database. | 114 // Test if data is stored as expected in the SQLite database. |
| 115 TEST_F(SQLiteOriginBoundCertStoreTest, TestPersistence) { | 115 TEST_F(SQLiteServerBoundCertStoreTest, TestPersistence) { |
| 116 store_->AddOriginBoundCert( | 116 store_->AddServerBoundCert( |
| 117 net::DefaultOriginBoundCertStore::OriginBoundCert( | 117 net::DefaultServerBoundCertStore::ServerBoundCert( |
| 118 "https://www.google.com/", | 118 "foo.com", |
| 119 net::CLIENT_CERT_ECDSA_SIGN, | 119 net::CLIENT_CERT_ECDSA_SIGN, |
| 120 base::Time::FromInternalValue(3), | 120 base::Time::FromInternalValue(3), |
| 121 base::Time::FromInternalValue(4), | 121 base::Time::FromInternalValue(4), |
| 122 "c", "d")); | 122 "c", "d")); |
| 123 | 123 |
| 124 ScopedVector<net::DefaultOriginBoundCertStore::OriginBoundCert> certs; | 124 ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert> certs; |
| 125 // Replace the store effectively destroying the current one and forcing it | 125 // Replace the store effectively destroying the current one and forcing it |
| 126 // to write it's data to disk. Then we can see if after loading it again it | 126 // to write it's data to disk. Then we can see if after loading it again it |
| 127 // is still there. | 127 // is still there. |
| 128 store_ = NULL; | 128 store_ = NULL; |
| 129 scoped_refptr<base::ThreadTestHelper> helper( | 129 scoped_refptr<base::ThreadTestHelper> helper( |
| 130 new base::ThreadTestHelper( | 130 new base::ThreadTestHelper( |
| 131 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); | 131 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); |
| 132 // Make sure we wait until the destructor has run. | 132 // Make sure we wait until the destructor has run. |
| 133 ASSERT_TRUE(helper->Run()); | 133 ASSERT_TRUE(helper->Run()); |
| 134 store_ = new SQLiteOriginBoundCertStore( | 134 store_ = new SQLiteServerBoundCertStore( |
| 135 temp_dir_.path().Append(chrome::kOBCertFilename)); | 135 temp_dir_.path().Append(chrome::kOBCertFilename)); |
| 136 | 136 |
| 137 // Reload and test for persistence | 137 // Reload and test for persistence |
| 138 ASSERT_TRUE(store_->Load(&certs.get())); | 138 ASSERT_TRUE(store_->Load(&certs.get())); |
| 139 ASSERT_EQ(2U, certs.size()); | 139 ASSERT_EQ(2U, certs.size()); |
| 140 net::DefaultOriginBoundCertStore::OriginBoundCert* ec_cert; | 140 net::DefaultServerBoundCertStore::ServerBoundCert* ec_cert; |
| 141 net::DefaultOriginBoundCertStore::OriginBoundCert* rsa_cert; | 141 net::DefaultServerBoundCertStore::ServerBoundCert* rsa_cert; |
| 142 if (net::CLIENT_CERT_RSA_SIGN == certs[0]->type()) { | 142 if (net::CLIENT_CERT_RSA_SIGN == certs[0]->type()) { |
| 143 rsa_cert = certs[0]; | 143 rsa_cert = certs[0]; |
| 144 ec_cert = certs[1]; | 144 ec_cert = certs[1]; |
| 145 } else { | 145 } else { |
| 146 rsa_cert = certs[1]; | 146 rsa_cert = certs[1]; |
| 147 ec_cert = certs[0]; | 147 ec_cert = certs[0]; |
| 148 } | 148 } |
| 149 ASSERT_STREQ("https://encrypted.google.com:8443", rsa_cert->origin().c_str()); | 149 ASSERT_STREQ("google.com", rsa_cert->server().c_str()); |
| 150 ASSERT_EQ(net::CLIENT_CERT_RSA_SIGN, rsa_cert->type()); | 150 ASSERT_EQ(net::CLIENT_CERT_RSA_SIGN, rsa_cert->type()); |
| 151 ASSERT_STREQ("a", rsa_cert->private_key().c_str()); | 151 ASSERT_STREQ("a", rsa_cert->private_key().c_str()); |
| 152 ASSERT_STREQ("b", rsa_cert->cert().c_str()); | 152 ASSERT_STREQ("b", rsa_cert->cert().c_str()); |
| 153 ASSERT_EQ(1, rsa_cert->creation_time().ToInternalValue()); | 153 ASSERT_EQ(1, rsa_cert->creation_time().ToInternalValue()); |
| 154 ASSERT_EQ(2, rsa_cert->expiration_time().ToInternalValue()); | 154 ASSERT_EQ(2, rsa_cert->expiration_time().ToInternalValue()); |
| 155 ASSERT_STREQ("https://www.google.com/", ec_cert->origin().c_str()); | 155 ASSERT_STREQ("foo.com", ec_cert->server().c_str()); |
| 156 ASSERT_EQ(net::CLIENT_CERT_ECDSA_SIGN, ec_cert->type()); | 156 ASSERT_EQ(net::CLIENT_CERT_ECDSA_SIGN, ec_cert->type()); |
| 157 ASSERT_STREQ("c", ec_cert->private_key().c_str()); | 157 ASSERT_STREQ("c", ec_cert->private_key().c_str()); |
| 158 ASSERT_STREQ("d", ec_cert->cert().c_str()); | 158 ASSERT_STREQ("d", ec_cert->cert().c_str()); |
| 159 ASSERT_EQ(3, ec_cert->creation_time().ToInternalValue()); | 159 ASSERT_EQ(3, ec_cert->creation_time().ToInternalValue()); |
| 160 ASSERT_EQ(4, ec_cert->expiration_time().ToInternalValue()); | 160 ASSERT_EQ(4, ec_cert->expiration_time().ToInternalValue()); |
| 161 | 161 |
| 162 // Now delete the cert and check persistence again. | 162 // Now delete the cert and check persistence again. |
| 163 store_->DeleteOriginBoundCert(*certs[0]); | 163 store_->DeleteServerBoundCert(*certs[0]); |
| 164 store_->DeleteOriginBoundCert(*certs[1]); | 164 store_->DeleteServerBoundCert(*certs[1]); |
| 165 store_ = NULL; | 165 store_ = NULL; |
| 166 // Make sure we wait until the destructor has run. | 166 // Make sure we wait until the destructor has run. |
| 167 ASSERT_TRUE(helper->Run()); | 167 ASSERT_TRUE(helper->Run()); |
| 168 certs.reset(); | 168 certs.reset(); |
| 169 store_ = new SQLiteOriginBoundCertStore( | 169 store_ = new SQLiteServerBoundCertStore( |
| 170 temp_dir_.path().Append(chrome::kOBCertFilename)); | 170 temp_dir_.path().Append(chrome::kOBCertFilename)); |
| 171 | 171 |
| 172 // Reload and check if the cert has been removed. | 172 // Reload and check if the cert has been removed. |
| 173 ASSERT_TRUE(store_->Load(&certs.get())); | 173 ASSERT_TRUE(store_->Load(&certs.get())); |
| 174 ASSERT_EQ(0U, certs.size()); | 174 ASSERT_EQ(0U, certs.size()); |
| 175 } | 175 } |
| 176 | 176 |
| 177 TEST_F(SQLiteOriginBoundCertStoreTest, TestUpgradeV1) { | 177 TEST_F(SQLiteServerBoundCertStoreTest, TestUpgradeV1) { |
| 178 // Reset the store. We'll be using a different database for this test. | 178 // Reset the store. We'll be using a different database for this test. |
| 179 store_ = NULL; | 179 store_ = NULL; |
| 180 | 180 |
| 181 FilePath v1_db_path(temp_dir_.path().AppendASCII("v1db")); | 181 FilePath v1_db_path(temp_dir_.path().AppendASCII("v1db")); |
| 182 | 182 |
| 183 std::string key_data; | 183 std::string key_data; |
| 184 std::string cert_data; | 184 std::string cert_data; |
| 185 ReadTestKeyAndCert(&key_data, &cert_data); | 185 ReadTestKeyAndCert(&key_data, &cert_data); |
| 186 | 186 |
| 187 // Create a version 1 database. | 187 // Create a version 1 database. |
| 188 { | 188 { |
| 189 sql::Connection db; | 189 sql::Connection db; |
| 190 ASSERT_TRUE(db.Open(v1_db_path)); | 190 ASSERT_TRUE(db.Open(v1_db_path)); |
| 191 ASSERT_TRUE(db.Execute( | 191 ASSERT_TRUE(db.Execute( |
| 192 "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY," | 192 "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY," |
| 193 "value LONGVARCHAR);" | 193 "value LONGVARCHAR);" |
| 194 "INSERT INTO \"meta\" VALUES('version','1');" | 194 "INSERT INTO \"meta\" VALUES('version','1');" |
| 195 "INSERT INTO \"meta\" VALUES('last_compatible_version','1');" | 195 "INSERT INTO \"meta\" VALUES('last_compatible_version','1');" |
| 196 "CREATE TABLE origin_bound_certs (" | 196 "CREATE TABLE origin_bound_certs (" |
| 197 "origin TEXT NOT NULL UNIQUE PRIMARY KEY," | 197 "origin TEXT NOT NULL UNIQUE PRIMARY KEY," |
| 198 "private_key BLOB NOT NULL,cert BLOB NOT NULL);")); | 198 "private_key BLOB NOT NULL,cert BLOB NOT NULL);")); |
| 199 | 199 |
| 200 sql::Statement add_smt(db.GetUniqueStatement( | 200 sql::Statement add_smt(db.GetUniqueStatement( |
| 201 "INSERT INTO origin_bound_certs (origin, private_key, cert) " | 201 "INSERT INTO origin_bound_certs (origin, private_key, cert) " |
| 202 "VALUES (?,?,?)")); | 202 "VALUES (?,?,?)")); |
| 203 add_smt.BindString(0, "https://www.google.com:443"); | 203 add_smt.BindString(0, "google.com"); |
| 204 add_smt.BindBlob(1, key_data.data(), key_data.size()); | 204 add_smt.BindBlob(1, key_data.data(), key_data.size()); |
| 205 add_smt.BindBlob(2, cert_data.data(), cert_data.size()); | 205 add_smt.BindBlob(2, cert_data.data(), cert_data.size()); |
| 206 ASSERT_TRUE(add_smt.Run()); | 206 ASSERT_TRUE(add_smt.Run()); |
| 207 | 207 |
| 208 ASSERT_TRUE(db.Execute( | 208 ASSERT_TRUE(db.Execute( |
| 209 "INSERT INTO \"origin_bound_certs\" VALUES(" | 209 "INSERT INTO \"origin_bound_certs\" VALUES(" |
| 210 "'https://foo.com',X'AA',X'BB');" | 210 "'foo.com',X'AA',X'BB');" |
| 211 )); | 211 )); |
| 212 } | 212 } |
| 213 | 213 |
| 214 // Load and test the DB contents twice. First time ensures that we can use | 214 // Load and test the DB contents twice. First time ensures that we can use |
| 215 // the updated values immediately. Second time ensures that the updated | 215 // the updated values immediately. Second time ensures that the updated |
| 216 // values are stored and read correctly on next load. | 216 // values are stored and read correctly on next load. |
| 217 for (int i = 0; i < 2; ++i) { | 217 for (int i = 0; i < 2; ++i) { |
| 218 SCOPED_TRACE(i); | 218 SCOPED_TRACE(i); |
| 219 | 219 |
| 220 ScopedVector<net::DefaultOriginBoundCertStore::OriginBoundCert> certs; | 220 ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert> certs; |
| 221 store_ = new SQLiteOriginBoundCertStore(v1_db_path); | 221 store_ = new SQLiteServerBoundCertStore(v1_db_path); |
| 222 | 222 |
| 223 // Load the database and ensure the certs can be read and are marked as RSA. | 223 // Load the database and ensure the certs can be read and are marked as RSA. |
| 224 ASSERT_TRUE(store_->Load(&certs.get())); | 224 ASSERT_TRUE(store_->Load(&certs.get())); |
| 225 ASSERT_EQ(2U, certs.size()); | 225 ASSERT_EQ(2U, certs.size()); |
| 226 | 226 |
| 227 ASSERT_STREQ("https://www.google.com:443", certs[0]->origin().c_str()); | 227 ASSERT_STREQ("google.com", certs[0]->server().c_str()); |
| 228 ASSERT_EQ(net::CLIENT_CERT_RSA_SIGN, certs[0]->type()); | 228 ASSERT_EQ(net::CLIENT_CERT_RSA_SIGN, certs[0]->type()); |
| 229 ASSERT_EQ(GetTestCertExpirationTime(), | 229 ASSERT_EQ(GetTestCertExpirationTime(), |
| 230 certs[0]->expiration_time()); | 230 certs[0]->expiration_time()); |
| 231 ASSERT_EQ(key_data, certs[0]->private_key()); | 231 ASSERT_EQ(key_data, certs[0]->private_key()); |
| 232 ASSERT_EQ(cert_data, certs[0]->cert()); | 232 ASSERT_EQ(cert_data, certs[0]->cert()); |
| 233 | 233 |
| 234 ASSERT_STREQ("https://foo.com", certs[1]->origin().c_str()); | 234 ASSERT_STREQ("foo.com", certs[1]->server().c_str()); |
| 235 ASSERT_EQ(net::CLIENT_CERT_RSA_SIGN, certs[1]->type()); | 235 ASSERT_EQ(net::CLIENT_CERT_RSA_SIGN, certs[1]->type()); |
| 236 // Undecodable cert, expiration time will be uninitialized. | 236 // Undecodable cert, expiration time will be uninitialized. |
| 237 ASSERT_EQ(base::Time(), certs[1]->expiration_time()); | 237 ASSERT_EQ(base::Time(), certs[1]->expiration_time()); |
| 238 ASSERT_STREQ("\xaa", certs[1]->private_key().c_str()); | 238 ASSERT_STREQ("\xaa", certs[1]->private_key().c_str()); |
| 239 ASSERT_STREQ("\xbb", certs[1]->cert().c_str()); | 239 ASSERT_STREQ("\xbb", certs[1]->cert().c_str()); |
| 240 | 240 |
| 241 store_ = NULL; | 241 store_ = NULL; |
| 242 // Make sure we wait until the destructor has run. | 242 // Make sure we wait until the destructor has run. |
| 243 scoped_refptr<base::ThreadTestHelper> helper( | 243 scoped_refptr<base::ThreadTestHelper> helper( |
| 244 new base::ThreadTestHelper( | 244 new base::ThreadTestHelper( |
| 245 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); | 245 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); |
| 246 ASSERT_TRUE(helper->Run()); | 246 ASSERT_TRUE(helper->Run()); |
| 247 | 247 |
| 248 // Verify the database version is updated. | 248 // Verify the database version is updated. |
| 249 { | 249 { |
| 250 sql::Connection db; | 250 sql::Connection db; |
| 251 ASSERT_TRUE(db.Open(v1_db_path)); | 251 ASSERT_TRUE(db.Open(v1_db_path)); |
| 252 sql::Statement smt(db.GetUniqueStatement( | 252 sql::Statement smt(db.GetUniqueStatement( |
| 253 "SELECT value FROM meta WHERE key = \"version\"")); | 253 "SELECT value FROM meta WHERE key = \"version\"")); |
| 254 ASSERT_TRUE(smt.Step()); | 254 ASSERT_TRUE(smt.Step()); |
| 255 EXPECT_EQ(4, smt.ColumnInt(0)); | 255 EXPECT_EQ(4, smt.ColumnInt(0)); |
| 256 EXPECT_FALSE(smt.Step()); | 256 EXPECT_FALSE(smt.Step()); |
| 257 } | 257 } |
| 258 } | 258 } |
| 259 } | 259 } |
| 260 | 260 |
| 261 TEST_F(SQLiteOriginBoundCertStoreTest, TestUpgradeV2) { | 261 TEST_F(SQLiteServerBoundCertStoreTest, TestUpgradeV2) { |
| 262 // Reset the store. We'll be using a different database for this test. | 262 // Reset the store. We'll be using a different database for this test. |
| 263 store_ = NULL; | 263 store_ = NULL; |
| 264 | 264 |
| 265 FilePath v2_db_path(temp_dir_.path().AppendASCII("v2db")); | 265 FilePath v2_db_path(temp_dir_.path().AppendASCII("v2db")); |
| 266 | 266 |
| 267 std::string key_data; | 267 std::string key_data; |
| 268 std::string cert_data; | 268 std::string cert_data; |
| 269 ReadTestKeyAndCert(&key_data, &cert_data); | 269 ReadTestKeyAndCert(&key_data, &cert_data); |
| 270 | 270 |
| 271 // Create a version 2 database. | 271 // Create a version 2 database. |
| 272 { | 272 { |
| 273 sql::Connection db; | 273 sql::Connection db; |
| 274 ASSERT_TRUE(db.Open(v2_db_path)); | 274 ASSERT_TRUE(db.Open(v2_db_path)); |
| 275 ASSERT_TRUE(db.Execute( | 275 ASSERT_TRUE(db.Execute( |
| 276 "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY," | 276 "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY," |
| 277 "value LONGVARCHAR);" | 277 "value LONGVARCHAR);" |
| 278 "INSERT INTO \"meta\" VALUES('version','2');" | 278 "INSERT INTO \"meta\" VALUES('version','2');" |
| 279 "INSERT INTO \"meta\" VALUES('last_compatible_version','1');" | 279 "INSERT INTO \"meta\" VALUES('last_compatible_version','1');" |
| 280 "CREATE TABLE origin_bound_certs (" | 280 "CREATE TABLE origin_bound_certs (" |
| 281 "origin TEXT NOT NULL UNIQUE PRIMARY KEY," | 281 "origin TEXT NOT NULL UNIQUE PRIMARY KEY," |
| 282 "private_key BLOB NOT NULL," | 282 "private_key BLOB NOT NULL," |
| 283 "cert BLOB NOT NULL," | 283 "cert BLOB NOT NULL," |
| 284 "cert_type INTEGER);" | 284 "cert_type INTEGER);" |
| 285 )); | 285 )); |
| 286 | 286 |
| 287 sql::Statement add_smt(db.GetUniqueStatement( | 287 sql::Statement add_smt(db.GetUniqueStatement( |
| 288 "INSERT INTO origin_bound_certs (origin, private_key, cert, cert_type) " | 288 "INSERT INTO origin_bound_certs (origin, private_key, cert, cert_type) " |
| 289 "VALUES (?,?,?,?)")); | 289 "VALUES (?,?,?,?)")); |
| 290 add_smt.BindString(0, "https://www.google.com:443"); | 290 add_smt.BindString(0, "google.com"); |
| 291 add_smt.BindBlob(1, key_data.data(), key_data.size()); | 291 add_smt.BindBlob(1, key_data.data(), key_data.size()); |
| 292 add_smt.BindBlob(2, cert_data.data(), cert_data.size()); | 292 add_smt.BindBlob(2, cert_data.data(), cert_data.size()); |
| 293 add_smt.BindInt64(3, 1); | 293 add_smt.BindInt64(3, 1); |
| 294 ASSERT_TRUE(add_smt.Run()); | 294 ASSERT_TRUE(add_smt.Run()); |
| 295 | 295 |
| 296 ASSERT_TRUE(db.Execute( | 296 ASSERT_TRUE(db.Execute( |
| 297 "INSERT INTO \"origin_bound_certs\" VALUES(" | 297 "INSERT INTO \"origin_bound_certs\" VALUES(" |
| 298 "'https://foo.com',X'AA',X'BB',64);" | 298 "'foo.com',X'AA',X'BB',64);" |
| 299 )); | 299 )); |
| 300 } | 300 } |
| 301 | 301 |
| 302 // Load and test the DB contents twice. First time ensures that we can use | 302 // Load and test the DB contents twice. First time ensures that we can use |
| 303 // the updated values immediately. Second time ensures that the updated | 303 // the updated values immediately. Second time ensures that the updated |
| 304 // values are saved and read correctly on next load. | 304 // values are saved and read correctly on next load. |
| 305 for (int i = 0; i < 2; ++i) { | 305 for (int i = 0; i < 2; ++i) { |
| 306 SCOPED_TRACE(i); | 306 SCOPED_TRACE(i); |
| 307 | 307 |
| 308 ScopedVector<net::DefaultOriginBoundCertStore::OriginBoundCert> certs; | 308 ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert> certs; |
| 309 store_ = new SQLiteOriginBoundCertStore(v2_db_path); | 309 store_ = new SQLiteServerBoundCertStore(v2_db_path); |
| 310 | 310 |
| 311 // Load the database and ensure the certs can be read and are marked as RSA. | 311 // Load the database and ensure the certs can be read and are marked as RSA. |
| 312 ASSERT_TRUE(store_->Load(&certs.get())); | 312 ASSERT_TRUE(store_->Load(&certs.get())); |
| 313 ASSERT_EQ(2U, certs.size()); | 313 ASSERT_EQ(2U, certs.size()); |
| 314 | 314 |
| 315 ASSERT_STREQ("https://www.google.com:443", certs[0]->origin().c_str()); | 315 ASSERT_STREQ("google.com", certs[0]->server().c_str()); |
| 316 ASSERT_EQ(net::CLIENT_CERT_RSA_SIGN, certs[0]->type()); | 316 ASSERT_EQ(net::CLIENT_CERT_RSA_SIGN, certs[0]->type()); |
| 317 ASSERT_EQ(GetTestCertExpirationTime(), | 317 ASSERT_EQ(GetTestCertExpirationTime(), |
| 318 certs[0]->expiration_time()); | 318 certs[0]->expiration_time()); |
| 319 ASSERT_EQ(key_data, certs[0]->private_key()); | 319 ASSERT_EQ(key_data, certs[0]->private_key()); |
| 320 ASSERT_EQ(cert_data, certs[0]->cert()); | 320 ASSERT_EQ(cert_data, certs[0]->cert()); |
| 321 | 321 |
| 322 ASSERT_STREQ("https://foo.com", certs[1]->origin().c_str()); | 322 ASSERT_STREQ("foo.com", certs[1]->server().c_str()); |
| 323 ASSERT_EQ(net::CLIENT_CERT_ECDSA_SIGN, certs[1]->type()); | 323 ASSERT_EQ(net::CLIENT_CERT_ECDSA_SIGN, certs[1]->type()); |
| 324 // Undecodable cert, expiration time will be uninitialized. | 324 // Undecodable cert, expiration time will be uninitialized. |
| 325 ASSERT_EQ(base::Time(), certs[1]->expiration_time()); | 325 ASSERT_EQ(base::Time(), certs[1]->expiration_time()); |
| 326 ASSERT_STREQ("\xaa", certs[1]->private_key().c_str()); | 326 ASSERT_STREQ("\xaa", certs[1]->private_key().c_str()); |
| 327 ASSERT_STREQ("\xbb", certs[1]->cert().c_str()); | 327 ASSERT_STREQ("\xbb", certs[1]->cert().c_str()); |
| 328 | 328 |
| 329 store_ = NULL; | 329 store_ = NULL; |
| 330 // Make sure we wait until the destructor has run. | 330 // Make sure we wait until the destructor has run. |
| 331 scoped_refptr<base::ThreadTestHelper> helper( | 331 scoped_refptr<base::ThreadTestHelper> helper( |
| 332 new base::ThreadTestHelper( | 332 new base::ThreadTestHelper( |
| 333 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); | 333 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); |
| 334 ASSERT_TRUE(helper->Run()); | 334 ASSERT_TRUE(helper->Run()); |
| 335 | 335 |
| 336 // Verify the database version is updated. | 336 // Verify the database version is updated. |
| 337 { | 337 { |
| 338 sql::Connection db; | 338 sql::Connection db; |
| 339 ASSERT_TRUE(db.Open(v2_db_path)); | 339 ASSERT_TRUE(db.Open(v2_db_path)); |
| 340 sql::Statement smt(db.GetUniqueStatement( | 340 sql::Statement smt(db.GetUniqueStatement( |
| 341 "SELECT value FROM meta WHERE key = \"version\"")); | 341 "SELECT value FROM meta WHERE key = \"version\"")); |
| 342 ASSERT_TRUE(smt.Step()); | 342 ASSERT_TRUE(smt.Step()); |
| 343 EXPECT_EQ(4, smt.ColumnInt(0)); | 343 EXPECT_EQ(4, smt.ColumnInt(0)); |
| 344 EXPECT_FALSE(smt.Step()); | 344 EXPECT_FALSE(smt.Step()); |
| 345 } | 345 } |
| 346 } | 346 } |
| 347 } | 347 } |
| 348 | 348 |
| 349 TEST_F(SQLiteOriginBoundCertStoreTest, TestUpgradeV3) { | 349 TEST_F(SQLiteServerBoundCertStoreTest, TestUpgradeV3) { |
| 350 // Reset the store. We'll be using a different database for this test. | 350 // Reset the store. We'll be using a different database for this test. |
| 351 store_ = NULL; | 351 store_ = NULL; |
| 352 | 352 |
| 353 FilePath v3_db_path(temp_dir_.path().AppendASCII("v3db")); | 353 FilePath v3_db_path(temp_dir_.path().AppendASCII("v3db")); |
| 354 | 354 |
| 355 std::string key_data; | 355 std::string key_data; |
| 356 std::string cert_data; | 356 std::string cert_data; |
| 357 ReadTestKeyAndCert(&key_data, &cert_data); | 357 ReadTestKeyAndCert(&key_data, &cert_data); |
| 358 | 358 |
| 359 // Create a version 3 database. | 359 // Create a version 3 database. |
| 360 { | 360 { |
| 361 sql::Connection db; | 361 sql::Connection db; |
| 362 ASSERT_TRUE(db.Open(v3_db_path)); | 362 ASSERT_TRUE(db.Open(v3_db_path)); |
| 363 ASSERT_TRUE(db.Execute( | 363 ASSERT_TRUE(db.Execute( |
| 364 "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY," | 364 "CREATE TABLE meta(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY," |
| 365 "value LONGVARCHAR);" | 365 "value LONGVARCHAR);" |
| 366 "INSERT INTO \"meta\" VALUES('version','3');" | 366 "INSERT INTO \"meta\" VALUES('version','3');" |
| 367 "INSERT INTO \"meta\" VALUES('last_compatible_version','1');" | 367 "INSERT INTO \"meta\" VALUES('last_compatible_version','1');" |
| 368 "CREATE TABLE origin_bound_certs (" | 368 "CREATE TABLE origin_bound_certs (" |
| 369 "origin TEXT NOT NULL UNIQUE PRIMARY KEY," | 369 "origin TEXT NOT NULL UNIQUE PRIMARY KEY," |
| 370 "private_key BLOB NOT NULL," | 370 "private_key BLOB NOT NULL," |
| 371 "cert BLOB NOT NULL," | 371 "cert BLOB NOT NULL," |
| 372 "cert_type INTEGER," | 372 "cert_type INTEGER," |
| 373 "expiration_time INTEGER);" | 373 "expiration_time INTEGER);" |
| 374 )); | 374 )); |
| 375 | 375 |
| 376 sql::Statement add_smt(db.GetUniqueStatement( | 376 sql::Statement add_smt(db.GetUniqueStatement( |
| 377 "INSERT INTO origin_bound_certs (origin, private_key, cert, cert_type, " | 377 "INSERT INTO origin_bound_certs (origin, private_key, cert, cert_type, " |
| 378 "expiration_time) VALUES (?,?,?,?,?)")); | 378 "expiration_time) VALUES (?,?,?,?,?)")); |
| 379 add_smt.BindString(0, "https://www.google.com:443"); | 379 add_smt.BindString(0, "google.com"); |
| 380 add_smt.BindBlob(1, key_data.data(), key_data.size()); | 380 add_smt.BindBlob(1, key_data.data(), key_data.size()); |
| 381 add_smt.BindBlob(2, cert_data.data(), cert_data.size()); | 381 add_smt.BindBlob(2, cert_data.data(), cert_data.size()); |
| 382 add_smt.BindInt64(3, 1); | 382 add_smt.BindInt64(3, 1); |
| 383 add_smt.BindInt64(4, 1000); | 383 add_smt.BindInt64(4, 1000); |
| 384 ASSERT_TRUE(add_smt.Run()); | 384 ASSERT_TRUE(add_smt.Run()); |
| 385 | 385 |
| 386 ASSERT_TRUE(db.Execute( | 386 ASSERT_TRUE(db.Execute( |
| 387 "INSERT INTO \"origin_bound_certs\" VALUES(" | 387 "INSERT INTO \"origin_bound_certs\" VALUES(" |
| 388 "'https://foo.com',X'AA',X'BB',64,2000);" | 388 "'foo.com',X'AA',X'BB',64,2000);" |
| 389 )); | 389 )); |
| 390 } | 390 } |
| 391 | 391 |
| 392 // Load and test the DB contents twice. First time ensures that we can use | 392 // Load and test the DB contents twice. First time ensures that we can use |
| 393 // the updated values immediately. Second time ensures that the updated | 393 // the updated values immediately. Second time ensures that the updated |
| 394 // values are saved and read correctly on next load. | 394 // values are saved and read correctly on next load. |
| 395 for (int i = 0; i < 2; ++i) { | 395 for (int i = 0; i < 2; ++i) { |
| 396 SCOPED_TRACE(i); | 396 SCOPED_TRACE(i); |
| 397 | 397 |
| 398 ScopedVector<net::DefaultOriginBoundCertStore::OriginBoundCert> certs; | 398 ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert> certs; |
| 399 store_ = new SQLiteOriginBoundCertStore(v3_db_path); | 399 store_ = new SQLiteServerBoundCertStore(v3_db_path); |
| 400 | 400 |
| 401 // Load the database and ensure the certs can be read and are marked as RSA. | 401 // Load the database and ensure the certs can be read and are marked as RSA. |
| 402 ASSERT_TRUE(store_->Load(&certs.get())); | 402 ASSERT_TRUE(store_->Load(&certs.get())); |
| 403 ASSERT_EQ(2U, certs.size()); | 403 ASSERT_EQ(2U, certs.size()); |
| 404 | 404 |
| 405 ASSERT_STREQ("https://www.google.com:443", certs[0]->origin().c_str()); | 405 ASSERT_STREQ("google.com", certs[0]->server().c_str()); |
| 406 ASSERT_EQ(net::CLIENT_CERT_RSA_SIGN, certs[0]->type()); | 406 ASSERT_EQ(net::CLIENT_CERT_RSA_SIGN, certs[0]->type()); |
| 407 ASSERT_EQ(1000, certs[0]->expiration_time().ToInternalValue()); | 407 ASSERT_EQ(1000, certs[0]->expiration_time().ToInternalValue()); |
| 408 ASSERT_EQ(GetTestCertCreationTime(), | 408 ASSERT_EQ(GetTestCertCreationTime(), |
| 409 certs[0]->creation_time()); | 409 certs[0]->creation_time()); |
| 410 ASSERT_EQ(key_data, certs[0]->private_key()); | 410 ASSERT_EQ(key_data, certs[0]->private_key()); |
| 411 ASSERT_EQ(cert_data, certs[0]->cert()); | 411 ASSERT_EQ(cert_data, certs[0]->cert()); |
| 412 | 412 |
| 413 ASSERT_STREQ("https://foo.com", certs[1]->origin().c_str()); | 413 ASSERT_STREQ("foo.com", certs[1]->server().c_str()); |
| 414 ASSERT_EQ(net::CLIENT_CERT_ECDSA_SIGN, certs[1]->type()); | 414 ASSERT_EQ(net::CLIENT_CERT_ECDSA_SIGN, certs[1]->type()); |
| 415 ASSERT_EQ(2000, certs[1]->expiration_time().ToInternalValue()); | 415 ASSERT_EQ(2000, certs[1]->expiration_time().ToInternalValue()); |
| 416 // Undecodable cert, creation time will be uninitialized. | 416 // Undecodable cert, creation time will be uninitialized. |
| 417 ASSERT_EQ(base::Time(), certs[1]->creation_time()); | 417 ASSERT_EQ(base::Time(), certs[1]->creation_time()); |
| 418 ASSERT_STREQ("\xaa", certs[1]->private_key().c_str()); | 418 ASSERT_STREQ("\xaa", certs[1]->private_key().c_str()); |
| 419 ASSERT_STREQ("\xbb", certs[1]->cert().c_str()); | 419 ASSERT_STREQ("\xbb", certs[1]->cert().c_str()); |
| 420 | 420 |
| 421 store_ = NULL; | 421 store_ = NULL; |
| 422 // Make sure we wait until the destructor has run. | 422 // Make sure we wait until the destructor has run. |
| 423 scoped_refptr<base::ThreadTestHelper> helper( | 423 scoped_refptr<base::ThreadTestHelper> helper( |
| 424 new base::ThreadTestHelper( | 424 new base::ThreadTestHelper( |
| 425 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); | 425 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); |
| 426 ASSERT_TRUE(helper->Run()); | 426 ASSERT_TRUE(helper->Run()); |
| 427 | 427 |
| 428 // Verify the database version is updated. | 428 // Verify the database version is updated. |
| 429 { | 429 { |
| 430 sql::Connection db; | 430 sql::Connection db; |
| 431 ASSERT_TRUE(db.Open(v3_db_path)); | 431 ASSERT_TRUE(db.Open(v3_db_path)); |
| 432 sql::Statement smt(db.GetUniqueStatement( | 432 sql::Statement smt(db.GetUniqueStatement( |
| 433 "SELECT value FROM meta WHERE key = \"version\"")); | 433 "SELECT value FROM meta WHERE key = \"version\"")); |
| 434 ASSERT_TRUE(smt.Step()); | 434 ASSERT_TRUE(smt.Step()); |
| 435 EXPECT_EQ(4, smt.ColumnInt(0)); | 435 EXPECT_EQ(4, smt.ColumnInt(0)); |
| 436 EXPECT_FALSE(smt.Step()); | 436 EXPECT_FALSE(smt.Step()); |
| 437 } | 437 } |
| 438 } | 438 } |
| 439 } | 439 } |
| 440 | 440 |
| 441 // Test that we can force the database to be written by calling Flush(). | 441 // Test that we can force the database to be written by calling Flush(). |
| 442 TEST_F(SQLiteOriginBoundCertStoreTest, TestFlush) { | 442 TEST_F(SQLiteServerBoundCertStoreTest, TestFlush) { |
| 443 // File timestamps don't work well on all platforms, so we'll determine | 443 // File timestamps don't work well on all platforms, so we'll determine |
| 444 // whether the DB file has been modified by checking its size. | 444 // whether the DB file has been modified by checking its size. |
| 445 FilePath path = temp_dir_.path().Append(chrome::kOBCertFilename); | 445 FilePath path = temp_dir_.path().Append(chrome::kOBCertFilename); |
| 446 base::PlatformFileInfo info; | 446 base::PlatformFileInfo info; |
| 447 ASSERT_TRUE(file_util::GetFileInfo(path, &info)); | 447 ASSERT_TRUE(file_util::GetFileInfo(path, &info)); |
| 448 int64 base_size = info.size; | 448 int64 base_size = info.size; |
| 449 | 449 |
| 450 // Write some certs, so the DB will have to expand by several KB. | 450 // Write some certs, so the DB will have to expand by several KB. |
| 451 for (char c = 'a'; c < 'z'; ++c) { | 451 for (char c = 'a'; c < 'z'; ++c) { |
| 452 std::string origin(1, c); | 452 std::string server(1, c); |
| 453 std::string private_key(1000, c); | 453 std::string private_key(1000, c); |
| 454 std::string cert(1000, c); | 454 std::string cert(1000, c); |
| 455 store_->AddOriginBoundCert( | 455 store_->AddServerBoundCert( |
| 456 net::DefaultOriginBoundCertStore::OriginBoundCert( | 456 net::DefaultServerBoundCertStore::ServerBoundCert( |
| 457 origin, | 457 server, |
| 458 net::CLIENT_CERT_RSA_SIGN, | 458 net::CLIENT_CERT_RSA_SIGN, |
| 459 base::Time(), | 459 base::Time(), |
| 460 base::Time(), | 460 base::Time(), |
| 461 private_key, | 461 private_key, |
| 462 cert)); | 462 cert)); |
| 463 } | 463 } |
| 464 | 464 |
| 465 // Call Flush() and wait until the DB thread is idle. | 465 // Call Flush() and wait until the DB thread is idle. |
| 466 store_->Flush(base::Closure()); | 466 store_->Flush(base::Closure()); |
| 467 scoped_refptr<base::ThreadTestHelper> helper( | 467 scoped_refptr<base::ThreadTestHelper> helper( |
| (...skipping 18 matching lines...) Expand all Loading... |
| 486 int callback_count() { | 486 int callback_count() { |
| 487 return callback_count_; | 487 return callback_count_; |
| 488 } | 488 } |
| 489 | 489 |
| 490 private: | 490 private: |
| 491 friend class base::RefCountedThreadSafe<CallbackCounter>; | 491 friend class base::RefCountedThreadSafe<CallbackCounter>; |
| 492 volatile int callback_count_; | 492 volatile int callback_count_; |
| 493 }; | 493 }; |
| 494 | 494 |
| 495 // Test that we can get a completion callback after a Flush(). | 495 // Test that we can get a completion callback after a Flush(). |
| 496 TEST_F(SQLiteOriginBoundCertStoreTest, TestFlushCompletionCallback) { | 496 TEST_F(SQLiteServerBoundCertStoreTest, TestFlushCompletionCallback) { |
| 497 scoped_refptr<CallbackCounter> counter(new CallbackCounter()); | 497 scoped_refptr<CallbackCounter> counter(new CallbackCounter()); |
| 498 | 498 |
| 499 // Callback shouldn't be invoked until we call Flush(). | 499 // Callback shouldn't be invoked until we call Flush(). |
| 500 ASSERT_EQ(0, counter->callback_count()); | 500 ASSERT_EQ(0, counter->callback_count()); |
| 501 | 501 |
| 502 store_->Flush(base::Bind(&CallbackCounter::Callback, counter.get())); | 502 store_->Flush(base::Bind(&CallbackCounter::Callback, counter.get())); |
| 503 | 503 |
| 504 scoped_refptr<base::ThreadTestHelper> helper( | 504 scoped_refptr<base::ThreadTestHelper> helper( |
| 505 new base::ThreadTestHelper( | 505 new base::ThreadTestHelper( |
| 506 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); | 506 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB))); |
| 507 ASSERT_TRUE(helper->Run()); | 507 ASSERT_TRUE(helper->Run()); |
| 508 | 508 |
| 509 ASSERT_EQ(1, counter->callback_count()); | 509 ASSERT_EQ(1, counter->callback_count()); |
| 510 } | 510 } |
| OLD | NEW |