Home | History | Annotate | Download | only in prefs
      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
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/prefs/pref_hash_store_impl.h"
      6 
      7 #include <string>
      8 
      9 #include "base/macros.h"
     10 #include "base/values.h"
     11 #include "chrome/browser/prefs/pref_hash_store_impl.h"
     12 #include "chrome/browser/prefs/pref_hash_store_transaction.h"
     13 #include "chrome/browser/prefs/tracked/dictionary_hash_store_contents.h"
     14 #include "chrome/browser/prefs/tracked/hash_store_contents.h"
     15 #include "testing/gtest/include/gtest/gtest.h"
     16 
     17 class PrefHashStoreImplTest : public testing::Test {
     18  protected:
     19   scoped_ptr<HashStoreContents> CreateHashStoreContents() {
     20     return scoped_ptr<HashStoreContents>(
     21         new DictionaryHashStoreContents(&pref_store_contents_));
     22   }
     23 
     24  private:
     25   base::DictionaryValue pref_store_contents_;
     26 };
     27 
     28 TEST_F(PrefHashStoreImplTest, AtomicHashStoreAndCheck) {
     29   base::StringValue string_1("string1");
     30   base::StringValue string_2("string2");
     31 
     32   {
     33     // 32 NULL bytes is the seed that was used to generate the legacy hash.
     34     PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
     35     scoped_ptr<PrefHashStoreTransaction> transaction(
     36         pref_hash_store.BeginTransaction(CreateHashStoreContents()));
     37 
     38     // Only NULL should be trusted in the absence of a hash.
     39     EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE,
     40               transaction->CheckValue("path1", &string_1));
     41     EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
     42               transaction->CheckValue("path1", NULL));
     43 
     44     transaction->StoreHash("path1", &string_1);
     45     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
     46               transaction->CheckValue("path1", &string_1));
     47     EXPECT_EQ(PrefHashStoreTransaction::CLEARED,
     48               transaction->CheckValue("path1", NULL));
     49     transaction->StoreHash("path1", NULL);
     50     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
     51               transaction->CheckValue("path1", NULL));
     52     EXPECT_EQ(PrefHashStoreTransaction::CHANGED,
     53               transaction->CheckValue("path1", &string_2));
     54 
     55     base::DictionaryValue dict;
     56     dict.Set("a", new base::StringValue("foo"));
     57     dict.Set("d", new base::StringValue("bad"));
     58     dict.Set("b", new base::StringValue("bar"));
     59     dict.Set("c", new base::StringValue("baz"));
     60 
     61     transaction->StoreHash("path1", &dict);
     62     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
     63               transaction->CheckValue("path1", &dict));
     64   }
     65 
     66   ASSERT_FALSE(CreateHashStoreContents()->GetSuperMac().empty());
     67 
     68   {
     69     // |pref_hash_store2| should trust its initial hashes dictionary and thus
     70     // trust new unknown values.
     71     PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", true);
     72     scoped_ptr<PrefHashStoreTransaction> transaction(
     73         pref_hash_store2.BeginTransaction(CreateHashStoreContents()));
     74     EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
     75               transaction->CheckValue("new_path", &string_1));
     76     EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
     77               transaction->CheckValue("new_path", &string_2));
     78     EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
     79               transaction->CheckValue("new_path", NULL));
     80   }
     81 
     82   // Manually corrupt the super MAC.
     83   CreateHashStoreContents()->SetSuperMac(std::string(64, 'A'));
     84 
     85   {
     86     // |pref_hash_store3| should no longer trust its initial hashes dictionary
     87     // and thus shouldn't trust non-NULL unknown values.
     88     PrefHashStoreImpl pref_hash_store3(std::string(32, 0), "device_id", true);
     89     scoped_ptr<PrefHashStoreTransaction> transaction(
     90         pref_hash_store3.BeginTransaction(CreateHashStoreContents()));
     91     EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE,
     92               transaction->CheckValue("new_path", &string_1));
     93     EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE,
     94               transaction->CheckValue("new_path", &string_2));
     95     EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
     96               transaction->CheckValue("new_path", NULL));
     97   }
     98 }
     99 
    100 TEST_F(PrefHashStoreImplTest, ImportExportOperations) {
    101   base::StringValue string_1("string1");
    102   base::StringValue string_2("string2");
    103 
    104   // Initial state: no super MAC.
    105   {
    106     PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
    107     scoped_ptr<PrefHashStoreTransaction> transaction(
    108         pref_hash_store.BeginTransaction(CreateHashStoreContents()));
    109     ASSERT_FALSE(transaction->IsSuperMACValid());
    110 
    111     ASSERT_FALSE(transaction->HasHash("path1"));
    112 
    113     // Storing a hash will stamp the super MAC.
    114     transaction->StoreHash("path1", &string_1);
    115 
    116     ASSERT_TRUE(transaction->HasHash("path1"));
    117     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
    118               transaction->CheckValue("path1", &string_1));
    119     EXPECT_EQ(PrefHashStoreTransaction::CHANGED,
    120               transaction->CheckValue("path1", &string_2));
    121   }
    122 
    123   // Make a copy of the stored hash for future use.
    124   const base::Value* hash = NULL;
    125   ASSERT_TRUE(CreateHashStoreContents()->GetContents()->Get("path1", &hash));
    126   scoped_ptr<base::Value> path_1_string_1_hash_copy(hash->DeepCopy());
    127   hash = NULL;
    128 
    129   // Verify that the super MAC was stamped.
    130   {
    131     PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
    132     scoped_ptr<PrefHashStoreTransaction> transaction(
    133         pref_hash_store.BeginTransaction(CreateHashStoreContents()));
    134     ASSERT_TRUE(transaction->IsSuperMACValid());
    135     ASSERT_TRUE(transaction->HasHash("path1"));
    136 
    137     // Clearing the hash should preserve validity.
    138     transaction->ClearHash("path1");
    139 
    140     // The effects of the clear should be immediately visible.
    141     ASSERT_FALSE(transaction->HasHash("path1"));
    142     EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
    143               transaction->CheckValue("path1", NULL));
    144     EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
    145               transaction->CheckValue("path1", &string_1));
    146   }
    147 
    148   // Verify that validity was preserved and that the clear took effect.
    149   {
    150     PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
    151     scoped_ptr<PrefHashStoreTransaction> transaction(
    152         pref_hash_store.BeginTransaction(CreateHashStoreContents()));
    153     ASSERT_TRUE(transaction->IsSuperMACValid());
    154     ASSERT_FALSE(transaction->HasHash("path1"));
    155   }
    156 
    157   // Invalidate the super MAC.
    158   CreateHashStoreContents()->SetSuperMac(std::string());
    159 
    160   {
    161     PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
    162     scoped_ptr<PrefHashStoreTransaction> transaction(
    163         pref_hash_store.BeginTransaction(CreateHashStoreContents()));
    164     ASSERT_FALSE(transaction->IsSuperMACValid());
    165     ASSERT_FALSE(transaction->HasHash("path1"));
    166 
    167     // An import should preserve invalidity.
    168     transaction->ImportHash("path1", path_1_string_1_hash_copy.get());
    169 
    170     ASSERT_TRUE(transaction->HasHash("path1"));
    171 
    172     // The imported hash should be usable for validating the original value.
    173     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
    174               transaction->CheckValue("path1", &string_1));
    175   }
    176 
    177   // Verify that invalidity was preserved and that the import took effect.
    178   {
    179     PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
    180     scoped_ptr<PrefHashStoreTransaction> transaction(
    181         pref_hash_store.BeginTransaction(CreateHashStoreContents()));
    182     ASSERT_FALSE(transaction->IsSuperMACValid());
    183     ASSERT_TRUE(transaction->HasHash("path1"));
    184     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
    185               transaction->CheckValue("path1", &string_1));
    186 
    187     // After clearing the hash, non-null values are UNTRUSTED_UNKNOWN.
    188     transaction->ClearHash("path1");
    189 
    190     EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
    191               transaction->CheckValue("path1", NULL));
    192     EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE,
    193               transaction->CheckValue("path1", &string_1));
    194   }
    195 
    196   {
    197     PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
    198     scoped_ptr<PrefHashStoreTransaction> transaction(
    199         pref_hash_store.BeginTransaction(CreateHashStoreContents()));
    200     ASSERT_FALSE(transaction->IsSuperMACValid());
    201 
    202     // Test StampSuperMac.
    203     transaction->StampSuperMac();
    204   }
    205 
    206   // Verify that the store is now valid.
    207   {
    208     PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
    209     scoped_ptr<PrefHashStoreTransaction> transaction(
    210         pref_hash_store.BeginTransaction(CreateHashStoreContents()));
    211     ASSERT_TRUE(transaction->IsSuperMACValid());
    212 
    213     // Store the hash of a different value to test an "over-import".
    214     transaction->StoreHash("path1", &string_2);
    215     EXPECT_EQ(PrefHashStoreTransaction::CHANGED,
    216               transaction->CheckValue("path1", &string_1));
    217     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
    218               transaction->CheckValue("path1", &string_2));
    219   }
    220 
    221   {
    222     PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
    223     scoped_ptr<PrefHashStoreTransaction> transaction(
    224         pref_hash_store.BeginTransaction(CreateHashStoreContents()));
    225     ASSERT_TRUE(transaction->IsSuperMACValid());
    226 
    227     // "Over-import". An import should preserve validity.
    228     transaction->ImportHash("path1", path_1_string_1_hash_copy.get());
    229     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
    230               transaction->CheckValue("path1", &string_1));
    231     EXPECT_EQ(PrefHashStoreTransaction::CHANGED,
    232               transaction->CheckValue("path1", &string_2));
    233   }
    234 
    235   // Verify that validity was preserved and the "over-import" took effect.
    236   {
    237     PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
    238     scoped_ptr<PrefHashStoreTransaction> transaction(
    239         pref_hash_store.BeginTransaction(CreateHashStoreContents()));
    240     ASSERT_TRUE(transaction->IsSuperMACValid());
    241     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
    242               transaction->CheckValue("path1", &string_1));
    243     EXPECT_EQ(PrefHashStoreTransaction::CHANGED,
    244               transaction->CheckValue("path1", &string_2));
    245   }
    246 }
    247 
    248 TEST_F(PrefHashStoreImplTest, SuperMACDisabled) {
    249   base::StringValue string_1("string1");
    250   base::StringValue string_2("string2");
    251 
    252   {
    253     // Pass |use_super_mac| => false.
    254     PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", false);
    255     scoped_ptr<PrefHashStoreTransaction> transaction(
    256         pref_hash_store.BeginTransaction(CreateHashStoreContents()));
    257 
    258     transaction->StoreHash("path1", &string_2);
    259     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
    260               transaction->CheckValue("path1", &string_2));
    261   }
    262 
    263   ASSERT_TRUE(CreateHashStoreContents()->GetSuperMac().empty());
    264 
    265   {
    266     PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", false);
    267     scoped_ptr<PrefHashStoreTransaction> transaction(
    268         pref_hash_store2.BeginTransaction(CreateHashStoreContents()));
    269     EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE,
    270               transaction->CheckValue("new_path", &string_1));
    271   }
    272 }
    273 
    274 TEST_F(PrefHashStoreImplTest, SplitHashStoreAndCheck) {
    275   base::DictionaryValue dict;
    276   dict.Set("a", new base::StringValue("to be replaced"));
    277   dict.Set("b", new base::StringValue("same"));
    278   dict.Set("o", new base::StringValue("old"));
    279 
    280   base::DictionaryValue modified_dict;
    281   modified_dict.Set("a", new base::StringValue("replaced"));
    282   modified_dict.Set("b", new base::StringValue("same"));
    283   modified_dict.Set("c", new base::StringValue("new"));
    284 
    285   base::DictionaryValue empty_dict;
    286 
    287   std::vector<std::string> invalid_keys;
    288 
    289   {
    290     PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
    291     scoped_ptr<PrefHashStoreTransaction> transaction(
    292         pref_hash_store.BeginTransaction(CreateHashStoreContents()));
    293 
    294     // No hashes stored yet and hashes dictionary is empty (and thus not
    295     // trusted).
    296     EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE,
    297               transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    298     EXPECT_TRUE(invalid_keys.empty());
    299 
    300     transaction->StoreSplitHash("path1", &dict);
    301 
    302     // Verify match post storage.
    303     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
    304               transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    305     EXPECT_TRUE(invalid_keys.empty());
    306 
    307     // Verify new path is still unknown.
    308     EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE,
    309               transaction->CheckSplitValue("path2", &dict, &invalid_keys));
    310     EXPECT_TRUE(invalid_keys.empty());
    311 
    312     // Verify NULL or empty dicts are declared as having been cleared.
    313     EXPECT_EQ(PrefHashStoreTransaction::CLEARED,
    314               transaction->CheckSplitValue("path1", NULL, &invalid_keys));
    315     EXPECT_TRUE(invalid_keys.empty());
    316     EXPECT_EQ(
    317         PrefHashStoreTransaction::CLEARED,
    318         transaction->CheckSplitValue("path1", &empty_dict, &invalid_keys));
    319     EXPECT_TRUE(invalid_keys.empty());
    320 
    321     // Verify changes are properly detected.
    322     EXPECT_EQ(
    323         PrefHashStoreTransaction::CHANGED,
    324         transaction->CheckSplitValue("path1", &modified_dict, &invalid_keys));
    325     std::vector<std::string> expected_invalid_keys1;
    326     expected_invalid_keys1.push_back("a");
    327     expected_invalid_keys1.push_back("c");
    328     expected_invalid_keys1.push_back("o");
    329     EXPECT_EQ(expected_invalid_keys1, invalid_keys);
    330     invalid_keys.clear();
    331 
    332     // Verify |dict| still matches post check.
    333     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
    334               transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    335     EXPECT_TRUE(invalid_keys.empty());
    336 
    337     // Store hash for |modified_dict|.
    338     transaction->StoreSplitHash("path1", &modified_dict);
    339 
    340     // Verify |modified_dict| is now the one that verifies correctly.
    341     EXPECT_EQ(
    342         PrefHashStoreTransaction::UNCHANGED,
    343         transaction->CheckSplitValue("path1", &modified_dict, &invalid_keys));
    344     EXPECT_TRUE(invalid_keys.empty());
    345 
    346     // Verify old dict no longer matches.
    347     EXPECT_EQ(PrefHashStoreTransaction::CHANGED,
    348               transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    349     std::vector<std::string> expected_invalid_keys2;
    350     expected_invalid_keys2.push_back("a");
    351     expected_invalid_keys2.push_back("o");
    352     expected_invalid_keys2.push_back("c");
    353     EXPECT_EQ(expected_invalid_keys2, invalid_keys);
    354     invalid_keys.clear();
    355 
    356   }
    357 
    358   {
    359     // |pref_hash_store2| should trust its initial hashes dictionary and thus
    360     // trust new unknown values.
    361     PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", true);
    362     scoped_ptr<PrefHashStoreTransaction> transaction(
    363         pref_hash_store2.BeginTransaction(CreateHashStoreContents()));
    364     EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
    365               transaction->CheckSplitValue("new_path", &dict, &invalid_keys));
    366     EXPECT_TRUE(invalid_keys.empty());
    367   }
    368 
    369   // Manually corrupt the super MAC.
    370   CreateHashStoreContents()->SetSuperMac(std::string(64, 'A'));
    371 
    372   {
    373     // |pref_hash_store3| should no longer trust its initial hashes dictionary
    374     // and thus shouldn't trust unknown values.
    375     PrefHashStoreImpl pref_hash_store3(std::string(32, 0), "device_id", true);
    376     scoped_ptr<PrefHashStoreTransaction> transaction(
    377         pref_hash_store3.BeginTransaction(CreateHashStoreContents()));
    378     EXPECT_EQ(PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE,
    379               transaction->CheckSplitValue("new_path", &dict, &invalid_keys));
    380     EXPECT_TRUE(invalid_keys.empty());
    381   }
    382 }
    383 
    384 TEST_F(PrefHashStoreImplTest, EmptyAndNULLSplitDict) {
    385   base::DictionaryValue empty_dict;
    386 
    387   std::vector<std::string> invalid_keys;
    388 
    389   {
    390     PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
    391     scoped_ptr<PrefHashStoreTransaction> transaction(
    392         pref_hash_store.BeginTransaction(CreateHashStoreContents()));
    393 
    394     // Store hashes for a random dict to be overwritten below.
    395     base::DictionaryValue initial_dict;
    396     initial_dict.Set("a", new base::StringValue("foo"));
    397     transaction->StoreSplitHash("path1", &initial_dict);
    398 
    399     // Verify stored empty dictionary matches NULL and empty dictionary back.
    400     transaction->StoreSplitHash("path1", &empty_dict);
    401     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
    402               transaction->CheckSplitValue("path1", NULL, &invalid_keys));
    403     EXPECT_TRUE(invalid_keys.empty());
    404     EXPECT_EQ(
    405         PrefHashStoreTransaction::UNCHANGED,
    406         transaction->CheckSplitValue("path1", &empty_dict, &invalid_keys));
    407     EXPECT_TRUE(invalid_keys.empty());
    408 
    409     // Same when storing NULL directly.
    410     transaction->StoreSplitHash("path1", NULL);
    411     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
    412               transaction->CheckSplitValue("path1", NULL, &invalid_keys));
    413     EXPECT_TRUE(invalid_keys.empty());
    414     EXPECT_EQ(
    415         PrefHashStoreTransaction::UNCHANGED,
    416         transaction->CheckSplitValue("path1", &empty_dict, &invalid_keys));
    417     EXPECT_TRUE(invalid_keys.empty());
    418   }
    419 
    420   {
    421     // |pref_hash_store2| should trust its initial hashes dictionary (and thus
    422     // trust new unknown values) even though the last action done was to clear
    423     // the hashes for path1 by setting its value to NULL (this is a regression
    424     // test ensuring that the internal action of clearing some hashes does
    425     // update the stored hash of hashes).
    426     PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", true);
    427     scoped_ptr<PrefHashStoreTransaction> transaction(
    428         pref_hash_store2.BeginTransaction(CreateHashStoreContents()));
    429 
    430     base::DictionaryValue tested_dict;
    431     tested_dict.Set("a", new base::StringValue("foo"));
    432     tested_dict.Set("b", new base::StringValue("bar"));
    433     EXPECT_EQ(
    434         PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
    435         transaction->CheckSplitValue("new_path", &tested_dict, &invalid_keys));
    436     EXPECT_TRUE(invalid_keys.empty());
    437   }
    438 }
    439 
    440 // Test that the PrefHashStore returns TRUSTED_UNKNOWN_VALUE when checking for
    441 // a split preference even if there is an existing atomic preference's hash
    442 // stored. There is no point providing a migration path for preferences
    443 // switching strategies after their initial release as split preferences are
    444 // turned into split preferences specifically because the atomic hash isn't
    445 // considered useful.
    446 TEST_F(PrefHashStoreImplTest, TrustedUnknownSplitValueFromExistingAtomic) {
    447   base::StringValue string("string1");
    448 
    449   base::DictionaryValue dict;
    450   dict.Set("a", new base::StringValue("foo"));
    451   dict.Set("d", new base::StringValue("bad"));
    452   dict.Set("b", new base::StringValue("bar"));
    453   dict.Set("c", new base::StringValue("baz"));
    454 
    455   {
    456     PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
    457     scoped_ptr<PrefHashStoreTransaction> transaction(
    458         pref_hash_store.BeginTransaction(CreateHashStoreContents()));
    459 
    460     transaction->StoreHash("path1", &string);
    461     EXPECT_EQ(PrefHashStoreTransaction::UNCHANGED,
    462               transaction->CheckValue("path1", &string));
    463   }
    464 
    465   {
    466     // Load a new |pref_hash_store2| in which the hashes dictionary is trusted.
    467     PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", true);
    468     scoped_ptr<PrefHashStoreTransaction> transaction(
    469         pref_hash_store2.BeginTransaction(CreateHashStoreContents()));
    470     std::vector<std::string> invalid_keys;
    471     EXPECT_EQ(PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE,
    472               transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    473     EXPECT_TRUE(invalid_keys.empty());
    474   }
    475 }
    476