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_calculator.h"
6
7#include <string>
8
9#include "base/memory/scoped_ptr.h"
10#include "base/strings/string_util.h"
11#include "base/values.h"
12#include "testing/gtest/include/gtest/gtest.h"
13
14TEST(PrefHashCalculatorTest, TestCurrentAlgorithm) {
15  base::StringValue string_value_1("string value 1");
16  base::StringValue string_value_2("string value 2");
17  base::DictionaryValue dictionary_value_1;
18  dictionary_value_1.SetInteger("int value", 1);
19  dictionary_value_1.Set("nested empty map", new base::DictionaryValue);
20  base::DictionaryValue dictionary_value_1_equivalent;
21  dictionary_value_1_equivalent.SetInteger("int value", 1);
22  base::DictionaryValue dictionary_value_2;
23  dictionary_value_2.SetInteger("int value", 2);
24
25  PrefHashCalculator calc1("seed1", "deviceid");
26  PrefHashCalculator calc1_dup("seed1", "deviceid");
27  PrefHashCalculator calc2("seed2", "deviceid");
28  PrefHashCalculator calc3("seed1", "deviceid2");
29
30  // Two calculators with same seed produce same hash.
31  ASSERT_EQ(calc1.Calculate("pref_path", &string_value_1),
32            calc1_dup.Calculate("pref_path", &string_value_1));
33  ASSERT_EQ(PrefHashCalculator::VALID,
34            calc1_dup.Validate(
35                "pref_path",
36                &string_value_1,
37                calc1.Calculate("pref_path", &string_value_1)));
38
39  // Different seeds, different hashes.
40  ASSERT_NE(calc1.Calculate("pref_path", &string_value_1),
41            calc2.Calculate("pref_path", &string_value_1));
42  ASSERT_EQ(PrefHashCalculator::INVALID,
43            calc2.Validate(
44                "pref_path",
45                &string_value_1,
46                calc1.Calculate("pref_path", &string_value_1)));
47
48  // Different device IDs, different hashes.
49  ASSERT_NE(calc1.Calculate("pref_path", &string_value_1),
50            calc3.Calculate("pref_path", &string_value_1));
51
52  // Different values, different hashes.
53  ASSERT_NE(calc1.Calculate("pref_path", &string_value_1),
54            calc1.Calculate("pref_path", &string_value_2));
55
56  // Different paths, different hashes.
57  ASSERT_NE(calc1.Calculate("pref_path", &string_value_1),
58            calc1.Calculate("pref_path_2", &string_value_1));
59
60  // Works for dictionaries.
61  ASSERT_EQ(calc1.Calculate("pref_path", &dictionary_value_1),
62            calc1.Calculate("pref_path", &dictionary_value_1));
63  ASSERT_NE(calc1.Calculate("pref_path", &dictionary_value_1),
64            calc1.Calculate("pref_path", &dictionary_value_2));
65
66  // Empty dictionary children are pruned.
67  ASSERT_EQ(calc1.Calculate("pref_path", &dictionary_value_1),
68            calc1.Calculate("pref_path", &dictionary_value_1_equivalent));
69
70  // NULL value is supported.
71  ASSERT_FALSE(calc1.Calculate("pref_path", NULL).empty());
72}
73
74// Tests the output against a known value to catch unexpected algorithm changes.
75// The test hashes below must NEVER be updated, the serialization algorithm used
76// must always be able to generate data that will produce these exact hashes.
77TEST(PrefHashCalculatorTest, CatchHashChanges) {
78  static const char kSeed[] = "0123456789ABCDEF0123456789ABCDEF";
79  static const char kDeviceId[] = "test_device_id1";
80
81  scoped_ptr<base::Value> null_value(base::Value::CreateNullValue());
82  scoped_ptr<base::Value> bool_value(new base::FundamentalValue(false));
83  scoped_ptr<base::Value> int_value(new base::FundamentalValue(1234567890));
84  scoped_ptr<base::Value> double_value(
85      new base::FundamentalValue(123.0987654321));
86  scoped_ptr<base::Value> string_value(
87      new base::StringValue("testing with special chars:\n<>{}:^^@#$\\/"));
88
89  // For legacy reasons, we have to support pruning of empty lists/dictionaries
90  // and nested empty ists/dicts in the hash generation algorithm.
91  scoped_ptr<base::DictionaryValue> nested_empty_dict(
92      new base::DictionaryValue);
93  nested_empty_dict->Set("a", new base::DictionaryValue);
94  nested_empty_dict->Set("b", new base::ListValue);
95  scoped_ptr<base::ListValue> nested_empty_list(
96      new base::ListValue);
97  nested_empty_list->Append(new base::DictionaryValue);
98  nested_empty_list->Append(new base::ListValue);
99  nested_empty_list->Append(nested_empty_dict->DeepCopy());
100
101  // A dictionary with an empty dictionary, an empty list, and nested empty
102  // dictionaries/lists in it.
103  scoped_ptr<base::DictionaryValue> dict_value(new base::DictionaryValue);
104  dict_value->Set("a", new base::StringValue("foo"));
105  dict_value->Set("d", new base::ListValue);
106  dict_value->Set("b", new base::DictionaryValue);
107  dict_value->Set("c", new base::StringValue("baz"));
108  dict_value->Set("e", nested_empty_dict.release());
109  dict_value->Set("f", nested_empty_list.release());
110
111  scoped_ptr<base::ListValue> list_value(new base::ListValue);
112  list_value->AppendBoolean(true);
113  list_value->AppendInteger(100);
114  list_value->AppendDouble(1.0);
115
116  ASSERT_EQ(base::Value::TYPE_NULL, null_value->GetType());
117  ASSERT_EQ(base::Value::TYPE_BOOLEAN, bool_value->GetType());
118  ASSERT_EQ(base::Value::TYPE_INTEGER, int_value->GetType());
119  ASSERT_EQ(base::Value::TYPE_DOUBLE, double_value->GetType());
120  ASSERT_EQ(base::Value::TYPE_STRING, string_value->GetType());
121  ASSERT_EQ(base::Value::TYPE_DICTIONARY, dict_value->GetType());
122  ASSERT_EQ(base::Value::TYPE_LIST, list_value->GetType());
123
124  // Test every value type independently. Intentionally omits TYPE_BINARY which
125  // isn't even allowed in JSONWriter's input.
126  static const char kExpectedNullValue[] =
127      "82A9F3BBC7F9FF84C76B033C854E79EEB162783FA7B3E99FF9372FA8E12C44F7";
128  EXPECT_EQ(PrefHashCalculator::VALID,
129            PrefHashCalculator(kSeed, kDeviceId).Validate(
130                "pref.path", null_value.get(), kExpectedNullValue));
131
132  static const char kExpectedBooleanValue[] =
133      "A520D8F43EA307B0063736DC9358C330539D0A29417580514C8B9862632C4CCC";
134  EXPECT_EQ(PrefHashCalculator::VALID,
135            PrefHashCalculator(kSeed, kDeviceId).Validate(
136                "pref.path", bool_value.get(), kExpectedBooleanValue));
137
138  static const char kExpectedIntegerValue[] =
139      "8D60DA1F10BF5AA29819D2D66D7CCEF9AABC5DA93C11A0D2BD21078D63D83682";
140  EXPECT_EQ(PrefHashCalculator::VALID,
141            PrefHashCalculator(kSeed, kDeviceId).Validate(
142                "pref.path", int_value.get(), kExpectedIntegerValue));
143
144  static const char kExpectedDoubleValue[] =
145      "C9D94772516125BEEDAE68C109D44BC529E719EE020614E894CC7FB4098C545D";
146  EXPECT_EQ(PrefHashCalculator::VALID,
147            PrefHashCalculator(kSeed, kDeviceId).Validate(
148                "pref.path", double_value.get(), kExpectedDoubleValue));
149
150  static const char kExpectedStringValue[] =
151      "05ACCBD3B05C45C36CD06190F63EC577112311929D8380E26E5F13182EB68318";
152  EXPECT_EQ(PrefHashCalculator::VALID,
153            PrefHashCalculator(kSeed, kDeviceId).Validate(
154                "pref.path", string_value.get(), kExpectedStringValue));
155
156  static const char kExpectedDictValue[] =
157      "7A84DCC710D796C771F789A4DA82C952095AA956B6F1667EE42D0A19ECAA3C4A";
158  EXPECT_EQ(PrefHashCalculator::VALID,
159            PrefHashCalculator(kSeed, kDeviceId).Validate(
160                "pref.path", dict_value.get(), kExpectedDictValue));
161
162  static const char kExpectedListValue[] =
163      "8D5A25972DF5AE20D041C780E7CA54E40F614AD53513A0724EE8D62D4F992740";
164  EXPECT_EQ(PrefHashCalculator::VALID,
165            PrefHashCalculator(kSeed, kDeviceId).Validate(
166                "pref.path", list_value.get(), kExpectedListValue));
167
168  // Also test every value type together in the same dictionary.
169  base::DictionaryValue everything;
170  everything.Set("null", null_value.release());
171  everything.Set("bool", bool_value.release());
172  everything.Set("int", int_value.release());
173  everything.Set("double", double_value.release());
174  everything.Set("string", string_value.release());
175  everything.Set("list", list_value.release());
176  everything.Set("dict", dict_value.release());
177  static const char kExpectedEverythingValue[] =
178      "B97D09BE7005693574DCBDD03D8D9E44FB51F4008B73FB56A49A9FA671A1999B";
179  EXPECT_EQ(PrefHashCalculator::VALID,
180            PrefHashCalculator(kSeed, kDeviceId).Validate(
181                "pref.path", &everything, kExpectedEverythingValue));
182}
183
184TEST(PrefHashCalculatorTest, TestCompatibilityWithLegacyPrefMetricsServiceId) {
185  static const char kSeed[] = {
186    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
187    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
188    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
189    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
190    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
191    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
192    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
193    0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
194  };
195  static const char kDeviceId[] =
196      "D730D9CBD98C734A4FB097A1922275FE9F7E026A4EA1BE0E84";
197  static const char kExpectedValue[] =
198      "845EF34663FF8D32BE6707F40258FBA531C2BFC532E3B014AFB3476115C2A9DE";
199
200  base::ListValue startup_urls;
201  startup_urls.Set(0, new base::StringValue("http://www.chromium.org/"));
202
203  EXPECT_EQ(PrefHashCalculator::VALID_SECURE_LEGACY,
204            PrefHashCalculator(std::string(kSeed, arraysize(kSeed)), kDeviceId).
205            Validate("session.startup_urls", &startup_urls, kExpectedValue));
206}
207