summaryrefslogtreecommitdiffstats
path: root/rlz/lib
diff options
context:
space:
mode:
authorjam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-21 14:10:52 +0000
committerjam@chromium.org <jam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-21 14:10:52 +0000
commit0a5e34a5386fa254a3b133f0a5f7667f1f790f67 (patch)
treed1dd0e80264f900004c56ad5ce6b56efea0bec49 /rlz/lib
parentb049c382ff842f546e2bcc261ce23438bc8764bf (diff)
downloadchromium_src-0a5e34a5386fa254a3b133f0a5f7667f1f790f67.zip
chromium_src-0a5e34a5386fa254a3b133f0a5f7667f1f790f67.tar.gz
chromium_src-0a5e34a5386fa254a3b133f0a5f7667f1f790f67.tar.bz2
Check-in rlz code to src\rlz from http://code.google.com/p/rlz/.
Review URL: https://chromiumcodereview.appspot.com/10597002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@143381 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'rlz/lib')
-rw-r--r--rlz/lib/assert.cc15
-rw-r--r--rlz/lib/assert.h53
-rw-r--r--rlz/lib/crc32.h17
-rw-r--r--rlz/lib/crc32_unittest.cc52
-rw-r--r--rlz/lib/crc32_wrapper.cc36
-rw-r--r--rlz/lib/crc8.cc90
-rw-r--r--rlz/lib/crc8.h24
-rw-r--r--rlz/lib/crc8_unittest.cc51
-rw-r--r--rlz/lib/financial_ping.cc354
-rw-r--r--rlz/lib/financial_ping.h64
-rw-r--r--rlz/lib/financial_ping_test.cc293
-rw-r--r--rlz/lib/lib_values.cc208
-rw-r--r--rlz/lib/lib_values.h99
-rw-r--r--rlz/lib/lib_values_unittest.cc61
-rw-r--r--rlz/lib/machine_id.cc80
-rw-r--r--rlz/lib/machine_id.h34
-rw-r--r--rlz/lib/machine_id_unittest.cc20
-rw-r--r--rlz/lib/rlz_enums.h132
-rw-r--r--rlz/lib/rlz_lib.cc651
-rw-r--r--rlz/lib/rlz_lib.h335
-rw-r--r--rlz/lib/rlz_lib_clear.cc82
-rw-r--r--rlz/lib/rlz_lib_test.cc824
-rw-r--r--rlz/lib/rlz_value_store.h116
-rw-r--r--rlz/lib/string_utils.cc92
-rw-r--r--rlz/lib/string_utils.h26
-rw-r--r--rlz/lib/string_utils_unittest.cc69
26 files changed, 3878 insertions, 0 deletions
diff --git a/rlz/lib/assert.cc b/rlz/lib/assert.cc
new file mode 100644
index 0000000..d8ad935
--- /dev/null
+++ b/rlz/lib/assert.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Macros specific to the RLZ library.
+
+#include "rlz/lib/assert.h"
+
+namespace rlz_lib {
+
+#ifdef MUTE_EXPECTED_ASSERTS
+std::string expected_assertion_;
+#endif
+
+} // namespace rlz_lib
diff --git a/rlz/lib/assert.h b/rlz/lib/assert.h
new file mode 100644
index 0000000..89e94e0
--- /dev/null
+++ b/rlz/lib/assert.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Macros specific to the RLZ library.
+
+#ifndef RLZ_LIB_ASSERT_H_
+#define RLZ_LIB_ASSERT_H_
+
+#include <string>
+#include "base/logging.h"
+
+// An assertion macro.
+// Can mute expected assertions in debug mode.
+
+#ifndef ASSERT_STRING
+ #ifndef MUTE_EXPECTED_ASSERTS
+ #define ASSERT_STRING(expr) LOG_IF(FATAL, false) << (expr)
+ #else
+ #define ASSERT_STRING(expr) \
+ do { \
+ std::string expr_string(expr); \
+ if (rlz_lib::expected_assertion_ != expr_string) { \
+ LOG_IF(FATAL, false) << (expr); \
+ } \
+ } while (0)
+ #endif
+#endif
+
+
+#ifndef VERIFY
+ #ifdef _DEBUG
+ #define VERIFY(expr) LOG_IF(FATAL, !(expr)) << #expr
+ #else
+ #define VERIFY(expr) (void)(expr)
+ #endif
+#endif
+
+namespace rlz_lib {
+
+#ifdef MUTE_EXPECTED_ASSERTS
+extern std::string expected_assertion_;
+#endif
+
+inline void SetExpectedAssertion(const char* s) {
+#ifdef MUTE_EXPECTED_ASSERTS
+ expected_assertion_ = s;
+#endif
+}
+
+} // rlz_lib
+
+#endif // RLZ_LIB_ASSERT_H_
diff --git a/rlz/lib/crc32.h b/rlz/lib/crc32.h
new file mode 100644
index 0000000..aded355
--- /dev/null
+++ b/rlz/lib/crc32.h
@@ -0,0 +1,17 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A wrapper around ZLib's CRC function.
+
+#ifndef RLZ_LIB_CRC32_H_
+#define RLZ_LIB_CRC32_H_
+
+namespace rlz_lib {
+
+int Crc32(const unsigned char* buf, int length);
+bool Crc32(const char* text, int* crc);
+
+} // namespace rlz_lib
+
+#endif // RLZ_LIB_CRC32_H_
diff --git a/rlz/lib/crc32_unittest.cc b/rlz/lib/crc32_unittest.cc
new file mode 100644
index 0000000..9700058
--- /dev/null
+++ b/rlz/lib/crc32_unittest.cc
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A test for ZLib's checksum function.
+
+#include "rlz/lib/crc32.h"
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(Crc32Unittest, ByteTest) {
+ struct {
+ const char* data;
+ int len;
+ // Externally calculated at http://crc32-checksum.waraxe.us/
+ int crc;
+ } kData[] = {
+ {"Hello" , 5, 0xF7D18982},
+ {"Google" , 6, 0x62B0F067},
+ {"" , 0, 0x0},
+ {"One more string.", 16, 0x0CA14970},
+ {NULL , 0, 0x0},
+ };
+
+ for (int i = 0; kData[i].data; i++)
+ EXPECT_EQ(kData[i].crc,
+ rlz_lib::Crc32(reinterpret_cast<const unsigned char*>(kData[i].data),
+ kData[i].len));
+}
+
+TEST(Crc32Unittest, CharTest) {
+ struct {
+ const char* data;
+ // Externally calculated at http://crc32-checksum.waraxe.us/
+ int crc;
+ } kData[] = {
+ {"Hello" , 0xF7D18982},
+ {"Google" , 0x62B0F067},
+ {"" , 0x0},
+ {"One more string.", 0x0CA14970},
+ {"Google\r\n" , 0x83A3E860},
+ {NULL , 0x0},
+ };
+
+ int crc;
+ for (int i = 0; kData[i].data; i++) {
+ EXPECT_TRUE(rlz_lib::Crc32(kData[i].data, &crc));
+ EXPECT_EQ(kData[i].crc, crc);
+ }
+}
diff --git a/rlz/lib/crc32_wrapper.cc b/rlz/lib/crc32_wrapper.cc
new file mode 100644
index 0000000..d763c26
--- /dev/null
+++ b/rlz/lib/crc32_wrapper.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A wrapper around ZLib's CRC functions to put them in the rlz_lib namespace
+// and use our types.
+
+#include "rlz/lib/assert.h"
+#include "rlz/lib/crc32.h"
+#include "rlz/lib/string_utils.h"
+#include "third_party/zlib/zlib.h"
+
+namespace rlz_lib {
+
+int Crc32(const unsigned char* buf, int length) {
+ return crc32(0L, buf, length);
+}
+
+bool Crc32(const char* text, int* crc) {
+ if (!crc) {
+ ASSERT_STRING("Crc32: crc is NULL.");
+ return false;
+ }
+
+ *crc = 0;
+ for (int i = 0; text[i]; i++) {
+ if (!IsAscii(text[i]))
+ return false;
+
+ *crc = crc32(*crc, reinterpret_cast<const unsigned char*>(text + i), 1);
+ }
+
+ return true;
+}
+
+} // namespace rlz_lib
diff --git a/rlz/lib/crc8.cc b/rlz/lib/crc8.cc
new file mode 100644
index 0000000..fe02eab
--- /dev/null
+++ b/rlz/lib/crc8.cc
@@ -0,0 +1,90 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "rlz/lib/crc8.h"
+
+namespace {
+
+// The CRC lookup table used for ATM HES (Polynomial = 0x07).
+// These are 256 unique 8-bit values.
+const unsigned char kCrcTable[256] = {
+ 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
+ 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
+ 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
+ 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
+ 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
+ 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
+ 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
+ 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
+ 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
+ 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
+ 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
+ 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
+ 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
+ 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
+ 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
+ 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
+ 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
+ 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
+ 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
+ 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
+ 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
+ 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
+ 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
+ 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
+ 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
+ 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
+ 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
+ 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
+ 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
+ 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
+ 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
+ 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
+};
+
+} // namespace anonymous
+
+
+namespace rlz_lib {
+
+bool Crc8::Generate(const unsigned char *data, int length,
+ unsigned char* check_sum) {
+ if (!check_sum)
+ return false;
+
+ *check_sum = 0;
+ if (!data)
+ return false;
+
+ // The inital and final constants are as used in the ATM HEC.
+ static const unsigned char kInitial = 0x00;
+ static const unsigned char kFinal = 0x55;
+ unsigned char crc = kInitial;
+ for (int i = 0; i < length; ++i) {
+ crc = kCrcTable[(data[i] ^ crc) & 0xFFU];
+ }
+
+ *check_sum = crc ^ kFinal;
+ return true;
+}
+
+bool Crc8::Verify(const unsigned char* data, int length,
+ unsigned char check_sum, bool* matches) {
+ if (!matches)
+ return false;
+
+ *matches = false;
+ if (!data)
+ return false;
+
+ unsigned char calculated_crc;
+ if (!Generate(data, length, &calculated_crc))
+ return false;
+
+ *matches = check_sum == calculated_crc;
+
+ return true;
+}
+
+} // namespace rlz_lib
diff --git a/rlz/lib/crc8.h b/rlz/lib/crc8.h
new file mode 100644
index 0000000..6c3c848
--- /dev/null
+++ b/rlz/lib/crc8.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Crc8 utility functions.
+
+#ifndef RLZ_LIB_CRC8_H_
+#define RLZ_LIB_CRC8_H_
+
+namespace rlz_lib {
+// CRC-8 methods:
+class Crc8 {
+ public:
+ static bool Generate(const unsigned char* data,
+ int length,
+ unsigned char* check_sum);
+ static bool Verify(const unsigned char* data,
+ int length,
+ unsigned char checksum,
+ bool * matches);
+};
+}; // namespace rlz_lib
+
+#endif // RLZ_LIB_CRC8_H_
diff --git a/rlz/lib/crc8_unittest.cc b/rlz/lib/crc8_unittest.cc
new file mode 100644
index 0000000..4889242
--- /dev/null
+++ b/rlz/lib/crc8_unittest.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Uniitest for data encryption functions.
+
+#include "base/logging.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "rlz/lib/crc8.h"
+
+TEST(Crc8Unittest, TestCrc8) {
+ struct Data {
+ char string[10];
+ // Externally calculated checksums use
+ // http://www.zorc.breitbandkatze.de/crc.html
+ // with the ATM HEC paramters:
+ // CRC-8, Polynomial 0x07, Initial value 0x00, Final XOR value 0x55
+ // (direct, don't reverse data byes, don't reverse CRC before final XOR)
+ unsigned char external_crc;
+ int random_byte;
+ unsigned char corrupt_value;
+ } data[] = {
+ {"Google", 0x01, 2, 0x53},
+ {"GOOGLE", 0xA6, 4, 0x11},
+ {"My CRC 8!", 0xDC, 0, 0x50},
+ };
+
+ unsigned char* bytes;
+ unsigned char crc;
+ bool matches;
+ int length;
+ for (size_t i = 0; i < sizeof(data) / sizeof(data[0]); ++i) {
+ bytes = reinterpret_cast<unsigned char*>(data[i].string);
+ crc = 0;
+ matches = false;
+ length = strlen(data[i].string);
+
+ // Calculate CRC and compare against external value.
+ rlz_lib::Crc8::Generate(bytes, length, &crc);
+ EXPECT_TRUE(crc == data[i].external_crc);
+ rlz_lib::Crc8::Verify(bytes, length, crc, &matches);
+ EXPECT_TRUE(matches);
+
+ // Corrupt string and see if CRC still matches.
+ data[i].string[data[i].random_byte] = data[i].corrupt_value;
+ rlz_lib::Crc8::Verify(bytes, length, crc, &matches);
+ EXPECT_FALSE(matches);
+ }
+}
diff --git a/rlz/lib/financial_ping.cc b/rlz/lib/financial_ping.cc
new file mode 100644
index 0000000..aad3db3
--- /dev/null
+++ b/rlz/lib/financial_ping.cc
@@ -0,0 +1,354 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Library functions related to the Financial Server ping.
+
+#include "rlz/lib/financial_ping.h"
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "rlz/lib/assert.h"
+#include "rlz/lib/lib_values.h"
+#include "rlz/lib/machine_id.h"
+#include "rlz/lib/rlz_lib.h"
+#include "rlz/lib/rlz_value_store.h"
+#include "rlz/lib/string_utils.h"
+
+#if !defined(OS_WIN)
+#include "base/time.h"
+#endif
+
+#if defined(RLZ_NETWORK_IMPLEMENTATION_WIN_INET)
+
+#include <windows.h>
+#include <wininet.h>
+
+namespace {
+
+class InternetHandle {
+ public:
+ InternetHandle(HINTERNET handle) { handle_ = handle; }
+ ~InternetHandle() { if (handle_) InternetCloseHandle(handle_); }
+ operator HINTERNET() const { return handle_; }
+ bool operator!() const { return (handle_ == NULL); }
+
+ private:
+ HINTERNET handle_;
+};
+
+} // namespace
+
+#else
+
+#include "base/bind.h"
+#include "base/message_loop.h"
+#include "base/time.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+
+#endif
+
+namespace {
+
+// Returns the time relative to a fixed point in the past in multiples of
+// 100 ns stepts. The point in the past is arbitrary but can't change, as the
+// result of this value is stored on disk.
+int64 GetSystemTimeAsInt64() {
+#if defined(OS_WIN)
+ FILETIME now_as_file_time;
+ // Relative to Jan 1, 1601 (UTC).
+ GetSystemTimeAsFileTime(&now_as_file_time);
+
+ LARGE_INTEGER integer;
+ integer.HighPart = now_as_file_time.dwHighDateTime;
+ integer.LowPart = now_as_file_time.dwLowDateTime;
+ return integer.QuadPart;
+#else
+ // Seconds since epoch (Jan 1, 1970).
+ double now_seconds = base::Time::Now().ToDoubleT();
+ return static_cast<int64>(now_seconds * 1000 * 1000 * 10);
+#endif
+}
+
+} // namespace
+
+
+namespace rlz_lib {
+
+bool FinancialPing::FormRequest(Product product,
+ const AccessPoint* access_points, const char* product_signature,
+ const char* product_brand, const char* product_id,
+ const char* product_lang, bool exclude_machine_id,
+ std::string* request) {
+ if (!request) {
+ ASSERT_STRING("FinancialPing::FormRequest: request is NULL");
+ return false;
+ }
+
+ request->clear();
+
+ ScopedRlzValueStoreLock lock;
+ RlzValueStore* store = lock.GetStore();
+ if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
+ return false;
+
+ if (!access_points) {
+ ASSERT_STRING("FinancialPing::FormRequest: access_points is NULL");
+ return false;
+ }
+
+ if (!product_signature) {
+ ASSERT_STRING("FinancialPing::FormRequest: product_signature is NULL");
+ return false;
+ }
+
+ if (!SupplementaryBranding::GetBrand().empty()) {
+ if (SupplementaryBranding::GetBrand() != product_brand) {
+ ASSERT_STRING("FinancialPing::FormRequest: supplementary branding bad");
+ return false;
+ }
+ }
+
+ base::StringAppendF(request, "%s?", kFinancialPingPath);
+
+ // Add the signature, brand, product id and language.
+ base::StringAppendF(request, "%s=%s", kProductSignatureCgiVariable,
+ product_signature);
+ if (product_brand)
+ base::StringAppendF(request, "&%s=%s", kProductBrandCgiVariable,
+ product_brand);
+
+ if (product_id)
+ base::StringAppendF(request, "&%s=%s", kProductIdCgiVariable, product_id);
+
+ if (product_lang)
+ base::StringAppendF(request, "&%s=%s", kProductLanguageCgiVariable,
+ product_lang);
+
+ // Add the product events.
+ char cgi[kMaxCgiLength + 1];
+ cgi[0] = 0;
+ bool has_events = GetProductEventsAsCgi(product, cgi, arraysize(cgi));
+ if (has_events)
+ base::StringAppendF(request, "&%s", cgi);
+
+ // If we don't have any events, we should ping all the AP's on the system
+ // that we know about and have a current RLZ value, even if they are not
+ // used by this product.
+ AccessPoint all_points[LAST_ACCESS_POINT];
+ if (!has_events) {
+ char rlz[kMaxRlzLength + 1];
+ int idx = 0;
+ for (int ap = NO_ACCESS_POINT + 1; ap < LAST_ACCESS_POINT; ap++) {
+ rlz[0] = 0;
+ AccessPoint point = static_cast<AccessPoint>(ap);
+ if (GetAccessPointRlz(point, rlz, arraysize(rlz)) &&
+ rlz[0] != '\0')
+ all_points[idx++] = point;
+ }
+ all_points[idx] = NO_ACCESS_POINT;
+ }
+
+ // Add the RLZ's and the DCC if needed. This is the same as get PingParams.
+ // This will also include the RLZ Exchange Protocol CGI Argument.
+ cgi[0] = 0;
+ if (GetPingParams(product, has_events ? access_points : all_points,
+ cgi, arraysize(cgi)))
+ base::StringAppendF(request, "&%s", cgi);
+
+ if (has_events && !exclude_machine_id) {
+ std::string machine_id;
+ if (GetMachineId(&machine_id)) {
+ base::StringAppendF(request, "&%s=%s", kMachineIdCgiVariable,
+ machine_id.c_str());
+ }
+ }
+
+ return true;
+}
+
+#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
+// The URLRequestContextGetter used by FinancialPing::PingServer().
+net::URLRequestContextGetter* g_context;
+
+bool FinancialPing::SetURLRequestContext(
+ net::URLRequestContextGetter* context) {
+ ScopedRlzValueStoreLock lock;
+ RlzValueStore* store = lock.GetStore();
+ if (!store)
+ return false;
+
+ g_context = context;
+ return true;
+}
+
+namespace {
+
+class FinancialPingUrlFetcherDelegate : public net::URLFetcherDelegate {
+ public:
+ FinancialPingUrlFetcherDelegate(MessageLoop* loop) : loop_(loop) { }
+ virtual void OnURLFetchComplete(const net::URLFetcher* source);
+ private:
+ MessageLoop* loop_;
+};
+
+void FinancialPingUrlFetcherDelegate::OnURLFetchComplete(
+ const net::URLFetcher* source) {
+ loop_->Quit();
+}
+
+} // namespace
+
+#endif
+
+bool FinancialPing::PingServer(const char* request, std::string* response) {
+ if (!response)
+ return false;
+
+ response->clear();
+
+#if defined(RLZ_NETWORK_IMPLEMENTATION_WIN_INET)
+ // Initialize WinInet.
+ InternetHandle inet_handle = InternetOpenA(kFinancialPingUserAgent,
+ INTERNET_OPEN_TYPE_PRECONFIG,
+ NULL, NULL, 0);
+ if (!inet_handle)
+ return false;
+
+ // Open network connection.
+ InternetHandle connection_handle = InternetConnectA(inet_handle,
+ kFinancialServer, kFinancialPort, "", "", INTERNET_SERVICE_HTTP,
+ INTERNET_FLAG_NO_CACHE_WRITE, 0);
+ if (!connection_handle)
+ return false;
+
+ // Prepare the HTTP request.
+ InternetHandle http_handle = HttpOpenRequestA(connection_handle,
+ "GET", request, NULL, NULL, kFinancialPingResponseObjects,
+ INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES, NULL);
+ if (!http_handle)
+ return false;
+
+ // Timeouts are probably:
+ // INTERNET_OPTION_SEND_TIMEOUT, INTERNET_OPTION_RECEIVE_TIMEOUT
+
+ // Send the HTTP request. Note: Fails if user is working in off-line mode.
+ if (!HttpSendRequest(http_handle, NULL, 0, NULL, 0))
+ return false;
+
+ // Check the response status.
+ DWORD status;
+ DWORD status_size = sizeof(status);
+ if (!HttpQueryInfo(http_handle, HTTP_QUERY_STATUS_CODE |
+ HTTP_QUERY_FLAG_NUMBER, &status, &status_size, NULL) ||
+ 200 != status)
+ return false;
+
+ // Get the response text.
+ scoped_array<char> buffer(new char[kMaxPingResponseLength]);
+ if (buffer.get() == NULL)
+ return false;
+
+ DWORD bytes_read = 0;
+ while (InternetReadFile(http_handle, buffer.get(), kMaxPingResponseLength,
+ &bytes_read) && bytes_read > 0) {
+ response->append(buffer.get(), bytes_read);
+ bytes_read = 0;
+ };
+
+ return true;
+#else
+ // Run a blocking event loop to match the win inet implementation.
+ MessageLoop loop;
+ FinancialPingUrlFetcherDelegate delegate(&loop);
+
+ std::string url = base::StringPrintf("http://%s:%d%s",
+ kFinancialServer, kFinancialPort,
+ request);
+
+ scoped_ptr<net::URLFetcher> fetcher(net::URLFetcher::Create(
+ GURL(url), net::URLFetcher::GET, &delegate));
+
+ fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE |
+ net::LOAD_DO_NOT_SEND_AUTH_DATA |
+ net::LOAD_DO_NOT_PROMPT_FOR_LOGIN |
+ net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+
+ // Ensure rlz_lib::SetURLRequestContext() has been called before sending
+ // pings.
+ CHECK(g_context);
+ fetcher->SetRequestContext(g_context);
+
+ const base::TimeDelta kTimeout = base::TimeDelta::FromMinutes(5);
+ loop.PostTask(
+ FROM_HERE,
+ base::Bind(&net::URLFetcher::Start, base::Unretained(fetcher.get())));
+ loop.PostNonNestableDelayedTask(
+ FROM_HERE, MessageLoop::QuitClosure(), kTimeout);
+
+ loop.Run();
+
+ if (fetcher->GetResponseCode() != 200)
+ return false;
+
+ return fetcher->GetResponseAsString(response);
+#endif
+}
+
+bool FinancialPing::IsPingTime(Product product, bool no_delay) {
+ ScopedRlzValueStoreLock lock;
+ RlzValueStore* store = lock.GetStore();
+ if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
+ return false;
+
+ int64 last_ping = 0;
+ if (!store->ReadPingTime(product, &last_ping))
+ return true;
+
+ uint64 now = GetSystemTimeAsInt64();
+ int64 interval = now - last_ping;
+
+ // If interval is negative, clock was probably reset. So ping.
+ if (interval < 0)
+ return true;
+
+ // Check if this product has any unreported events.
+ char cgi[kMaxCgiLength + 1];
+ cgi[0] = 0;
+ bool has_events = GetProductEventsAsCgi(product, cgi, arraysize(cgi));
+ if (no_delay && has_events)
+ return true;
+
+ return interval >= (has_events ? kEventsPingInterval : kNoEventsPingInterval);
+}
+
+
+bool FinancialPing::UpdateLastPingTime(Product product) {
+ ScopedRlzValueStoreLock lock;
+ RlzValueStore* store = lock.GetStore();
+ if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
+ return false;
+
+ uint64 now = GetSystemTimeAsInt64();
+ return store->WritePingTime(product, now);
+}
+
+
+bool FinancialPing::ClearLastPingTime(Product product) {
+ ScopedRlzValueStoreLock lock;
+ RlzValueStore* store = lock.GetStore();
+ if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
+ return false;
+ return store->ClearPingTime(product);
+}
+
+} // namespace
diff --git a/rlz/lib/financial_ping.h b/rlz/lib/financial_ping.h
new file mode 100644
index 0000000..081ecba
--- /dev/null
+++ b/rlz/lib/financial_ping.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Library functions related to the Financial Server ping.
+
+#ifndef RLZ_LIB_FINANCIAL_PING_H_
+#define RLZ_LIB_FINANCIAL_PING_H_
+
+#include <string>
+#include "rlz/lib/rlz_enums.h"
+
+#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
+namespace net {
+class URLRequestContextGetter;
+} // namespace net
+#endif
+
+namespace rlz_lib {
+
+class FinancialPing {
+ public:
+ // Form the HTTP request to send to the PSO server.
+ // Will look something like:
+ // /pso/ping?as=swg&brand=GGLD&id=124&hl=en&
+ // events=I7S&rep=1&rlz=I7:val,W1:&dcc=dval
+ static bool FormRequest(Product product, const AccessPoint* access_points,
+ const char* product_signature,
+ const char* product_brand, const char* product_id,
+ const char* product_lang, bool exclude_machine_id,
+ std::string* request);
+
+ // Returns whether the time is right to send a ping.
+ // If no_delay is true, this should always ping if there are events,
+ // or one week has passed since last_ping when there are no new events.
+ // If no_delay is false, this should ping if current time < last_ping time
+ // (case of time reset) or if one day has passed since last_ping and there
+ // are events, or one week has passed since last_ping when there are
+ // no new events.
+ static bool IsPingTime(Product product, bool no_delay);
+
+ // Set the last ping time to be now. Writes to RlzValueStore.
+ static bool UpdateLastPingTime(Product product);
+
+ // Clear the last ping time - should be called on uninstall.
+ // Writes to RlzValueStore.
+ static bool ClearLastPingTime(Product product);
+
+ // Ping the financial server with request. Writes to RlzValueStore.
+ static bool PingServer(const char* request, std::string* response);
+
+#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
+ static bool SetURLRequestContext(net::URLRequestContextGetter* context);
+#endif
+
+ private:
+ FinancialPing() {}
+ ~FinancialPing() {}
+};
+
+} // namespace rlz_lib
+
+
+#endif // RLZ_LIB_FINANCIAL_PING_H_
diff --git a/rlz/lib/financial_ping_test.cc b/rlz/lib/financial_ping_test.cc
new file mode 100644
index 0000000..c04cf46
--- /dev/null
+++ b/rlz/lib/financial_ping_test.cc
@@ -0,0 +1,293 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A test application for the FinancialPing class.
+//
+// These tests should not be executed on the build server:
+// - They modify machine state (registry).
+//
+// These tests require write access to HKCU and HKLM.
+//
+// The "GGLA" brand is used to test the normal code flow of the code, and the
+// "TEST" brand is used to test the supplementary brand code code flow. In one
+// case below, the brand "GOOG" is used because the code wants to use a brand
+// that is neither of the two mentioned above.
+
+#include "rlz/lib/financial_ping.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "rlz/lib/lib_values.h"
+#include "rlz/lib/machine_id.h"
+#include "rlz/lib/rlz_lib.h"
+#include "rlz/lib/rlz_value_store.h"
+#include "rlz/test/rlz_test_helpers.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include "rlz/win/lib/machine_deal.h"
+#else
+#include "base/time.h"
+#endif
+
+namespace {
+
+// Must match the implementation in file_time.cc.
+int64 GetSystemTimeAsInt64() {
+#if defined(OS_WIN)
+ FILETIME now_as_file_time;
+ GetSystemTimeAsFileTime(&now_as_file_time);
+ LARGE_INTEGER integer;
+ integer.HighPart = now_as_file_time.dwHighDateTime;
+ integer.LowPart = now_as_file_time.dwLowDateTime;
+ return integer.QuadPart;
+#else
+ double now_seconds = base::Time::Now().ToDoubleT();
+ return static_cast<int64>(now_seconds * 1000 * 1000 * 10);
+#endif
+}
+
+// Ping times in 100-nanosecond intervals.
+const int64 k1MinuteInterval = 60LL * 10000000LL; // 1 minute
+
+} // namespace anonymous
+
+class FinancialPingTest : public RlzLibTestBase {
+};
+
+TEST_F(FinancialPingTest, FormRequest) {
+ std::string brand_string = rlz_lib::SupplementaryBranding::GetBrand();
+ const char* brand = brand_string.empty() ? "GGLA" : brand_string.c_str();
+
+#if defined(OS_WIN)
+ EXPECT_TRUE(rlz_lib::MachineDealCode::Set("dcc_value"));
+#define DCC_PARAM "&dcc=dcc_value"
+#else
+#define DCC_PARAM ""
+#endif
+
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX,
+ "TbRlzValue"));
+
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL));
+
+ rlz_lib::AccessPoint points[] =
+ {rlz_lib::IETB_SEARCH_BOX, rlz_lib::NO_ACCESS_POINT,
+ rlz_lib::NO_ACCESS_POINT};
+
+ std::string machine_id;
+ bool got_machine_id = rlz_lib::GetMachineId(&machine_id);
+
+ std::string request;
+ EXPECT_TRUE(rlz_lib::FinancialPing::FormRequest(rlz_lib::TOOLBAR_NOTIFIER,
+ points, "swg", brand, NULL, "en", false, &request));
+ std::string expected_response;
+ base::StringAppendF(&expected_response,
+ "/tools/pso/ping?as=swg&brand=%s&hl=en&"
+ "events=I7S,W1I&rep=2&rlz=T4:TbRlzValue" DCC_PARAM
+, brand);
+
+ if (got_machine_id)
+ base::StringAppendF(&expected_response, "&id=%s", machine_id.c_str());
+ EXPECT_EQ(expected_response, request);
+
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, ""));
+ EXPECT_TRUE(rlz_lib::FinancialPing::FormRequest(rlz_lib::TOOLBAR_NOTIFIER,
+ points, "swg", brand, "IdOk2", NULL, false, &request));
+ expected_response.clear();
+ base::StringAppendF(&expected_response,
+ "/tools/pso/ping?as=swg&brand=%s&pid=IdOk2&"
+ "events=I7S,W1I&rep=2&rlz=T4:" DCC_PARAM, brand);
+
+ if (got_machine_id)
+ base::StringAppendF(&expected_response, "&id=%s", machine_id.c_str());
+ EXPECT_EQ(expected_response, request);
+
+ EXPECT_TRUE(rlz_lib::FinancialPing::FormRequest(rlz_lib::TOOLBAR_NOTIFIER,
+ points, "swg", brand, "IdOk", NULL, true, &request));
+ expected_response.clear();
+ base::StringAppendF(&expected_response,
+ "/tools/pso/ping?as=swg&brand=%s&pid=IdOk&"
+ "events=I7S,W1I&rep=2&rlz=T4:" DCC_PARAM, brand);
+ EXPECT_EQ(expected_response, request);
+
+ EXPECT_TRUE(rlz_lib::FinancialPing::FormRequest(rlz_lib::TOOLBAR_NOTIFIER,
+ points, "swg", brand, NULL, NULL, true, &request));
+ expected_response.clear();
+ base::StringAppendF(&expected_response,
+ "/tools/pso/ping?as=swg&brand=%s&events=I7S,W1I&rep=2"
+ "&rlz=T4:" DCC_PARAM, brand);
+ EXPECT_EQ(expected_response, request);
+
+
+ // Clear all events.
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+
+ // Clear all RLZs.
+ char rlz[rlz_lib::kMaxRlzLength + 1];
+ for (int ap = rlz_lib::NO_ACCESS_POINT + 1;
+ ap < rlz_lib::LAST_ACCESS_POINT; ap++) {
+ rlz[0] = 0;
+ rlz_lib::AccessPoint point = static_cast<rlz_lib::AccessPoint>(ap);
+ if (rlz_lib::GetAccessPointRlz(point, rlz, arraysize(rlz)) && rlz[0]) {
+ rlz_lib::SetAccessPointRlz(point, "");
+ }
+ }
+
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX,
+ "TbRlzValue"));
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::QUICK_SEARCH_BOX,
+ "QsbRlzValue"));
+ EXPECT_TRUE(rlz_lib::FinancialPing::FormRequest(rlz_lib::TOOLBAR_NOTIFIER,
+ points, "swg", brand, NULL, NULL, false, &request));
+ expected_response.clear();
+ base::StringAppendF(&expected_response,
+ "/tools/pso/ping?as=swg&brand=%s&rep=2&rlz=T4:TbRlzValue,"
+ "Q1:QsbRlzValue" DCC_PARAM, brand);
+ EXPECT_STREQ(expected_response.c_str(), request.c_str());
+
+ if (!GetAccessPointRlz(rlz_lib::IE_HOME_PAGE, rlz, arraysize(rlz))) {
+ points[2] = rlz_lib::IE_HOME_PAGE;
+ EXPECT_TRUE(rlz_lib::FinancialPing::FormRequest(rlz_lib::TOOLBAR_NOTIFIER,
+ points, "swg", brand, "MyId", "en-US", true, &request));
+ expected_response.clear();
+ base::StringAppendF(&expected_response,
+ "/tools/pso/ping?as=swg&brand=%s&hl=en-US&pid=MyId&rep=2"
+ "&rlz=T4:TbRlzValue,Q1:QsbRlzValue" DCC_PARAM, brand);
+ EXPECT_STREQ(expected_response.c_str(), request.c_str());
+ }
+}
+
+TEST_F(FinancialPingTest, FormRequestBadBrand) {
+ rlz_lib::AccessPoint points[] =
+ {rlz_lib::IETB_SEARCH_BOX, rlz_lib::NO_ACCESS_POINT,
+ rlz_lib::NO_ACCESS_POINT};
+
+ std::string request;
+ bool ok = rlz_lib::FinancialPing::FormRequest(rlz_lib::TOOLBAR_NOTIFIER,
+ points, "swg", "GOOG", NULL, "en", false, &request);
+ EXPECT_EQ(rlz_lib::SupplementaryBranding::GetBrand().empty(), ok);
+}
+
+
+static void SetLastPingTime(int64 time, rlz_lib::Product product) {
+ rlz_lib::ScopedRlzValueStoreLock lock;
+ rlz_lib::RlzValueStore* store = lock.GetStore();
+ ASSERT_TRUE(store);
+ ASSERT_TRUE(store->HasAccess(rlz_lib::RlzValueStore::kWriteAccess));
+ store->WritePingTime(product, time);
+}
+
+TEST_F(FinancialPingTest, IsPingTime) {
+ int64 now = GetSystemTimeAsInt64();
+ int64 last_ping = now - rlz_lib::kEventsPingInterval - k1MinuteInterval;
+ SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER);
+
+ // No events, last ping just over a day ago.
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ EXPECT_FALSE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER,
+ false));
+
+ // Has events, last ping just over a day ago.
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER,
+ false));
+
+ // Has events, last ping just under a day ago.
+ last_ping = now - rlz_lib::kEventsPingInterval + k1MinuteInterval;
+ SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER);
+ EXPECT_FALSE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER,
+ false));
+
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+
+ // No events, last ping just under a week ago.
+ last_ping = now - rlz_lib::kNoEventsPingInterval + k1MinuteInterval;
+ SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER);
+ EXPECT_FALSE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER,
+ false));
+
+ // No events, last ping just over a week ago.
+ last_ping = now - rlz_lib::kNoEventsPingInterval - k1MinuteInterval;
+ SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER);
+ EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER,
+ false));
+
+ // Last ping was in future (invalid).
+ last_ping = now + k1MinuteInterval;
+ SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER);
+ EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER,
+ false));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER,
+ false));
+}
+
+TEST_F(FinancialPingTest, BrandingIsPingTime) {
+ // Don't run these tests if a supplementary brand is already in place. That
+ // way we can control the branding.
+ if (!rlz_lib::SupplementaryBranding::GetBrand().empty())
+ return;
+
+ int64 now = GetSystemTimeAsInt64();
+ int64 last_ping = now - rlz_lib::kEventsPingInterval - k1MinuteInterval;
+ SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER);
+
+ // Has events, last ping just over a day ago.
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER,
+ false));
+
+ {
+ rlz_lib::SupplementaryBranding branding("TEST");
+ SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER);
+
+ // Has events, last ping just over a day ago.
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER,
+ false));
+ }
+
+ last_ping = now - k1MinuteInterval;
+ SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER);
+
+ EXPECT_FALSE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER,
+ false));
+
+ {
+ rlz_lib::SupplementaryBranding branding("TEST");
+ EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER,
+ false));
+ }
+}
+
+TEST_F(FinancialPingTest, ClearLastPingTime) {
+ int64 now = GetSystemTimeAsInt64();
+ int64 last_ping = now - rlz_lib::kEventsPingInterval + k1MinuteInterval;
+ SetLastPingTime(last_ping, rlz_lib::TOOLBAR_NOTIFIER);
+
+ // Has events, last ping just under a day ago.
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_FALSE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER,
+ false));
+
+ EXPECT_TRUE(rlz_lib::FinancialPing::ClearLastPingTime(
+ rlz_lib::TOOLBAR_NOTIFIER));
+ EXPECT_TRUE(rlz_lib::FinancialPing::IsPingTime(rlz_lib::TOOLBAR_NOTIFIER,
+ false));
+}
diff --git a/rlz/lib/lib_values.cc b/rlz/lib/lib_values.cc
new file mode 100644
index 0000000..467e4568
--- /dev/null
+++ b/rlz/lib/lib_values.cc
@@ -0,0 +1,208 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Key and value names of the location of the RLZ shared state.
+
+#include "rlz/lib/lib_values.h"
+
+#include "base/stringprintf.h"
+#include "rlz/lib/assert.h"
+
+namespace rlz_lib {
+
+//
+// Ping information.
+//
+
+// rep=2: includes the new stateful events.
+const char kProtocolCgiArgument[] = "rep=2";
+
+const char kEventsCgiVariable[] = "events";
+const char kStatefulEventsCgiVariable[] = "stateful-events";
+const char kEventsCgiSeparator = ',';
+
+const char kRlzCgiVariable[] = "rlz";
+const char kRlzCgiSeparator[] = ",";
+const char kRlzCgiIndicator[] = ":";
+
+const char kProductSignatureCgiVariable[] = "as";
+const char kProductBrandCgiVariable[] = "brand";
+const char kProductLanguageCgiVariable[] = "hl";
+const char kProductIdCgiVariable[] = "pid";
+
+const char kDccCgiVariable[] = "dcc";
+const char kRlsCgiVariable[] = "rls";
+const char kMachineIdCgiVariable[] = "id";
+const char kSetDccResponseVariable[] = "set_dcc";
+
+//
+// Financial server information.
+//
+
+const char kFinancialPingPath[] = "/tools/pso/ping";
+const char kFinancialServer[] = "clients1.google.com";
+const int kFinancialPort = 80;
+
+// Ping times in 100-nanosecond intervals.
+const int64 kEventsPingInterval = 24LL * 3600LL * 10000000LL; // 1 day
+const int64 kNoEventsPingInterval = kEventsPingInterval * 7LL; // 1 week
+
+const char kFinancialPingUserAgent[] = "Mozilla/4.0 (compatible; Win32)";
+const char* kFinancialPingResponseObjects[] = { "text/*", NULL };
+
+//
+// AccessPoint and Event names.
+//
+//
+
+const char* GetAccessPointName(AccessPoint point) {
+ switch (point) {
+ case NO_ACCESS_POINT: return "";
+ case IE_DEFAULT_SEARCH: return "I7";
+ case IE_HOME_PAGE: return "W1";
+ case IETB_SEARCH_BOX: return "T4";
+ case QUICK_SEARCH_BOX: return "Q1";
+ case GD_DESKBAND: return "D1";
+ case GD_SEARCH_GADGET: return "D2";
+ case GD_WEB_SERVER: return "D3";
+ case GD_OUTLOOK: return "D4";
+ case CHROME_OMNIBOX: return "C1";
+ case CHROME_HOME_PAGE: return "C2";
+ case FFTB2_BOX: return "B2";
+ case FFTB3_BOX: return "B3";
+ case PINYIN_IME_BHO: return "N1";
+ case IGOOGLE_WEBPAGE: return "G1";
+ case MOBILE_IDLE_SCREEN_BLACKBERRY: return "H1";
+ case MOBILE_IDLE_SCREEN_WINMOB: return "H2";
+ case MOBILE_IDLE_SCREEN_SYMBIAN: return "H3";
+ case FF_HOME_PAGE: return "R0";
+ case FF_SEARCH_BOX: return "R1";
+ case IE_BROWSED_PAGE: return "R2";
+ case QSB_WIN_BOX: return "R3";
+ case WEBAPPS_CALENDAR: return "R4";
+ case WEBAPPS_DOCS: return "R5";
+ case WEBAPPS_GMAIL: return "R6";
+ case IETB_LINKDOCTOR: return "R7";
+ case FFTB_LINKDOCTOR: return "R8";
+ case IETB7_SEARCH_BOX: return "T7";
+ case TB8_SEARCH_BOX: return "T8";
+ case CHROME_FRAME: return "C3";
+ case PARTNER_AP_1: return "V1";
+ case PARTNER_AP_2: return "V2";
+ case PARTNER_AP_3: return "V3";
+ case PARTNER_AP_4: return "V4";
+ case PARTNER_AP_5: return "V5";
+ case UNDEFINED_AP_H: return "RH";
+ case UNDEFINED_AP_I: return "RI";
+ case UNDEFINED_AP_J: return "RJ";
+ case UNDEFINED_AP_K: return "RK";
+ case UNDEFINED_AP_L: return "RL";
+ case UNDEFINED_AP_M: return "RM";
+ case UNDEFINED_AP_N: return "RN";
+ case UNDEFINED_AP_O: return "RO";
+ case UNDEFINED_AP_P: return "RP";
+ case UNDEFINED_AP_Q: return "RQ";
+ case UNDEFINED_AP_R: return "RR";
+ case UNDEFINED_AP_S: return "RS";
+ case UNDEFINED_AP_T: return "RT";
+ case UNDEFINED_AP_U: return "RU";
+ case UNDEFINED_AP_V: return "RV";
+ case UNDEFINED_AP_W: return "RW";
+ case UNDEFINED_AP_X: return "RX";
+ case UNDEFINED_AP_Y: return "RY";
+ case UNDEFINED_AP_Z: return "RZ";
+ case PACK_AP0: return "U0";
+ case PACK_AP1: return "U1";
+ case PACK_AP2: return "U2";
+ case PACK_AP3: return "U3";
+ case PACK_AP4: return "U4";
+ case PACK_AP5: return "U5";
+ case PACK_AP6: return "U6";
+ case PACK_AP7: return "U7";
+ case PACK_AP8: return "U8";
+ case PACK_AP9: return "U9";
+ case PACK_AP10: return "UA";
+ case PACK_AP11: return "UB";
+ case PACK_AP12: return "UC";
+ case PACK_AP13: return "UD";
+ case LAST_ACCESS_POINT: ; // Fall through.
+ }
+
+ ASSERT_STRING("GetAccessPointName: Unknown Access Point");
+ return NULL;
+}
+
+
+bool GetAccessPointFromName(const char* name, AccessPoint* point) {
+ if (!point) {
+ ASSERT_STRING("GetAccessPointFromName: point is NULL");
+ return false;
+ }
+ *point = NO_ACCESS_POINT;
+ if (!name)
+ return false;
+
+ for (int i = NO_ACCESS_POINT; i < LAST_ACCESS_POINT; i++)
+ if (strcmp(name, GetAccessPointName(static_cast<AccessPoint>(i))) == 0) {
+ *point = static_cast<AccessPoint>(i);
+ return true;
+ }
+
+ return false;
+}
+
+
+const char* GetEventName(Event event) {
+ switch (event) {
+ case INVALID_EVENT: return "";
+ case INSTALL: return "I";
+ case SET_TO_GOOGLE: return "S";
+ case FIRST_SEARCH: return "F";
+ case REPORT_RLS: return "R";
+ case ACTIVATE: return "A";
+ case LAST_EVENT: ; // Fall through.
+ }
+
+ ASSERT_STRING("GetPointName: Unknown Event");
+ return NULL;
+}
+
+
+bool GetEventFromName(const char* name, Event* event) {
+ if (!event) {
+ ASSERT_STRING("GetEventFromName: event is NULL");
+ return false;
+ }
+ *event = INVALID_EVENT;
+ if (!name)
+ return false;
+
+ for (int i = INVALID_EVENT; i < LAST_EVENT; i++)
+ if (strcmp(name, GetEventName(static_cast<Event>(i))) == 0) {
+ *event = static_cast<Event>(i);
+ return true;
+ }
+
+ return false;
+}
+
+const char* GetProductName(Product product) {
+ switch (product) {
+ case IE_TOOLBAR: return "T";
+ case TOOLBAR_NOTIFIER: return "P";
+ case PACK: return "U";
+ case DESKTOP: return "D";
+ case CHROME: return "C";
+ case FF_TOOLBAR: return "B";
+ case QSB_WIN: return "K";
+ case WEBAPPS: return "W";
+ case PINYIN_IME: return "N";
+ case PARTNER: return "V";
+ }
+
+ ASSERT_STRING("GetProductName: Unknown Product");
+ return "";
+}
+
+} // namespace rlz_lib
diff --git a/rlz/lib/lib_values.h b/rlz/lib/lib_values.h
new file mode 100644
index 0000000..05fbc6b
--- /dev/null
+++ b/rlz/lib/lib_values.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Key and value names of the location of the RLZ shared state.
+
+#ifndef RLZ_LIB_LIB_VALUES_H_
+#define RLZ_LIB_LIB_VALUES_H_
+
+#include "base/basictypes.h"
+#include "rlz/lib/rlz_enums.h"
+
+namespace rlz_lib {
+
+//
+// Ping CGI arguments:
+//
+// Events are reported as (without spaces):
+// kEventsCgiVariable = <AccessPoint1><Event1> kEventsCgiSeparator <P2><E2>...
+//
+// Event responses from the server look like:
+// kEventsCgiVariable : <AccessPoint1><Event1> kEventsCgiSeparator <P2><E2>...
+//
+// RLZ's are reported as (without spaces):
+// kRlzCgiVariable = <AccessPoint> <kRlzCgiIndicator> <RLZ value>
+// <kRlzCgiSeparator> <AP2><Indicator><V2><Separator> ....
+//
+// RLZ responses from the server look like (without spaces):
+// kRlzCgiVariable<Access Point> : <RLZ value>
+//
+// DCC if reported should look like (without spaces):
+// kDccCgiVariable = <DCC Value>
+//
+// RLS if reported should look like (without spaces):
+// kRlsCgiVariable = <RLS Value>
+//
+// Machine ID if reported should look like (without spaces):
+// kMachineIdCgiVariable = <Machine ID Value>
+//
+// A server response setting / confirming the DCC will look like (no spaces):
+// kDccCgiVariable : <DCC Value>
+//
+// Each ping to the server must also contain kProtocolCgiArgument as well.
+//
+// Pings may also contain (but not necessarily controlled by this Lib):
+// - The product signature: kProductSignatureCgiVariable = <signature>
+// - The product brand: kProductBrandCgiVariable = <brand>
+// - The product installation ID: kProductIdCgiVariable = <id>
+extern const char kEventsCgiVariable[];
+extern const char kStatefulEventsCgiVariable[];
+extern const char kEventsCgiSeparator;
+
+extern const char kDccCgiVariable[];
+extern const char kProtocolCgiArgument[];
+
+extern const char kProductSignatureCgiVariable[];
+extern const char kProductBrandCgiVariable[];
+extern const char kProductLanguageCgiVariable[];
+extern const char kProductIdCgiVariable[];
+
+extern const char kRlzCgiVariable[];
+extern const char kRlzCgiSeparator[];
+extern const char kRlzCgiIndicator[];
+
+extern const char kRlsCgiVariable[];
+extern const char kMachineIdCgiVariable[];
+extern const char kSetDccResponseVariable[];
+
+//
+// Financial ping server information.
+//
+
+extern const char kFinancialPingPath[];
+extern const char kFinancialServer[];
+
+extern const int kFinancialPort;
+
+extern const int64 kEventsPingInterval;
+extern const int64 kNoEventsPingInterval;
+
+extern const char kFinancialPingUserAgent[];
+extern const char* kFinancialPingResponseObjects[];
+
+//
+// The names for AccessPoints and Events that we use MUST be the same
+// as those used/understood by the server.
+//
+const char* GetAccessPointName(AccessPoint point);
+bool GetAccessPointFromName(const char* name, AccessPoint* point);
+
+const char* GetEventName(Event event);
+bool GetEventFromName(const char* name, Event* event);
+
+// The names for products are used only client-side.
+const char* GetProductName(Product product);
+
+} // namespace rlz_lib
+
+#endif // RLZ_LIB_LIB_VALUES_H_
diff --git a/rlz/lib/lib_values_unittest.cc b/rlz/lib/lib_values_unittest.cc
new file mode 100644
index 0000000..7e8fa83
--- /dev/null
+++ b/rlz/lib/lib_values_unittest.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "rlz/lib/lib_values.h"
+
+#include "base/logging.h"
+#include "rlz/lib/assert.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(LibValuesUnittest, GetAccessPointFromName) {
+ rlz_lib::SetExpectedAssertion("GetAccessPointFromName: point is NULL");
+ EXPECT_FALSE(rlz_lib::GetAccessPointFromName("", NULL));
+ rlz_lib::SetExpectedAssertion("");
+
+ rlz_lib::AccessPoint point;
+ EXPECT_FALSE(rlz_lib::GetAccessPointFromName(NULL, &point));
+ EXPECT_EQ(rlz_lib::NO_ACCESS_POINT, point);
+
+ EXPECT_TRUE(rlz_lib::GetAccessPointFromName("", &point));
+ EXPECT_EQ(rlz_lib::NO_ACCESS_POINT, point);
+
+ EXPECT_FALSE(rlz_lib::GetAccessPointFromName("i1", &point));
+ EXPECT_EQ(rlz_lib::NO_ACCESS_POINT, point);
+
+ EXPECT_TRUE(rlz_lib::GetAccessPointFromName("I7", &point));
+ EXPECT_EQ(rlz_lib::IE_DEFAULT_SEARCH, point);
+
+ EXPECT_TRUE(rlz_lib::GetAccessPointFromName("T4", &point));
+ EXPECT_EQ(rlz_lib::IETB_SEARCH_BOX, point);
+
+ EXPECT_FALSE(rlz_lib::GetAccessPointFromName("T4 ", &point));
+ EXPECT_EQ(rlz_lib::NO_ACCESS_POINT, point);
+}
+
+
+TEST(LibValuesUnittest, GetEventFromName) {
+ rlz_lib::SetExpectedAssertion("GetEventFromName: event is NULL");
+ EXPECT_FALSE(rlz_lib::GetEventFromName("", NULL));
+ rlz_lib::SetExpectedAssertion("");
+
+ rlz_lib::Event event;
+ EXPECT_FALSE(rlz_lib::GetEventFromName(NULL, &event));
+ EXPECT_EQ(rlz_lib::INVALID_EVENT, event);
+
+ EXPECT_TRUE(rlz_lib::GetEventFromName("", &event));
+ EXPECT_EQ(rlz_lib::INVALID_EVENT, event);
+
+ EXPECT_FALSE(rlz_lib::GetEventFromName("i1", &event));
+ EXPECT_EQ(rlz_lib::INVALID_EVENT, event);
+
+ EXPECT_TRUE(rlz_lib::GetEventFromName("I", &event));
+ EXPECT_EQ(rlz_lib::INSTALL, event);
+
+ EXPECT_TRUE(rlz_lib::GetEventFromName("F", &event));
+ EXPECT_EQ(rlz_lib::FIRST_SEARCH, event);
+
+ EXPECT_FALSE(rlz_lib::GetEventFromName("F ", &event));
+ EXPECT_EQ(rlz_lib::INVALID_EVENT, event);
+}
diff --git a/rlz/lib/machine_id.cc b/rlz/lib/machine_id.cc
new file mode 100644
index 0000000..48d05b9
--- /dev/null
+++ b/rlz/lib/machine_id.cc
@@ -0,0 +1,80 @@
+#include "rlz/lib/machine_id.h"
+
+#include "base/sha1.h"
+#include "rlz/lib/assert.h"
+#include "rlz/lib/crc8.h"
+#include "rlz/lib/string_utils.h"
+
+namespace rlz_lib {
+
+bool GetMachineId(std::string* machine_id) {
+ if (!machine_id)
+ return false;
+
+ static std::string calculated_id;
+ static bool calculated = false;
+ if (calculated) {
+ *machine_id = calculated_id;
+ return true;
+ }
+
+ string16 sid_string;
+ int volume_id;
+ if (!GetRawMachineId(&sid_string, &volume_id))
+ return false;
+
+ if (!testing::GetMachineIdImpl(sid_string, volume_id, machine_id))
+ return false;
+
+ calculated = true;
+ calculated_id = *machine_id;
+ return true;
+}
+
+namespace testing {
+
+bool GetMachineIdImpl(const string16& sid_string,
+ int volume_id,
+ std::string* machine_id) {
+ machine_id->clear();
+
+ // The ID should be the SID hash + the Hard Drive SNo. + checksum byte.
+ static const int kSizeWithoutChecksum = base::kSHA1Length + sizeof(int);
+ std::basic_string<unsigned char> id_binary(kSizeWithoutChecksum + 1, 0);
+
+ if (!sid_string.empty()) {
+ // In order to be compatible with the old version of RLZ, the hash of the
+ // SID must be done with all the original bytes from the unicode string.
+ // However, the chromebase SHA1 hash function takes only an std::string as
+ // input, so the unicode string needs to be converted to std::string
+ // "as is".
+ size_t byte_count = sid_string.size() * sizeof(string16::value_type);
+ const char* buffer = reinterpret_cast<const char*>(sid_string.c_str());
+ std::string sid_string_buffer(buffer, byte_count);
+
+ // Note that digest can have embedded nulls.
+ std::string digest(base::SHA1HashString(sid_string_buffer));
+ VERIFY(digest.size() == base::kSHA1Length);
+ std::copy(digest.begin(), digest.end(), id_binary.begin());
+ }
+
+ // Convert from int to binary (makes big-endian).
+ for (size_t i = 0; i < sizeof(int); i++) {
+ int shift_bits = 8 * (sizeof(int) - i - 1);
+ id_binary[base::kSHA1Length + i] = static_cast<unsigned char>(
+ (volume_id >> shift_bits) & 0xFF);
+ }
+
+ // Append the checksum byte.
+ if (!sid_string.empty() || (0 != volume_id))
+ rlz_lib::Crc8::Generate(id_binary.c_str(),
+ kSizeWithoutChecksum,
+ &id_binary[kSizeWithoutChecksum]);
+
+ return rlz_lib::BytesToString(
+ id_binary.c_str(), kSizeWithoutChecksum + 1, machine_id);
+}
+
+} // namespace testing
+
+} // namespace rlz_lib
diff --git a/rlz/lib/machine_id.h b/rlz/lib/machine_id.h
new file mode 100644
index 0000000..3ec4888
--- /dev/null
+++ b/rlz/lib/machine_id.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RLZ_LIB_MACHINE_ID_H_
+#define RLZ_LIB_MACHINE_ID_H_
+
+#include "base/string16.h"
+
+#include <string>
+
+namespace rlz_lib {
+
+// Gets the unique ID for the machine used for RLZ tracking purposes. On
+// Windows, this ID is derived from the Windows machine SID, and is the string
+// representation of a 20 byte hash + 4 bytes volum id + a 1 byte checksum.
+// Included in financial pings with events, unless explicitly forbidden by the
+// calling application.
+bool GetMachineId(std::string* machine_id);
+
+// Retrieves a raw machine identifier string and a machine-specific
+// 4 byte value. GetMachineId() will SHA1 |data|, append |more_data|, compute
+// the Crc8 of that, and return a hex-encoded string of that data.
+bool GetRawMachineId(string16* data, int* more_data);
+
+namespace testing {
+bool GetMachineIdImpl(const string16& sid_string,
+ int volume_id,
+ std::string* machine_id);
+} // namespace testing
+
+} // namespace rlz_lib
+
+#endif // RLZ_LIB_MACHINE_ID_H_
diff --git a/rlz/lib/machine_id_unittest.cc b/rlz/lib/machine_id_unittest.cc
new file mode 100644
index 0000000..cecf529
--- /dev/null
+++ b/rlz/lib/machine_id_unittest.cc
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "rlz/lib/machine_id.h"
+
+#include "base/string16.h"
+#include "base/utf_string_conversions.h"
+#include "rlz/test/rlz_test_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// This test will fail if the behavior of GetMachineId changes.
+TEST(MachineDealCodeTestMachineId, MachineId) {
+ string16 computer_sid(UTF8ToUTF16(
+ "S-1-5-21-2345599882-2448789067-1921365677"));
+ std::string id;
+ rlz_lib::testing::GetMachineIdImpl(computer_sid, 2651229008, &id);
+ EXPECT_STREQ("A341BA986A7E86840688977FCF20C86E253F00919E068B50F8",
+ id.c_str());
+}
diff --git a/rlz/lib/rlz_enums.h b/rlz/lib/rlz_enums.h
new file mode 100644
index 0000000..4fc60ff
--- /dev/null
+++ b/rlz/lib/rlz_enums.h
@@ -0,0 +1,132 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RLZ_LIB_RLZ_ENUMS_H_
+#define RLZ_LIB_RLZ_ENUMS_H_
+
+namespace rlz_lib {
+
+// An Access Point offers a way to search using Google.
+enum AccessPoint {
+ NO_ACCESS_POINT = 0,
+
+ // Access points on Windows PCs.
+ IE_DEFAULT_SEARCH, // The IE7+ chrome search box next to the address bar.
+ IE_HOME_PAGE, // Search box on IE 5+ primary home page when Google.
+ IETB_SEARCH_BOX, // IE Toolbar v4+ search box.
+ QUICK_SEARCH_BOX, // Search box brought up by ctrl-ctrl key sequence,
+ // distributed as a part of Google Desktop
+ GD_DESKBAND, // Search box in deskbar when GD in deskbar mode.
+ GD_SEARCH_GADGET, // Search gadget when GD in sidebar mode.
+ GD_WEB_SERVER, // Boxes in web pages shown by local GD web server.
+ GD_OUTLOOK, // Search box installed within outlook by GD.
+ CHROME_OMNIBOX, // Chrome searches through the address bar omnibox.
+ CHROME_HOME_PAGE, // Chrome searches through Google as home page.
+ FFTB2_BOX, // Firefox Toolbar v2 Search Box.
+ FFTB3_BOX, // Firefox Toolbar v3+ Search Box.
+ PINYIN_IME_BHO, // Goopy Input Method Editor BHO (Pinyin).
+ IGOOGLE_WEBPAGE, // Searches on iGoogle through partner deals.
+
+ // Mobile idle screen search for different platforms.
+ MOBILE_IDLE_SCREEN_BLACKBERRY,
+ MOBILE_IDLE_SCREEN_WINMOB,
+ MOBILE_IDLE_SCREEN_SYMBIAN,
+
+ FF_HOME_PAGE, // Firefox home page when set to Google.
+ FF_SEARCH_BOX, // Firefox search box when set to Google.
+ IE_BROWSED_PAGE, // Search made in IE through user action (no product).
+ QSB_WIN_BOX, // Search box brought up by ctrl+space by default,
+ // distributed by toolbar and separate from the GD QSB
+ WEBAPPS_CALENDAR, // Webapps use of calendar.
+ WEBAPPS_DOCS, // Webapps use of writely.
+ WEBAPPS_GMAIL, // Webapps use of Gmail.
+
+ IETB_LINKDOCTOR, // Linkdoctor of IE Toolbar
+ FFTB_LINKDOCTOR, // Linkdoctor of FF Toolbar
+ IETB7_SEARCH_BOX, // IE Toolbar search box.
+ TB8_SEARCH_BOX, // IE/FF Toolbar search box.
+ CHROME_FRAME, // Chrome Frame.
+
+ // Partner access points.
+ PARTNER_AP_1,
+ PARTNER_AP_2,
+ PARTNER_AP_3,
+ PARTNER_AP_4,
+ PARTNER_AP_5,
+
+ // Unclaimed access points - should be used first before creating new APs.
+ // Please also make sure you re-name the enum before using an unclaimed value;
+ // this acts as a check to ensure we don't have collisions.
+ UNDEFINED_AP_H,
+ UNDEFINED_AP_I,
+ UNDEFINED_AP_J,
+ UNDEFINED_AP_K,
+ UNDEFINED_AP_L,
+ UNDEFINED_AP_M,
+ UNDEFINED_AP_N,
+ UNDEFINED_AP_O,
+ UNDEFINED_AP_P,
+ UNDEFINED_AP_Q,
+ UNDEFINED_AP_R,
+ UNDEFINED_AP_S,
+ UNDEFINED_AP_T,
+ UNDEFINED_AP_U,
+ UNDEFINED_AP_V,
+ UNDEFINED_AP_W,
+ UNDEFINED_AP_X,
+ UNDEFINED_AP_Y,
+ UNDEFINED_AP_Z,
+
+ PACK_AP0,
+ PACK_AP1,
+ PACK_AP2,
+ PACK_AP3,
+ PACK_AP4,
+ PACK_AP5,
+ PACK_AP6,
+ PACK_AP7,
+ PACK_AP8,
+ PACK_AP9,
+ PACK_AP10,
+ PACK_AP11,
+ PACK_AP12,
+ PACK_AP13,
+
+ // New Access Points should be added here without changing existing enums,
+ // (i.e. before LAST_ACCESS_POINT)
+ LAST_ACCESS_POINT
+};
+
+// A product is an entity which wants to gets credit for setting
+// an Access Point.
+enum Product {
+ IE_TOOLBAR = 1,
+ TOOLBAR_NOTIFIER,
+ PACK,
+ DESKTOP,
+ CHROME,
+ FF_TOOLBAR,
+ QSB_WIN,
+ WEBAPPS,
+ PINYIN_IME,
+ PARTNER
+ // New Products should be added here without changing existing enums.
+};
+
+// Events that note Product and Access Point modifications.
+enum Event {
+ INVALID_EVENT = 0,
+ INSTALL = 1, // Access Point added to the system.
+ SET_TO_GOOGLE, // Point set from non-Google provider to Google.
+ FIRST_SEARCH, // First search from point since INSTALL
+ REPORT_RLS, // Report old system "RLS" financial value for this point.
+ // New Events should be added here without changing existing enums,
+ // before LAST_EVENT.
+ ACTIVATE, // Product being used for a period of time.
+ LAST_EVENT
+};
+
+} // namespace rlz_lib
+
+#endif // RLZ_LIB_RLZ_ENUMS_H_
diff --git a/rlz/lib/rlz_lib.cc b/rlz/lib/rlz_lib.cc
new file mode 100644
index 0000000..a613876
--- /dev/null
+++ b/rlz/lib/rlz_lib.cc
@@ -0,0 +1,651 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A library to manage RLZ information for access-points shared
+// across different client applications.
+
+#include "rlz/lib/rlz_lib.h"
+
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "rlz/lib/assert.h"
+#include "rlz/lib/crc32.h"
+#include "rlz/lib/financial_ping.h"
+#include "rlz/lib/lib_values.h"
+#include "rlz/lib/rlz_value_store.h"
+#include "rlz/lib/string_utils.h"
+
+namespace {
+
+// Event information returned from ping response.
+struct ReturnedEvent {
+ rlz_lib::AccessPoint access_point;
+ rlz_lib::Event event_type;
+};
+
+// Helper functions
+
+bool IsAccessPointSupported(rlz_lib::AccessPoint point) {
+ switch (point) {
+ case rlz_lib::NO_ACCESS_POINT:
+ case rlz_lib::LAST_ACCESS_POINT:
+
+ case rlz_lib::MOBILE_IDLE_SCREEN_BLACKBERRY:
+ case rlz_lib::MOBILE_IDLE_SCREEN_WINMOB:
+ case rlz_lib::MOBILE_IDLE_SCREEN_SYMBIAN:
+ // These AP's are never available on Windows PCs.
+ return false;
+
+ case rlz_lib::IE_DEFAULT_SEARCH:
+ case rlz_lib::IE_HOME_PAGE:
+ case rlz_lib::IETB_SEARCH_BOX:
+ case rlz_lib::QUICK_SEARCH_BOX:
+ case rlz_lib::GD_DESKBAND:
+ case rlz_lib::GD_SEARCH_GADGET:
+ case rlz_lib::GD_WEB_SERVER:
+ case rlz_lib::GD_OUTLOOK:
+ case rlz_lib::CHROME_OMNIBOX:
+ case rlz_lib::CHROME_HOME_PAGE:
+ // TODO: Figure out when these settings are set to Google.
+
+ default:
+ return true;
+ }
+}
+
+// Current RLZ can only use [a-zA-Z0-9_\-]
+// We will be more liberal and allow some additional chars, but not url meta
+// chars.
+bool IsGoodRlzChar(const char ch) {
+ if (IsAsciiAlpha(ch) || IsAsciiDigit(ch))
+ return true;
+
+ switch (ch) {
+ case '_':
+ case '-':
+ case '!':
+ case '@':
+ case '$':
+ case '*':
+ case '(':
+ case ')':
+ case ';':
+ case '.':
+ case '<':
+ case '>':
+ return true;
+ }
+
+ return false;
+}
+
+// This function will remove bad rlz chars and also limit the max rlz to some
+// reasonable size. It also assumes that normalized_rlz is at least
+// kMaxRlzLength+1 long.
+void NormalizeRlz(const char* raw_rlz, char* normalized_rlz) {
+ int index = 0;
+ for (; raw_rlz[index] != 0 && index < rlz_lib::kMaxRlzLength; ++index) {
+ char current = raw_rlz[index];
+ if (IsGoodRlzChar(current)) {
+ normalized_rlz[index] = current;
+ } else {
+ normalized_rlz[index] = '.';
+ }
+ }
+
+ normalized_rlz[index] = 0;
+}
+
+void GetEventsFromResponseString(
+ const std::string& response_line,
+ const std::string& field_header,
+ std::vector<ReturnedEvent>* event_array) {
+ // Get the string of events.
+ std::string events = response_line.substr(field_header.size());
+ TrimWhitespaceASCII(events, TRIM_LEADING, &events);
+
+ int events_length = events.find_first_of("\r\n ");
+ if (events_length < 0)
+ events_length = events.size();
+ events = events.substr(0, events_length);
+
+ // Break this up into individual events
+ int event_end_index = -1;
+ do {
+ int event_begin = event_end_index + 1;
+ event_end_index = events.find(rlz_lib::kEventsCgiSeparator, event_begin);
+ int event_end = event_end_index;
+ if (event_end < 0)
+ event_end = events_length;
+
+ std::string event_string = events.substr(event_begin,
+ event_end - event_begin);
+ if (event_string.size() != 3) // 3 = 2(AP) + 1(E)
+ continue;
+
+ rlz_lib::AccessPoint point = rlz_lib::NO_ACCESS_POINT;
+ rlz_lib::Event event = rlz_lib::INVALID_EVENT;
+ if (!GetAccessPointFromName(event_string.substr(0, 2).c_str(), &point) ||
+ point == rlz_lib::NO_ACCESS_POINT) {
+ continue;
+ }
+
+ if (!GetEventFromName(event_string.substr(event_string.size() - 1).c_str(),
+ &event) || event == rlz_lib::INVALID_EVENT) {
+ continue;
+ }
+
+ ReturnedEvent current_event = {point, event};
+ event_array->push_back(current_event);
+ } while (event_end_index >= 0);
+}
+
+// Event storage functions.
+bool RecordStatefulEvent(rlz_lib::Product product, rlz_lib::AccessPoint point,
+ rlz_lib::Event event) {
+ rlz_lib::ScopedRlzValueStoreLock lock;
+ rlz_lib::RlzValueStore* store = lock.GetStore();
+ if (!store || !store->HasAccess(rlz_lib::RlzValueStore::kWriteAccess))
+ return false;
+
+ // Write the new event to the value store.
+ const char* point_name = GetAccessPointName(point);
+ const char* event_name = GetEventName(event);
+ if (!point_name || !event_name)
+ return false;
+
+ if (!point_name[0] || !event_name[0])
+ return false;
+
+ std::string new_event_value;
+ base::StringAppendF(&new_event_value, "%s%s", point_name, event_name);
+ return store->AddStatefulEvent(product, new_event_value.c_str());
+}
+
+bool GetProductEventsAsCgiHelper(rlz_lib::Product product, char* cgi,
+ size_t cgi_size,
+ rlz_lib::RlzValueStore* store) {
+ // Prepend the CGI param key to the buffer.
+ std::string cgi_arg;
+ base::StringAppendF(&cgi_arg, "%s=", rlz_lib::kEventsCgiVariable);
+ if (cgi_size <= cgi_arg.size())
+ return false;
+
+ size_t index;
+ for (index = 0; index < cgi_arg.size(); ++index)
+ cgi[index] = cgi_arg[index];
+
+ // Read stored events.
+ std::vector<std::string> events;
+ if (!store->ReadProductEvents(product, &events))
+ return false;
+
+ // Append the events to the buffer.
+ size_t num_values = 0;
+
+ for (num_values = 0; num_values < events.size(); ++num_values) {
+ cgi[index] = '\0';
+
+ int divider = num_values > 0 ? 1 : 0;
+ int size = cgi_size - (index + divider);
+ if (size <= 0)
+ return cgi_size >= (rlz_lib::kMaxCgiLength + 1);
+
+ strncpy(cgi + index + divider, events[num_values].c_str(), size);
+ if (divider)
+ cgi[index] = rlz_lib::kEventsCgiSeparator;
+
+ index += std::min((int)events[num_values].length(), size) + divider;
+ }
+
+ cgi[index] = '\0';
+
+ return num_values > 0;
+}
+
+} // namespace
+
+namespace rlz_lib {
+
+#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
+bool SetURLRequestContext(net::URLRequestContextGetter* context) {
+ return FinancialPing::SetURLRequestContext(context);
+}
+#endif
+
+bool GetProductEventsAsCgi(Product product, char* cgi, size_t cgi_size) {
+ if (!cgi || cgi_size <= 0) {
+ ASSERT_STRING("GetProductEventsAsCgi: Invalid buffer");
+ return false;
+ }
+
+ cgi[0] = 0;
+
+ ScopedRlzValueStoreLock lock;
+ RlzValueStore* store = lock.GetStore();
+ if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
+ return false;
+
+ size_t size_local = std::min(
+ static_cast<size_t>(kMaxCgiLength + 1), cgi_size);
+ bool result = GetProductEventsAsCgiHelper(product, cgi, size_local, store);
+
+ if (!result) {
+ ASSERT_STRING("GetProductEventsAsCgi: Possibly insufficient buffer size");
+ cgi[0] = 0;
+ return false;
+ }
+
+ return true;
+}
+
+bool RecordProductEvent(Product product, AccessPoint point, Event event) {
+ ScopedRlzValueStoreLock lock;
+ RlzValueStore* store = lock.GetStore();
+ if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
+ return false;
+
+ // Get this event's value.
+ const char* point_name = GetAccessPointName(point);
+ const char* event_name = GetEventName(event);
+ if (!point_name || !event_name)
+ return false;
+
+ if (!point_name[0] || !event_name[0])
+ return false;
+
+ std::string new_event_value;
+ base::StringAppendF(&new_event_value, "%s%s", point_name, event_name);
+
+ // Check whether this event is a stateful event. If so, don't record it.
+ if (store->IsStatefulEvent(product, new_event_value.c_str())) {
+ // For a stateful event we skip recording, this function is also
+ // considered successful.
+ return true;
+ }
+
+ // Write the new event to the value store.
+ return store->AddProductEvent(product, new_event_value.c_str());
+}
+
+bool ClearProductEvent(Product product, AccessPoint point, Event event) {
+ ScopedRlzValueStoreLock lock;
+ RlzValueStore* store = lock.GetStore();
+ if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
+ return false;
+
+ // Get the event's value store value and delete it.
+ const char* point_name = GetAccessPointName(point);
+ const char* event_name = GetEventName(event);
+ if (!point_name || !event_name)
+ return false;
+
+ if (!point_name[0] || !event_name[0])
+ return false;
+
+ std::string event_value;
+ base::StringAppendF(&event_value, "%s%s", point_name, event_name);
+ return store->ClearProductEvent(product, event_value.c_str());
+}
+
+// RLZ storage functions.
+
+bool GetAccessPointRlz(AccessPoint point, char* rlz, size_t rlz_size) {
+ if (!rlz || rlz_size <= 0) {
+ ASSERT_STRING("GetAccessPointRlz: Invalid buffer");
+ return false;
+ }
+
+ rlz[0] = 0;
+
+ ScopedRlzValueStoreLock lock;
+ RlzValueStore* store = lock.GetStore();
+ if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
+ return false;
+
+ if (!IsAccessPointSupported(point))
+ return false;
+
+ return store->ReadAccessPointRlz(point, rlz, rlz_size);
+}
+
+bool SetAccessPointRlz(AccessPoint point, const char* new_rlz) {
+ ScopedRlzValueStoreLock lock;
+ RlzValueStore* store = lock.GetStore();
+ if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
+ return false;
+
+ if (!new_rlz) {
+ ASSERT_STRING("SetAccessPointRlz: Invalid buffer");
+ return false;
+ }
+
+ // Return false if the access point is not set to Google.
+ if (!IsAccessPointSupported(point)) {
+ ASSERT_STRING(("SetAccessPointRlz: "
+ "Cannot set RLZ for unsupported access point."));
+ return false;
+ }
+
+ // Verify the RLZ length.
+ size_t rlz_length = strlen(new_rlz);
+ if (rlz_length > kMaxRlzLength) {
+ ASSERT_STRING("SetAccessPointRlz: RLZ length is exceeds max allowed.");
+ return false;
+ }
+
+ char normalized_rlz[kMaxRlzLength + 1];
+ NormalizeRlz(new_rlz, normalized_rlz);
+ VERIFY(strlen(new_rlz) == rlz_length);
+
+ // Setting RLZ to empty == clearing.
+ if (normalized_rlz[0] == 0)
+ return store->ClearAccessPointRlz(point);
+ return store->WriteAccessPointRlz(point, normalized_rlz);
+}
+
+// Financial Server pinging functions.
+
+bool FormFinancialPingRequest(Product product, const AccessPoint* access_points,
+ const char* product_signature,
+ const char* product_brand,
+ const char* product_id,
+ const char* product_lang,
+ bool exclude_machine_id,
+ char* request, size_t request_buffer_size) {
+ if (!request || request_buffer_size == 0)
+ return false;
+
+ request[0] = 0;
+
+ std::string request_string;
+ if (!FinancialPing::FormRequest(product, access_points, product_signature,
+ product_brand, product_id, product_lang,
+ exclude_machine_id, &request_string))
+ return false;
+
+ if (request_string.size() >= request_buffer_size)
+ return false;
+
+ strncpy(request, request_string.c_str(), request_buffer_size);
+ request[request_buffer_size - 1] = 0;
+ return true;
+}
+
+bool PingFinancialServer(Product product, const char* request, char* response,
+ size_t response_buffer_size) {
+ if (!response || response_buffer_size == 0)
+ return false;
+ response[0] = 0;
+
+ // Check if the time is right to ping.
+ if (!FinancialPing::IsPingTime(product, false))
+ return false;
+
+ // Send out the ping.
+ std::string response_string;
+ if (!FinancialPing::PingServer(request, &response_string))
+ return false;
+
+ if (response_string.size() >= response_buffer_size)
+ return false;
+
+ strncpy(response, response_string.c_str(), response_buffer_size);
+ response[response_buffer_size - 1] = 0;
+ return true;
+}
+
+bool IsPingResponseValid(const char* response, int* checksum_idx) {
+ if (!response || !response[0])
+ return false;
+
+ if (checksum_idx)
+ *checksum_idx = -1;
+
+ if (strlen(response) > kMaxPingResponseLength) {
+ ASSERT_STRING("IsPingResponseValid: response is too long to parse.");
+ return false;
+ }
+
+ // Find the checksum line.
+ std::string response_string(response);
+
+ std::string checksum_param("\ncrc32: ");
+ int calculated_crc;
+ int checksum_index = response_string.find(checksum_param);
+ if (checksum_index >= 0) {
+ // Calculate checksum of message preceeding checksum line.
+ // (+ 1 to include the \n)
+ std::string message(response_string.substr(0, checksum_index + 1));
+ if (!Crc32(message.c_str(), &calculated_crc))
+ return false;
+ } else {
+ checksum_param = "crc32: "; // Empty response case.
+ if (!StartsWithASCII(response_string, checksum_param, true))
+ return false;
+
+ checksum_index = 0;
+ if (!Crc32("", &calculated_crc))
+ return false;
+ }
+
+ // Find the checksum value on the response.
+ int checksum_end = response_string.find("\n", checksum_index + 1);
+ if (checksum_end < 0)
+ checksum_end = response_string.size();
+
+ int checksum_begin = checksum_index + checksum_param.size();
+ std::string checksum = response_string.substr(checksum_begin,
+ checksum_end - checksum_begin + 1);
+ TrimWhitespaceASCII(checksum, TRIM_ALL, &checksum);
+
+ if (checksum_idx)
+ *checksum_idx = checksum_index;
+
+ return calculated_crc == HexStringToInteger(checksum.c_str());
+}
+
+// Complex helpers built on top of other functions.
+
+bool ParseFinancialPingResponse(Product product, const char* response) {
+ // Update the last ping time irrespective of success.
+ FinancialPing::UpdateLastPingTime(product);
+ // Parse the ping response - update RLZs, clear events.
+ return ParsePingResponse(product, response);
+}
+
+bool SendFinancialPing(Product product, const AccessPoint* access_points,
+ const char* product_signature,
+ const char* product_brand,
+ const char* product_id, const char* product_lang,
+ bool exclude_machine_id) {
+ return SendFinancialPing(product, access_points, product_signature,
+ product_brand, product_id, product_lang,
+ exclude_machine_id, false);
+}
+
+
+bool SendFinancialPing(Product product, const AccessPoint* access_points,
+ const char* product_signature,
+ const char* product_brand,
+ const char* product_id, const char* product_lang,
+ bool exclude_machine_id,
+ const bool skip_time_check) {
+ // Create the financial ping request.
+ std::string request;
+ if (!FinancialPing::FormRequest(product, access_points, product_signature,
+ product_brand, product_id, product_lang,
+ exclude_machine_id, &request))
+ return false;
+
+ // Check if the time is right to ping.
+ if (!FinancialPing::IsPingTime(product, skip_time_check))
+ return false;
+
+ // Send out the ping, update the last ping time irrespective of success.
+ FinancialPing::UpdateLastPingTime(product);
+ std::string response;
+ if (!FinancialPing::PingServer(request.c_str(), &response))
+ return false;
+
+ // Parse the ping response - update RLZs, clear events.
+ return ParsePingResponse(product, response.c_str());
+}
+
+// TODO: Use something like RSA to make sure the response is
+// from a Google server.
+bool ParsePingResponse(Product product, const char* response) {
+ rlz_lib::ScopedRlzValueStoreLock lock;
+ rlz_lib::RlzValueStore* store = lock.GetStore();
+ if (!store || !store->HasAccess(rlz_lib::RlzValueStore::kWriteAccess))
+ return false;
+
+ std::string response_string(response);
+ int response_length = -1;
+ if (!IsPingResponseValid(response, &response_length))
+ return false;
+
+ if (0 == response_length)
+ return true; // Empty response - no parsing.
+
+ std::string events_variable;
+ std::string stateful_events_variable;
+ base::SStringPrintf(&events_variable, "%s: ", kEventsCgiVariable);
+ base::SStringPrintf(&stateful_events_variable, "%s: ",
+ kStatefulEventsCgiVariable);
+
+ int rlz_cgi_length = strlen(kRlzCgiVariable);
+
+ // Split response lines. Expected response format is lines of the form:
+ // rlzW1: 1R1_____en__252
+ int line_end_index = -1;
+ do {
+ int line_begin = line_end_index + 1;
+ line_end_index = response_string.find("\n", line_begin);
+
+ int line_end = line_end_index;
+ if (line_end < 0)
+ line_end = response_length;
+
+ if (line_end <= line_begin)
+ continue; // Empty line.
+
+ std::string response_line;
+ response_line = response_string.substr(line_begin, line_end - line_begin);
+
+ if (StartsWithASCII(response_line, kRlzCgiVariable, true)) { // An RLZ.
+ int separator_index = -1;
+ if ((separator_index = response_line.find(": ")) < 0)
+ continue; // Not a valid key-value pair.
+
+ // Get the access point.
+ std::string point_name =
+ response_line.substr(3, separator_index - rlz_cgi_length);
+ AccessPoint point = NO_ACCESS_POINT;
+ if (!GetAccessPointFromName(point_name.c_str(), &point) ||
+ point == NO_ACCESS_POINT)
+ continue; // Not a valid access point.
+
+ // Get the new RLZ.
+ std::string rlz_value(response_line.substr(separator_index + 2));
+ TrimWhitespaceASCII(rlz_value, TRIM_LEADING, &rlz_value);
+
+ int rlz_length = rlz_value.find_first_of("\r\n ");
+ if (rlz_length < 0)
+ rlz_length = rlz_value.size();
+
+ if (rlz_length > kMaxRlzLength)
+ continue; // Too long.
+
+ if (IsAccessPointSupported(point))
+ SetAccessPointRlz(point, rlz_value.substr(0, rlz_length).c_str());
+ } else if (StartsWithASCII(response_line, events_variable, true)) {
+ // Clear events which server parsed.
+ std::vector<ReturnedEvent> event_array;
+ GetEventsFromResponseString(response_line, events_variable, &event_array);
+ for (size_t i = 0; i < event_array.size(); ++i) {
+ ClearProductEvent(product, event_array[i].access_point,
+ event_array[i].event_type);
+ }
+ } else if (StartsWithASCII(response_line, stateful_events_variable, true)) {
+ // Record any stateful events the server send over.
+ std::vector<ReturnedEvent> event_array;
+ GetEventsFromResponseString(response_line, stateful_events_variable,
+ &event_array);
+ for (size_t i = 0; i < event_array.size(); ++i) {
+ RecordStatefulEvent(product, event_array[i].access_point,
+ event_array[i].event_type);
+ }
+ }
+ } while (line_end_index >= 0);
+
+#if defined(OS_WIN)
+ // Update the DCC in registry if needed.
+ SetMachineDealCodeFromPingResponse(response);
+#endif
+
+ return true;
+}
+
+bool GetPingParams(Product product, const AccessPoint* access_points,
+ char* cgi, size_t cgi_size) {
+ if (!cgi || cgi_size <= 0) {
+ ASSERT_STRING("GetPingParams: Invalid buffer");
+ return false;
+ }
+
+ cgi[0] = 0;
+
+ if (!access_points) {
+ ASSERT_STRING("GetPingParams: access_points is NULL");
+ return false;
+ }
+
+ // Add the RLZ Exchange Protocol version.
+ std::string cgi_string(kProtocolCgiArgument);
+
+ // Copy the &rlz= over.
+ base::StringAppendF(&cgi_string, "&%s=", kRlzCgiVariable);
+
+ {
+ // Now add each of the RLZ's. Keep the lock during all GetAccessPointRlz()
+ // calls below.
+ ScopedRlzValueStoreLock lock;
+ RlzValueStore* store = lock.GetStore();
+ if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
+ return false;
+ bool first_rlz = true; // comma before every RLZ but the first.
+ for (int i = 0; access_points[i] != NO_ACCESS_POINT; i++) {
+ char rlz[kMaxRlzLength + 1];
+ if (GetAccessPointRlz(access_points[i], rlz, arraysize(rlz))) {
+ const char* access_point = GetAccessPointName(access_points[i]);
+ if (!access_point)
+ continue;
+
+ base::StringAppendF(&cgi_string, "%s%s%s%s",
+ first_rlz ? "" : kRlzCgiSeparator,
+ access_point, kRlzCgiIndicator, rlz);
+ first_rlz = false;
+ }
+ }
+
+#if defined(OS_WIN)
+ // Report the DCC too if not empty. DCCs are windows-only.
+ char dcc[kMaxDccLength + 1];
+ dcc[0] = 0;
+ if (GetMachineDealCode(dcc, arraysize(dcc)) && dcc[0])
+ base::StringAppendF(&cgi_string, "&%s=%s", kDccCgiVariable, dcc);
+#endif
+ }
+
+ if (cgi_string.size() >= cgi_size)
+ return false;
+
+ strncpy(cgi, cgi_string.c_str(), cgi_size);
+ cgi[cgi_size - 1] = 0;
+
+ return true;
+}
+
+} // namespace rlz_lib
diff --git a/rlz/lib/rlz_lib.h b/rlz/lib/rlz_lib.h
new file mode 100644
index 0000000..9d4cf3b
--- /dev/null
+++ b/rlz/lib/rlz_lib.h
@@ -0,0 +1,335 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A library to manage RLZ information for access-points shared
+// across different client applications.
+//
+// All functions return true on success and false on error.
+// This implemenation is thread safe.
+
+
+#ifndef RLZ_LIB_RLZ_LIB_H_
+#define RLZ_LIB_RLZ_LIB_H_
+
+#include <stdio.h>
+#include <string>
+
+#include "build/build_config.h"
+
+#include "rlz/lib/rlz_enums.h"
+
+#if defined(OS_WIN)
+#define RLZ_LIB_API __cdecl
+#else
+#define RLZ_LIB_API
+#endif
+
+// Define one of
+// + RLZ_NETWORK_IMPLEMENTATION_WIN_INET: Uses win inet to send financial pings.
+// + RLZ_NETWORK_IMPLEMENTATION_CHROME_NET: Uses chrome's network stack to send
+// financial pings. rlz_lib::SetURLRequestContext() must be called before
+// any calls to SendFinancialPing().
+#if defined(RLZ_NETWORK_IMPLEMENTATION_WIN_INET) && \
+ defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
+#error Exactly one of RLZ_NETWORK_IMPLEMENTATION_WIN_INET and \
+ RLZ_NETWORK_IMPLEMENTATION_CHROME_NET should be defined.
+#endif
+#if !defined(RLZ_NETWORK_IMPLEMENTATION_WIN_INET) && \
+ !defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
+#if defined(OS_WIN)
+#define RLZ_NETWORK_IMPLEMENTATION_WIN_INET
+#else
+#define RLZ_NETWORK_IMPLEMENTATION_CHROME_NET
+#endif
+#endif
+
+#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
+namespace net {
+class URLRequestContextGetter;
+} // namespace net
+#endif
+
+namespace rlz_lib {
+
+class ScopedRlzValueStoreLock;
+
+// The maximum length of an access points RLZ in bytes.
+const int kMaxRlzLength = 64;
+// The maximum length of an access points RLZ in bytes.
+const int kMaxDccLength = 128;
+// The maximum length of a CGI string in bytes.
+const int kMaxCgiLength = 2048;
+// The maximum length of a ping response we will parse in bytes. If the response
+// is bigger, please break it up into separate calls.
+const int kMaxPingResponseLength = 0x4000; // 16K
+
+#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
+// Set the URLRequestContextGetter used by SendFinancialPing(). The IO message
+// loop returned by this context will be used for the IO done by
+// SendFinancialPing().
+bool RLZ_LIB_API SetURLRequestContext(net::URLRequestContextGetter* context);
+#endif
+
+// RLZ storage functions.
+
+// Get all the events reported by this product as a CGI string to append to
+// the daily ping.
+// Access: HKCU read.
+bool RLZ_LIB_API GetProductEventsAsCgi(Product product, char* unescaped_cgi,
+ size_t unescaped_cgi_size);
+
+// Records an RLZ event.
+// Some events can be product-independent (e.g: First search from home page),
+// and some can be access point independent (e.g. Pack installed). However,
+// product independent events must still include the product which cares about
+// that information being reported.
+// Access: HKCU write.
+bool RLZ_LIB_API RecordProductEvent(Product product, AccessPoint point,
+ Event event_id);
+
+// Clear an event reported by this product. This should be called after a
+// successful ping to the RLZ server.
+// Access: HKCU write.
+bool RLZ_LIB_API ClearProductEvent(Product product, AccessPoint point,
+ Event event_id);
+
+// Clear all reported events and recorded stateful events of this product.
+// This should be called on complete uninstallation of the product.
+// Access: HKCU write.
+bool RLZ_LIB_API ClearAllProductEvents(Product product);
+
+// Clears all product-specifc state from the RLZ registry.
+// Should be called during product uninstallation.
+// This removes outstanding product events, product financial ping times,
+// the product RLS argument (if any), and any RLZ's for access points being
+// uninstalled with the product.
+// access_points is an array terminated with NO_ACCESS_POINT.
+// IMPORTANT: These are the access_points the product is removing as part
+// of the uninstallation, not necessarily all the access points passed to
+// SendFinancialPing() and GetPingParams().
+// access_points can be NULL if no points are being uninstalled.
+// No return value - this is best effort. Will assert in debug mode on
+// failed attempts.
+// Access: HKCU write.
+void RLZ_LIB_API ClearProductState(Product product,
+ const AccessPoint* access_points);
+
+// Get the RLZ value of the access point. If the access point is not Google, the
+// RLZ will be the empty string and the function will return false.
+// Access: HKCU read.
+bool RLZ_LIB_API GetAccessPointRlz(AccessPoint point, char* rlz,
+ size_t rlz_size);
+
+// Set the RLZ for the access-point. Fails and asserts if called when the access
+// point is not set to Google.
+// new_rlz should come from a server-response. Client applications should not
+// create their own RLZ values.
+// Access: HKCU write.
+bool RLZ_LIB_API SetAccessPointRlz(AccessPoint point, const char* new_rlz);
+
+// Financial Server pinging functions.
+// These functions deal with pinging the RLZ financial server and parsing and
+// acting upon the response. Clients should SendFinancialPing() to avoid needing
+// these functions. However, these functions allow clients to split the various
+// parts of the pinging process up as needed (to avoid firewalls, etc).
+
+// Forms the HTTP request to send to the RLZ financial server.
+//
+// product : The product to ping for.
+// access_points : The access points this product affects. Array must be
+// terminated with NO_ACCESS_POINT.
+// product_signature : The signature sent with daily pings (e.g. swg, ietb)
+// product_brand : The brand of the pinging product, if any.
+// product_id : The product-specific installation ID (can be NULL).
+// product_lang : The language for the product (used to determine cohort).
+// exclude_machine_id : Whether the Machine ID should be explicitly excluded
+// based on the products privacy policy.
+// request : The buffer where the function returns the HTTP request.
+// request_buffer_size: The size of the request buffer in bytes. The buffer
+// size (kMaxCgiLength+1) is guaranteed to be enough.
+//
+// Access: HKCU read.
+bool RLZ_LIB_API FormFinancialPingRequest(Product product,
+ const AccessPoint* access_points,
+ const char* product_signature,
+ const char* product_brand,
+ const char* product_id,
+ const char* product_lang,
+ bool exclude_machine_id,
+ char* request,
+ size_t request_buffer_size);
+
+// Pings the financial server and returns the HTTP response. This will fail
+// if it is too early to ping the server since the last ping.
+//
+// If RLZ_NETWORK_IMPLEMENTATION_CHROME_NET is set, SetURLRequestContext() needs
+// to be called before calling this function.
+//
+// product : The product to ping for.
+// request : The HTTP request (for example, returned by
+// FormFinancialPingRequest).
+// response : The buffer in which the HTTP response is returned.
+// response_buffer_size : The size of the response buffer in bytes. The buffer
+// size (kMaxPingResponseLength+1) is enough for all
+// legitimate server responses (any response that is
+// bigger should be considered the same way as a general
+// network problem).
+//
+// Access: HKCU read.
+bool RLZ_LIB_API PingFinancialServer(Product product,
+ const char* request,
+ char* response,
+ size_t response_buffer_size);
+
+// Checks if a ping response is valid - ie. it has a checksum line which
+// is the CRC-32 checksum of the message uptil the checksum. If
+// checksum_idx is not NULL, it will get the index of the checksum, i.e. -
+// the effective end of the message.
+// Access: No restrictions.
+bool RLZ_LIB_API IsPingResponseValid(const char* response,
+ int* checksum_idx);
+
+
+// Complex helpers built on top of other functions.
+
+// Parses the responses from the financial server and updates product state
+// and access point RLZ's in registry. Like ParsePingResponse(), but also
+// updates the last ping time.
+// Access: HKCU write.
+bool RLZ_LIB_API ParseFinancialPingResponse(Product product,
+ const char* response);
+
+// Send the ping with RLZs and events to the PSO server.
+// This ping method should be called daily. (More frequent calls will fail).
+// Also, if there are no events, the call will succeed only once a week.
+//
+// If RLZ_NETWORK_IMPLEMENTATION_CHROME_NET is set, SetURLRequestContext() needs
+// to be called before calling this function.
+//
+// product : The product to ping for.
+// access_points : The access points this product affects. Array must be
+// terminated with NO_ACCESS_POINT.
+// product_signature : The signature sent with daily pings (e.g. swg, ietb)
+// product_brand : The brand of the pinging product, if any.
+// product_id : The product-specific installation ID (can be NULL).
+// product_lang : The language for the product (used to determine cohort).
+// exclude_machine_id : Whether the Machine ID should be explicitly excluded
+// based on the products privacy policy.
+//
+// Returns true on successful ping and response, false otherwise.
+// Access: HKCU write.
+bool RLZ_LIB_API SendFinancialPing(Product product,
+ const AccessPoint* access_points,
+ const char* product_signature,
+ const char* product_brand,
+ const char* product_id,
+ const char* product_lang,
+ bool exclude_machine_id);
+
+// An alternate implementations of SendFinancialPing with the same behavior,
+// except the caller can optionally choose to skip the timing check.
+bool RLZ_LIB_API SendFinancialPing(Product product,
+ const AccessPoint* access_points,
+ const char* product_signature,
+ const char* product_brand,
+ const char* product_id,
+ const char* product_lang,
+ bool exclude_machine_id,
+ const bool skip_time_check);
+
+// Parses RLZ related ping response information from the server.
+// Updates stored RLZ values and clears stored events accordingly.
+// Access: HKCU write.
+bool RLZ_LIB_API ParsePingResponse(Product product, const char* response);
+
+
+// Copies the events associated with the product and the RLZ's for each access
+// point in access_points into cgi. This string can be directly appended
+// to a ping (will need an & if not first paramter).
+// access_points must be an array of AccessPoints terminated with
+// NO_ACCESS_POINT.
+// Access: HKCU read.
+bool RLZ_LIB_API GetPingParams(Product product,
+ const AccessPoint* access_points,
+ char* unescaped_cgi, size_t unescaped_cgi_size);
+
+#if defined(OS_WIN)
+// OEM Deal confirmation storage functions. OEM Deals are windows-only.
+
+// Makes the OEM Deal Confirmation code writable by all users on the machine.
+// This should be called before calling SetMachineDealCode from a non-admin
+// account.
+// Access: HKLM write.
+bool RLZ_LIB_API CreateMachineState(void);
+
+// Set the OEM Deal Confirmation Code (DCC). This information is used for RLZ
+// initalization.
+// Access: HKLM write, or
+// HKCU read if rlz_lib::CreateMachineState() has been sucessfully called.
+bool RLZ_LIB_API SetMachineDealCode(const char* dcc);
+
+// Get the DCC cgi argument string to append to a daily ping.
+// Should be used only by OEM deal trackers. Applications should use the
+// GetMachineDealCode method which has an AccessPoint paramter.
+// Access: HKLM read.
+bool RLZ_LIB_API GetMachineDealCodeAsCgi(char* cgi, size_t cgi_size);
+
+// Get the DCC value stored in registry.
+// Should be used only by OEM deal trackers. Applications should use the
+// GetMachineDealCode method which has an AccessPoint paramter.
+// Access: HKLM read.
+bool RLZ_LIB_API GetMachineDealCode(char* dcc, size_t dcc_size);
+
+// Parses a ping response, checks if it is valid and sets the machine DCC
+// from the response. The ping must also contain the current DCC value in
+// order to be considered valid.
+// Access: HKLM write;
+// HKCU write if CreateMachineState() has been successfully called.
+bool RLZ_LIB_API SetMachineDealCodeFromPingResponse(const char* response);
+
+#endif
+
+// Segment RLZ persistence based on branding information.
+// All information for a given product is persisted under keys with the either
+// product's name or its access point's name. This assumes that only
+// one instance of the product is installed on the machine, and that only one
+// product brand is associated with it.
+//
+// In some cases, a given product may be using supplementary brands. The RLZ
+// information must be kept separately for each of these brands. To achieve
+// this segmentation, scope all RLZ library calls that deal with supplementary
+// brands within the lifetime of an rlz_lib::ProductBranding instance.
+//
+// For example, to record events for a supplementary brand, do the following:
+//
+// {
+// rlz_lib::SupplementaryBranding branding("AAAA");
+// // This call to RecordProductEvent is scoped to the AAAA brand.
+// rlz_lib::RecordProductEvent(rlz_lib::DESKTOP, rlz_lib::GD_DESKBAND,
+// rlz_lib::INSTALL);
+// }
+//
+// // This call to RecordProductEvent is not scoped to any supplementary brand.
+// rlz_lib::RecordProductEvent(rlz_lib::DESKTOP, rlz_lib::GD_DESKBAND,
+// rlz_lib::INSTALL);
+//
+// In particular, this affects the recording of stateful events and the sending
+// of financial pings. In the former case, a stateful event recorded while
+// scoped to a supplementary brand will be recorded again when scoped to a
+// different supplementary brand (or not scoped at all). In the latter case,
+// the time skip check is specific to each supplementary brand.
+class SupplementaryBranding {
+ public:
+ SupplementaryBranding(const char* brand);
+ ~SupplementaryBranding();
+
+ static const std::string& GetBrand();
+
+ private:
+ ScopedRlzValueStoreLock* lock_;
+};
+
+} // namespace rlz_lib
+
+#endif // RLZ_LIB_RLZ_LIB_H_
diff --git a/rlz/lib/rlz_lib_clear.cc b/rlz/lib/rlz_lib_clear.cc
new file mode 100644
index 0000000..0dd13a4
--- /dev/null
+++ b/rlz/lib/rlz_lib_clear.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The methods in this file belong conceptually to rlz_lib.cc. However, some
+// programs depend on rlz only to call ClearAllProductEvents(), so this file
+// contains this in fairly self-contained form to make it easier for linkers
+// to strip away most of rlz. In particular, this file should not reference any
+// symbols defined in financial_ping.cc.
+
+#include "rlz/lib/rlz_lib.h"
+
+#include "base/lazy_instance.h"
+#include "rlz/lib/assert.h"
+#include "rlz/lib/rlz_value_store.h"
+
+namespace rlz_lib {
+
+bool ClearAllProductEvents(Product product) {
+ rlz_lib::ScopedRlzValueStoreLock lock;
+ rlz_lib::RlzValueStore* store = lock.GetStore();
+ if (!store || !store->HasAccess(rlz_lib::RlzValueStore::kWriteAccess))
+ return false;
+
+ bool result;
+ result = store->ClearAllProductEvents(product);
+ result &= store->ClearAllStatefulEvents(product);
+ return result;
+}
+
+void ClearProductState(Product product, const AccessPoint* access_points) {
+ rlz_lib::ScopedRlzValueStoreLock lock;
+ rlz_lib::RlzValueStore* store = lock.GetStore();
+ if (!store || !store->HasAccess(rlz_lib::RlzValueStore::kWriteAccess))
+ return;
+
+ // Delete all product specific state.
+ VERIFY(ClearAllProductEvents(product));
+ VERIFY(store->ClearPingTime(product));
+
+ // Delete all RLZ's for access points being uninstalled.
+ if (access_points) {
+ for (int i = 0; access_points[i] != NO_ACCESS_POINT; i++) {
+ VERIFY(store->ClearAccessPointRlz(access_points[i]));
+ }
+ }
+
+ store->CollectGarbage();
+}
+
+static base::LazyInstance<std::string>::Leaky g_supplemental_branding;
+
+SupplementaryBranding::SupplementaryBranding(const char* brand)
+ : lock_(new ScopedRlzValueStoreLock) {
+ if (!lock_->GetStore())
+ return;
+
+ if (!g_supplemental_branding.Get().empty()) {
+ ASSERT_STRING("ProductBranding: existing brand is not empty");
+ return;
+ }
+
+ if (brand == NULL || brand[0] == 0) {
+ ASSERT_STRING("ProductBranding: new brand is empty");
+ return;
+ }
+
+ g_supplemental_branding.Get() = brand;
+}
+
+SupplementaryBranding::~SupplementaryBranding() {
+ if (lock_->GetStore())
+ g_supplemental_branding.Get().clear();
+ delete lock_;
+}
+
+// static
+const std::string& SupplementaryBranding::GetBrand() {
+ return g_supplemental_branding.Get();
+}
+
+} // namespace rlz_lib
diff --git a/rlz/lib/rlz_lib_test.cc b/rlz/lib/rlz_lib_test.cc
new file mode 100644
index 0000000..0f8cb4c
--- /dev/null
+++ b/rlz/lib/rlz_lib_test.cc
@@ -0,0 +1,824 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A test application for the RLZ library.
+//
+// These tests should not be executed on the build server:
+// - They assert for the failed cases.
+// - They modify machine state (registry).
+//
+// These tests require write access to HKLM and HKCU.
+//
+// The "GGLA" brand is used to test the normal code flow of the code, and the
+// "TEST" brand is used to test the supplementary brand code code flow.
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "rlz/lib/rlz_lib.h"
+#include "rlz/test/rlz_test_helpers.h"
+
+#if defined(OS_WIN)
+#include <Windows.h>
+#include "rlz/win/lib/machine_deal.h"
+#endif
+
+#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
+#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/threading/thread.h"
+#include "net/url_request/url_request_test_util.h"
+#endif
+
+
+class MachineDealCodeHelper
+#if defined(OS_WIN)
+ : public rlz_lib::MachineDealCode
+#endif
+ {
+ public:
+ static bool Clear() {
+#if defined(OS_WIN)
+ return rlz_lib::MachineDealCode::Clear();
+#else
+ return true;
+#endif
+ }
+
+ private:
+ MachineDealCodeHelper() {}
+ ~MachineDealCodeHelper() {}
+};
+
+class RlzLibTest : public RlzLibTestBase {
+};
+
+TEST_F(RlzLibTest, RecordProductEvent) {
+ char cgi_50[50];
+
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_STREQ("events=I7S", cgi_50);
+
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL));
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_STREQ("events=I7S,W1I", cgi_50);
+
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_STREQ("events=I7S,W1I", cgi_50);
+}
+
+TEST_F(RlzLibTest, ClearProductEvent) {
+ char cgi_50[50];
+
+ // Clear 1 of 1 events.
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_STREQ("events=I7S", cgi_50);
+ EXPECT_TRUE(rlz_lib::ClearProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_STREQ("", cgi_50);
+
+ // Clear 1 of 2 events.
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL));
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_STREQ("events=I7S,W1I", cgi_50);
+ EXPECT_TRUE(rlz_lib::ClearProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_STREQ("events=W1I", cgi_50);
+
+ // Clear a non-recorded event.
+ EXPECT_TRUE(rlz_lib::ClearProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IETB_SEARCH_BOX, rlz_lib::FIRST_SEARCH));
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_STREQ("events=W1I", cgi_50);
+}
+
+
+TEST_F(RlzLibTest, GetProductEventsAsCgi) {
+ char cgi_50[50];
+ char cgi_1[1];
+
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL));
+
+ EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_1, 1));
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_STREQ("events=I7S,W1I", cgi_50);
+}
+
+TEST_F(RlzLibTest, ClearAllAllProductEvents) {
+ char cgi_50[50];
+
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_STREQ("events=I7S", cgi_50);
+
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_STREQ("", cgi_50);
+}
+
+TEST_F(RlzLibTest, SetAccessPointRlz) {
+ char rlz_50[50];
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, ""));
+ EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_50, 50));
+ EXPECT_STREQ("", rlz_50);
+
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, "IeTbRlz"));
+ EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_50, 50));
+ EXPECT_STREQ("IeTbRlz", rlz_50);
+}
+
+TEST_F(RlzLibTest, GetAccessPointRlz) {
+ char rlz_1[1];
+ char rlz_50[50];
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, ""));
+ EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_1, 1));
+ EXPECT_STREQ("", rlz_1);
+
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, "IeTbRlz"));
+ EXPECT_FALSE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_1, 1));
+ EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_50, 50));
+ EXPECT_STREQ("IeTbRlz", rlz_50);
+}
+
+TEST_F(RlzLibTest, GetPingParams) {
+ MachineDealCodeHelper::Clear();
+
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX,
+ "TbRlzValue"));
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IE_HOME_PAGE, ""));
+
+ char cgi[2048];
+ rlz_lib::AccessPoint points[] =
+ {rlz_lib::IETB_SEARCH_BOX, rlz_lib::NO_ACCESS_POINT,
+ rlz_lib::NO_ACCESS_POINT};
+
+ EXPECT_TRUE(rlz_lib::GetPingParams(rlz_lib::TOOLBAR_NOTIFIER, points,
+ cgi, 2048));
+ EXPECT_STREQ("rep=2&rlz=T4:TbRlzValue", cgi);
+
+#if defined(OS_WIN)
+ EXPECT_TRUE(rlz_lib::MachineDealCode::Set("dcc_value"));
+#define DCC_PARAM "&dcc=dcc_value"
+#else
+#define DCC_PARAM ""
+#endif
+
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, ""));
+ EXPECT_TRUE(rlz_lib::GetPingParams(rlz_lib::TOOLBAR_NOTIFIER, points,
+ cgi, 2048));
+ EXPECT_STREQ("rep=2&rlz=T4:" DCC_PARAM, cgi);
+
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX,
+ "TbRlzValue"));
+ EXPECT_FALSE(rlz_lib::GetPingParams(rlz_lib::TOOLBAR_NOTIFIER, points,
+ cgi, 23 + strlen(DCC_PARAM)));
+ EXPECT_STREQ("", cgi);
+ EXPECT_TRUE(rlz_lib::GetPingParams(rlz_lib::TOOLBAR_NOTIFIER, points,
+ cgi, 24 + strlen(DCC_PARAM)));
+ EXPECT_STREQ("rep=2&rlz=T4:TbRlzValue" DCC_PARAM, cgi);
+
+ EXPECT_TRUE(GetAccessPointRlz(rlz_lib::IE_HOME_PAGE, cgi, 2048));
+ points[2] = rlz_lib::IE_HOME_PAGE;
+ EXPECT_TRUE(rlz_lib::GetPingParams(rlz_lib::TOOLBAR_NOTIFIER, points,
+ cgi, 2048));
+ EXPECT_STREQ("rep=2&rlz=T4:TbRlzValue" DCC_PARAM, cgi);
+}
+
+TEST_F(RlzLibTest, IsPingResponseValid) {
+ const char* kBadPingResponses[] = {
+ // No checksum.
+ "version: 3.0.914.7250\r\n"
+ "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n"
+ "launch-action: custom-action\r\n"
+ "launch-target: SearchWithGoogleUpdate.exe\r\n"
+ "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n"
+ "rlz: 1R1_____en__252\r\n"
+ "rlzXX: 1R1_____en__250\r\n",
+
+ // Invalid checksum.
+ "version: 3.0.914.7250\r\n"
+ "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n"
+ "launch-action: custom-action\r\n"
+ "launch-target: SearchWithGoogleUpdate.exe\r\n"
+ "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n"
+ "rlz: 1R1_____en__252\r\n"
+ "rlzXX: 1R1_____en__250\r\n"
+ "rlzT4 1T4_____en__251\r\n"
+ "rlzT4: 1T4_____en__252\r\n"
+ "rlz\r\n"
+ "crc32: B12CC79A",
+
+ // Misplaced checksum.
+ "version: 3.0.914.7250\r\n"
+ "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n"
+ "launch-action: custom-action\r\n"
+ "launch-target: SearchWithGoogleUpdate.exe\r\n"
+ "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n"
+ "rlz: 1R1_____en__252\r\n"
+ "rlzXX: 1R1_____en__250\r\n"
+ "crc32: B12CC79C\r\n"
+ "rlzT4 1T4_____en__251\r\n"
+ "rlzT4: 1T4_____en__252\r\n"
+ "rlz\r\n",
+
+ NULL
+ };
+
+ const char* kGoodPingResponses[] = {
+ "version: 3.0.914.7250\r\n"
+ "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n"
+ "launch-action: custom-action\r\n"
+ "launch-target: SearchWithGoogleUpdate.exe\r\n"
+ "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n"
+ "rlz: 1R1_____en__252\r\n"
+ "rlzXX: 1R1_____en__250\r\n"
+ "rlzT4 1T4_____en__251\r\n"
+ "rlzT4: 1T4_____en__252\r\n"
+ "rlz\r\n"
+ "crc32: D6FD55A3",
+
+ "version: 3.0.914.7250\r\n"
+ "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n"
+ "launch-action: custom-action\r\n"
+ "launch-target: SearchWithGoogleUpdate.exe\r\n"
+ "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n"
+ "rlz: 1R1_____en__252\r\n"
+ "rlzXX: 1R1_____en__250\r\n"
+ "rlzT4 1T4_____en__251\r\n"
+ "rlzT4: 1T4_____en__252\r\n"
+ "rlz\r\n"
+ "crc32: D6FD55A3\r\n"
+ "extradata: not checksummed",
+
+ NULL
+ };
+
+ for (int i = 0; kBadPingResponses[i]; i++)
+ EXPECT_FALSE(rlz_lib::IsPingResponseValid(kBadPingResponses[i], NULL));
+
+ for (int i = 0; kGoodPingResponses[i]; i++)
+ EXPECT_TRUE(rlz_lib::IsPingResponseValid(kGoodPingResponses[i], NULL));
+}
+
+TEST_F(RlzLibTest, ParsePingResponse) {
+ const char* kPingResponse =
+ "version: 3.0.914.7250\r\n"
+ "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n"
+ "launch-action: custom-action\r\n"
+ "launch-target: SearchWithGoogleUpdate.exe\r\n"
+ "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n"
+ "rlz: 1R1_____en__252\r\n" // Invalid RLZ - no access point.
+ "rlzXX: 1R1_____en__250\r\n" // Invalid RLZ - bad access point.
+ "rlzT4 1T4_____en__251\r\n" // Invalid RLZ - missing colon.
+ "rlzT4: 1T4_____en__252\r\n" // GoodRLZ.
+ "events: I7S,W1I\r\n" // Clear all events.
+ "rlz\r\n"
+ "dcc: dcc_value\r\n"
+ "crc32: F9070F81";
+
+#if defined(OS_WIN)
+ EXPECT_TRUE(rlz_lib::MachineDealCode::Set("dcc_value2"));
+#endif
+
+ // Record some product events to check that they get cleared.
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL));
+
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(
+ rlz_lib::IETB_SEARCH_BOX, "TbRlzValue"));
+
+ EXPECT_TRUE(rlz_lib::ParsePingResponse(rlz_lib::TOOLBAR_NOTIFIER,
+ kPingResponse));
+
+#if defined(OS_WIN)
+ EXPECT_TRUE(rlz_lib::MachineDealCode::Set("dcc_value"));
+#endif
+ EXPECT_TRUE(rlz_lib::ParsePingResponse(rlz_lib::TOOLBAR_NOTIFIER,
+ kPingResponse));
+
+ char value[50];
+ EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, value, 50));
+ EXPECT_STREQ("1T4_____en__252", value);
+ EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ value, 50));
+ EXPECT_STREQ("", value);
+
+ const char* kPingResponse2 =
+ "rlzT4: 1T4_____de__253 \r\n" // Good with extra spaces.
+ "crc32: 321334F5\r\n";
+ EXPECT_TRUE(rlz_lib::ParsePingResponse(rlz_lib::TOOLBAR_NOTIFIER,
+ kPingResponse2));
+ EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, value, 50));
+ EXPECT_STREQ("1T4_____de__253", value);
+
+ const char* kPingResponse3 =
+ "crc32: 0\r\n"; // Good RLZ - empty response.
+ EXPECT_TRUE(rlz_lib::ParsePingResponse(rlz_lib::TOOLBAR_NOTIFIER,
+ kPingResponse3));
+ EXPECT_STREQ("1T4_____de__253", value);
+}
+
+// Test whether a stateful event will only be sent in financial pings once.
+TEST_F(RlzLibTest, ParsePingResponseWithStatefulEvents) {
+ const char* kPingResponse =
+ "version: 3.0.914.7250\r\n"
+ "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n"
+ "launch-action: custom-action\r\n"
+ "launch-target: SearchWithGoogleUpdate.exe\r\n"
+ "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n"
+ "rlzT4: 1T4_____en__252\r\n" // GoodRLZ.
+ "events: I7S,W1I\r\n" // Clear all events.
+ "stateful-events: W1I\r\n" // W1I as an stateful event.
+ "rlz\r\n"
+ "dcc: dcc_value\r\n"
+ "crc32: 55191759";
+
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+
+ // Record some product events to check that they get cleared.
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL));
+
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(
+ rlz_lib::IETB_SEARCH_BOX, "TbRlzValue"));
+
+ EXPECT_TRUE(rlz_lib::ParsePingResponse(rlz_lib::TOOLBAR_NOTIFIER,
+ kPingResponse));
+
+ // Check all the events sent earlier are cleared.
+ char value[50];
+ EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ value, 50));
+ EXPECT_STREQ("", value);
+
+ // Record both events (one is stateless and the other is stateful) again.
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL));
+
+ // Check the stateful event won't be sent again while the stateless one will.
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ value, 50));
+ EXPECT_STREQ("events=I7S", value);
+
+ // Test that stateful events are cleared by ClearAllProductEvents(). After
+ // calling it, trying to record a stateful again should result in it being
+ // recorded again.
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL));
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ value, 50));
+ EXPECT_STREQ("events=W1I", value);
+}
+
+TEST_F(RlzLibTest, SendFinancialPing) {
+ // We don't really check a value or result in this test. All this does is
+ // attempt to ping the financial server, which you can verify in Fiddler.
+ // TODO: Make this a measurable test.
+
+#if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
+#if defined(OS_MACOSX)
+ base::mac::ScopedNSAutoreleasePool pool;
+#endif
+
+ base::Thread::Options options;
+ options.message_loop_type = MessageLoop::TYPE_IO;
+
+ base::Thread io_thread("rlz_unittest_io_thread");
+ ASSERT_TRUE(io_thread.StartWithOptions(options));
+
+ scoped_refptr<TestURLRequestContextGetter> context =
+ new TestURLRequestContextGetter(
+ io_thread.message_loop()->message_loop_proxy());
+ rlz_lib::SetURLRequestContext(context.get());
+
+ class URLRequestRAII {
+ public:
+ URLRequestRAII(net::URLRequestContextGetter* context) {
+ rlz_lib::SetURLRequestContext(context);
+ }
+ ~URLRequestRAII() {
+ rlz_lib::SetURLRequestContext(NULL);
+ }
+ };
+
+ URLRequestRAII set_context(context.get());
+#endif
+
+ MachineDealCodeHelper::Clear();
+#if defined(OS_WIN)
+ EXPECT_TRUE(rlz_lib::MachineDealCode::Set("dcc_value"));
+#endif
+
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX,
+ "TbRlzValue"));
+
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL));
+
+ rlz_lib::AccessPoint points[] =
+ {rlz_lib::IETB_SEARCH_BOX, rlz_lib::NO_ACCESS_POINT,
+ rlz_lib::NO_ACCESS_POINT};
+
+ std::string request;
+ rlz_lib::SendFinancialPing(rlz_lib::TOOLBAR_NOTIFIER, points,
+ "swg", "GGLA", "SwgProductId1234", "en-UK", false,
+ /*skip_time_check=*/true);
+}
+
+TEST_F(RlzLibTest, ClearProductState) {
+ MachineDealCodeHelper::Clear();
+
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX,
+ "TbRlzValue"));
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::GD_DESKBAND,
+ "GdbRlzValue"));
+
+ rlz_lib::AccessPoint points[] =
+ { rlz_lib::IETB_SEARCH_BOX, rlz_lib::NO_ACCESS_POINT };
+
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IETB_SEARCH_BOX, rlz_lib::INSTALL));
+
+ rlz_lib::AccessPoint points2[] =
+ { rlz_lib::IETB_SEARCH_BOX,
+ rlz_lib::GD_DESKBAND,
+ rlz_lib::NO_ACCESS_POINT };
+
+ char cgi[2048];
+ EXPECT_TRUE(rlz_lib::GetPingParams(rlz_lib::TOOLBAR_NOTIFIER, points2,
+ cgi, 2048));
+ EXPECT_STREQ("rep=2&rlz=T4:TbRlzValue,D1:GdbRlzValue", cgi);
+
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi, 2048));
+ std::string events(cgi);
+ EXPECT_LT(0u, events.find("I7S"));
+ EXPECT_LT(0u, events.find("T4I"));
+ EXPECT_LT(0u, events.find("T4R"));
+
+ rlz_lib::ClearProductState(rlz_lib::TOOLBAR_NOTIFIER, points);
+
+ EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX,
+ cgi, 2048));
+ EXPECT_STREQ("", cgi);
+ EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::GD_DESKBAND,
+ cgi, 2048));
+ EXPECT_STREQ("GdbRlzValue", cgi);
+
+ EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi, 2048));
+ EXPECT_STREQ("", cgi);
+}
+
+#if defined(OS_WIN)
+template<class T>
+class typed_buffer_ptr {
+ scoped_array<char> buffer_;
+
+ public:
+ typed_buffer_ptr() {
+ }
+
+ explicit typed_buffer_ptr(size_t size) : buffer_(new char[size]) {
+ }
+
+ void reset(size_t size) {
+ buffer_.reset(new char[size]);
+ }
+
+ operator T*() {
+ return reinterpret_cast<T*>(buffer_.get());
+ }
+};
+
+namespace rlz_lib {
+bool HasAccess(PSID sid, ACCESS_MASK access_mask, ACL* dacl);
+}
+
+bool EmptyAcl(ACL* acl) {
+ ACL_SIZE_INFORMATION info;
+ bool ret = GetAclInformation(acl, &info, sizeof(info), AclSizeInformation);
+ EXPECT_TRUE(ret);
+
+ for (DWORD i = 0; i < info.AceCount && ret; ++i) {
+ ret = DeleteAce(acl, 0);
+ EXPECT_TRUE(ret);
+ }
+
+ return ret;
+}
+
+TEST_F(RlzLibTest, HasAccess) {
+ // Create a SID that represents ALL USERS.
+ DWORD users_sid_size = SECURITY_MAX_SID_SIZE;
+ typed_buffer_ptr<SID> users_sid(users_sid_size);
+ CreateWellKnownSid(WinBuiltinUsersSid, NULL, users_sid, &users_sid_size);
+
+ // RLZ always asks for KEY_ALL_ACCESS access to the key. This is what we
+ // test here.
+
+ // No ACL mean no access.
+ EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, NULL));
+
+ // Create an ACL for these tests.
+ const DWORD kMaxAclSize = 1024;
+ typed_buffer_ptr<ACL> dacl(kMaxAclSize);
+ InitializeAcl(dacl, kMaxAclSize, ACL_REVISION);
+
+ // Empty DACL mean no access.
+ EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl));
+
+ // ACE without all needed privileges should mean no access.
+ EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_READ, users_sid));
+ EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl));
+
+ // ACE without all needed privileges should mean no access.
+ EXPECT_TRUE(EmptyAcl(dacl));
+ EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_WRITE, users_sid));
+ EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl));
+
+ // A deny ACE before an allow ACE should not give access.
+ EXPECT_TRUE(EmptyAcl(dacl));
+ EXPECT_TRUE(AddAccessDeniedAce(dacl, ACL_REVISION, KEY_ALL_ACCESS,
+ users_sid));
+ EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_ALL_ACCESS,
+ users_sid));
+ EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl));
+
+ // A deny ACE before an allow ACE should not give access.
+ EXPECT_TRUE(EmptyAcl(dacl));
+ EXPECT_TRUE(AddAccessDeniedAce(dacl, ACL_REVISION, KEY_READ, users_sid));
+ EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_ALL_ACCESS,
+ users_sid));
+ EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl));
+
+
+ // An allow ACE without all required bits should not give access.
+ EXPECT_TRUE(EmptyAcl(dacl));
+ EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_WRITE, users_sid));
+ EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl));
+
+ // An allow ACE with all required bits should give access.
+ EXPECT_TRUE(EmptyAcl(dacl));
+ EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_ALL_ACCESS,
+ users_sid));
+ EXPECT_TRUE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl));
+
+ // A deny ACE after an allow ACE should not give access.
+ EXPECT_TRUE(EmptyAcl(dacl));
+ EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_ALL_ACCESS,
+ users_sid));
+ EXPECT_TRUE(AddAccessDeniedAce(dacl, ACL_REVISION, KEY_READ, users_sid));
+ EXPECT_TRUE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl));
+
+ // An inherit-only allow ACE should not give access.
+ EXPECT_TRUE(EmptyAcl(dacl));
+ EXPECT_TRUE(AddAccessAllowedAceEx(dacl, ACL_REVISION, INHERIT_ONLY_ACE,
+ KEY_ALL_ACCESS, users_sid));
+ EXPECT_FALSE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl));
+
+ // An inherit-only deny ACE should not apply.
+ EXPECT_TRUE(EmptyAcl(dacl));
+ EXPECT_TRUE(AddAccessDeniedAceEx(dacl, ACL_REVISION, INHERIT_ONLY_ACE,
+ KEY_ALL_ACCESS, users_sid));
+ EXPECT_TRUE(AddAccessAllowedAce(dacl, ACL_REVISION, KEY_ALL_ACCESS,
+ users_sid));
+ EXPECT_TRUE(rlz_lib::HasAccess(users_sid, KEY_ALL_ACCESS, dacl));
+}
+#endif
+
+TEST_F(RlzLibTest, BrandingRecordProductEvent) {
+ // Don't run these tests if a supplementary brand is already in place. That
+ // way we can control the branding.
+ if (!rlz_lib::SupplementaryBranding::GetBrand().empty())
+ return;
+
+ char cgi_50[50];
+
+ // Record different events for the same product with diffrent branding, and
+ // make sure that the information remains separate.
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ {
+ rlz_lib::SupplementaryBranding branding("TEST");
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ }
+
+ // Test that recording events with the default brand and a supplementary
+ // brand don't overwrite each other.
+
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_STREQ("events=I7S", cgi_50);
+
+ {
+ rlz_lib::SupplementaryBranding branding("TEST");
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::INSTALL));
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_STREQ("events=I7I", cgi_50);
+ }
+
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ cgi_50, 50));
+ EXPECT_STREQ("events=I7S", cgi_50);
+}
+
+TEST_F(RlzLibTest, BrandingSetAccessPointRlz) {
+ // Don't run these tests if a supplementary brand is already in place. That
+ // way we can control the branding.
+ if (!rlz_lib::SupplementaryBranding::GetBrand().empty())
+ return;
+
+ char rlz_50[50];
+
+ // Test that setting RLZ strings with the default brand and a supplementary
+ // brand don't overwrite each other.
+
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, "IeTbRlz"));
+ EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_50, 50));
+ EXPECT_STREQ("IeTbRlz", rlz_50);
+
+ {
+ rlz_lib::SupplementaryBranding branding("TEST");
+
+ EXPECT_TRUE(rlz_lib::SetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, "SuppRlz"));
+ EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_50,
+ 50));
+ EXPECT_STREQ("SuppRlz", rlz_50);
+ }
+
+ EXPECT_TRUE(rlz_lib::GetAccessPointRlz(rlz_lib::IETB_SEARCH_BOX, rlz_50, 50));
+ EXPECT_STREQ("IeTbRlz", rlz_50);
+
+}
+
+TEST_F(RlzLibTest, BrandingWithStatefulEvents) {
+ // Don't run these tests if a supplementary brand is already in place. That
+ // way we can control the branding.
+ if (!rlz_lib::SupplementaryBranding::GetBrand().empty())
+ return;
+
+ const char* kPingResponse =
+ "version: 3.0.914.7250\r\n"
+ "url: http://www.corp.google.com/~av/45/opt/SearchWithGoogleUpdate.exe\r\n"
+ "launch-action: custom-action\r\n"
+ "launch-target: SearchWithGoogleUpdate.exe\r\n"
+ "signature: c08a3f4438e1442c4fe5678ee147cf6c5516e5d62bb64e\r\n"
+ "rlzT4: 1T4_____en__252\r\n" // GoodRLZ.
+ "events: I7S,W1I\r\n" // Clear all events.
+ "stateful-events: W1I\r\n" // W1I as an stateful event.
+ "rlz\r\n"
+ "dcc: dcc_value\r\n"
+ "crc32: 55191759";
+
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ {
+ rlz_lib::SupplementaryBranding branding("TEST");
+ EXPECT_TRUE(rlz_lib::ClearAllProductEvents(rlz_lib::TOOLBAR_NOTIFIER));
+ }
+
+ // Record some product events for the default and supplementary brand.
+ // Check that they get cleared only for the default brand.
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL));
+
+ {
+ rlz_lib::SupplementaryBranding branding("TEST");
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL));
+ }
+
+ EXPECT_TRUE(rlz_lib::ParsePingResponse(rlz_lib::TOOLBAR_NOTIFIER,
+ kPingResponse));
+
+ // Check all the events sent earlier are cleared only for default brand.
+ char value[50];
+ EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ value, 50));
+ EXPECT_STREQ("", value);
+
+ {
+ rlz_lib::SupplementaryBranding branding("TEST");
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ value, 50));
+ EXPECT_STREQ("events=I7S,W1I", value);
+ }
+
+ // Record both events (one is stateless and the other is stateful) again.
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+ EXPECT_TRUE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_HOME_PAGE, rlz_lib::INSTALL));
+
+ // Check the stateful event won't be sent again while the stateless one will.
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ value, 50));
+ EXPECT_STREQ("events=I7S", value);
+
+ {
+ rlz_lib::SupplementaryBranding branding("TEST");
+ EXPECT_TRUE(rlz_lib::ParsePingResponse(rlz_lib::TOOLBAR_NOTIFIER,
+ kPingResponse));
+
+ EXPECT_FALSE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ value, 50));
+ EXPECT_STREQ("", value);
+ }
+
+ EXPECT_TRUE(rlz_lib::GetProductEventsAsCgi(rlz_lib::TOOLBAR_NOTIFIER,
+ value, 50));
+ EXPECT_STREQ("events=I7S", value);
+}
+
+#if defined(OS_MACOSX)
+class ReadonlyRlzDirectoryTest : public RlzLibTestNoMachineState {
+ protected:
+ virtual void SetUp() OVERRIDE;
+};
+
+void ReadonlyRlzDirectoryTest::SetUp() {
+ RlzLibTestNoMachineState::SetUp();
+ // Make the rlz directory non-writeable.
+ chmod(temp_dir_.path().value().c_str(), 0500);
+}
+
+TEST_F(ReadonlyRlzDirectoryTest, WriteFails) {
+ // The rlz test runner runs every test twice: Once normally, and once with
+ // a SupplementaryBranding on the stack. In the latter case, the rlz lock
+ // has already been acquired before the rlz directory got changed to
+ // read-only, which makes this test pointless. So run it only in the first
+ // pass.
+ if (!rlz_lib::SupplementaryBranding::GetBrand().empty())
+ return;
+
+ EXPECT_FALSE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::SET_TO_GOOGLE));
+}
+
+// Regression test for http://crbug.com/121255
+TEST_F(ReadonlyRlzDirectoryTest, SupplementaryBrandingDoesNotCrash) {
+ // See the comment at the top of WriteFails.
+ if (!rlz_lib::SupplementaryBranding::GetBrand().empty())
+ return;
+
+ rlz_lib::SupplementaryBranding branding("TEST");
+ EXPECT_FALSE(rlz_lib::RecordProductEvent(rlz_lib::TOOLBAR_NOTIFIER,
+ rlz_lib::IE_DEFAULT_SEARCH, rlz_lib::INSTALL));
+}
+#endif
diff --git a/rlz/lib/rlz_value_store.h b/rlz/lib/rlz_value_store.h
new file mode 100644
index 0000000..d41421b
--- /dev/null
+++ b/rlz/lib/rlz_value_store.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RLZ_VALUE_STORE_H_
+#define RLZ_VALUE_STORE_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "rlz/lib/rlz_enums.h"
+
+#if defined(OS_WIN)
+#include "rlz/win/lib/lib_mutex.h"
+#endif
+
+#if defined(OS_MACOSX)
+#include "base/mac/scoped_nsautorelease_pool.h"
+#endif
+
+
+#include <string>
+#include <vector>
+
+class FilePath;
+
+namespace rlz_lib {
+
+// Abstracts away rlz's key value store. On windows, this usually writes to
+// the registry. On mac, it writes to an NSDefaults object.
+class RlzValueStore {
+ public:
+ virtual ~RlzValueStore() {}
+
+ enum AccessType { kReadAccess, kWriteAccess };
+ virtual bool HasAccess(AccessType type) = 0;
+
+ // Ping times.
+ virtual bool WritePingTime(Product product, int64 time) = 0;
+ virtual bool ReadPingTime(Product product, int64* time) = 0;
+ virtual bool ClearPingTime(Product product) = 0;
+
+ // Access point RLZs.
+ virtual bool WriteAccessPointRlz(AccessPoint access_point,
+ const char* new_rlz) = 0;
+ virtual bool ReadAccessPointRlz(AccessPoint access_point,
+ char* rlz, // At most kMaxRlzLength + 1 bytes
+ size_t rlz_size) = 0;
+ virtual bool ClearAccessPointRlz(AccessPoint access_point) = 0;
+
+ // Product events.
+ // Stores |event_rlz| for product |product| as product event.
+ virtual bool AddProductEvent(Product product, const char* event_rlz) = 0;
+ // Appends all events for |product| to |events|, in arbirtrary order.
+ virtual bool ReadProductEvents(Product product,
+ std::vector<std::string>* events) = 0;
+ // Removes the stored event |event_rlz| for |product| if it exists.
+ virtual bool ClearProductEvent(Product product, const char* event_rlz) = 0;
+ // Removes all stored product events for |product|.
+ virtual bool ClearAllProductEvents(Product product) = 0;
+
+ // Stateful events.
+ // Stores |event_rlz| for product |product| as stateful event.
+ virtual bool AddStatefulEvent(Product product, const char* event_rlz) = 0;
+ // Checks if |event_rlz| has been stored as stateful event for |product|.
+ virtual bool IsStatefulEvent(Product product, const char* event_rlz) = 0;
+ // Removes all stored stateful events for |product|.
+ virtual bool ClearAllStatefulEvents(Product product) = 0;
+
+ // Tells the value store to clean up unimportant internal data structures, for
+ // example empty registry folders, that might remain after clearing other
+ // data. Best-effort.
+ virtual void CollectGarbage() = 0;
+};
+
+// All methods of RlzValueStore must stays consistent even when accessed from
+// multiple threads in multiple processes. To enforce this through the type
+// system, the only way to access the RlzValueStore is through a
+// ScopedRlzValueStoreLock, which is a cross-process lock. It is active while
+// it is in scope. If the class fails to acquire a lock, its GetStore() method
+// returns NULL. If the lock fails to be acquired, it must not be taken
+// recursively. That is, all user code should look like this:
+// ScopedRlzValueStoreLock lock;
+// RlzValueStore* store = lock.GetStore();
+// if (!store)
+// return some_error_code;
+// ...
+class ScopedRlzValueStoreLock {
+ public:
+ ScopedRlzValueStoreLock();
+ ~ScopedRlzValueStoreLock();
+
+ // Returns a RlzValueStore protected by a cross-process lock, or NULL if the
+ // lock can't be obtained. The lifetime of the returned object is limited to
+ // the lifetime of this ScopedRlzValueStoreLock object.
+ RlzValueStore* GetStore();
+
+ private:
+ scoped_ptr<RlzValueStore> store_;
+#if defined(OS_WIN)
+ LibMutex lock_;
+#else
+ base::mac::ScopedNSAutoreleasePool autorelease_pool_;
+#endif
+};
+
+#if defined(OS_MACOSX)
+namespace testing {
+// Prefix |directory| to the path where the RLZ data file lives, for tests.
+void SetRlzStoreDirectory(const FilePath& directory);
+} // namespace testing
+#endif // defined(OS_MACOSX)
+
+
+} // namespace rlz_lib
+
+#endif // RLZ_VALUE_STORE_H_
diff --git a/rlz/lib/string_utils.cc b/rlz/lib/string_utils.cc
new file mode 100644
index 0000000..50206a8
--- /dev/null
+++ b/rlz/lib/string_utils.cc
@@ -0,0 +1,92 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// String manipulation functions used in the RLZ library.
+
+#include "rlz/lib/string_utils.h"
+
+#include "rlz/lib/assert.h"
+
+namespace rlz_lib {
+
+bool IsAscii(char letter) {
+ return (letter >= 0x0 && letter < 0x80);
+}
+
+bool GetHexValue(char letter, int* value) {
+ if (!value) {
+ ASSERT_STRING("GetHexValue: Invalid output paramter");
+ return false;
+ }
+ *value = 0;
+
+ if (letter >= '0' && letter <= '9')
+ *value = letter - '0';
+ else if (letter >= 'a' && letter <= 'f')
+ *value = (letter - 'a') + 0xA;
+ else if (letter >= 'A' && letter <= 'F')
+ *value = (letter - 'A') + 0xA;
+ else
+ return false;
+
+ return true;
+}
+
+int HexStringToInteger(const char* text) {
+ if (!text) {
+ ASSERT_STRING("HexStringToInteger: text is NULL.");
+ return 0;
+ }
+
+ int idx = 0;
+ // Ignore leading whitespace.
+ while (text[idx] == '\t' || text[idx] == ' ')
+ idx++;
+
+ if ((text[idx] == '0') &&
+ (text[idx + 1] == 'X' || text[idx + 1] == 'x'))
+ idx +=2; // String is of the form 0x...
+
+ int number = 0;
+ int digit = 0;
+ for (; text[idx] != '\0'; idx++) {
+ if (!GetHexValue(text[idx], &digit)) {
+ // Ignore trailing whitespaces, but assert on other trailing characters.
+ bool only_whitespaces = true;
+ while (only_whitespaces && text[idx])
+ only_whitespaces = (text[idx++] == ' ');
+ if (!only_whitespaces)
+ ASSERT_STRING("HexStringToInteger: text contains non-hex characters.");
+ return number;
+ }
+ number = (number << 4) | digit;
+ }
+
+ return number;
+}
+
+bool BytesToString(const unsigned char* data,
+ int data_len,
+ std::string* string) {
+ if (!string)
+ return false;
+
+ string->clear();
+ if (data_len < 1 || !data)
+ return false;
+
+ static const char kHex[] = "0123456789ABCDEF";
+
+ // Fix the buffer size to begin with to avoid repeated re-allocation.
+ string->resize(data_len * 2);
+ int index = data_len;
+ while (index--) {
+ string->at(2 * index) = kHex[data[index] >> 4]; // high digit
+ string->at(2 * index + 1) = kHex[data[index] & 0x0F]; // low digit
+ }
+
+ return true;
+}
+
+} // namespace rlz_lib
diff --git a/rlz/lib/string_utils.h b/rlz/lib/string_utils.h
new file mode 100644
index 0000000..6f64e58
--- /dev/null
+++ b/rlz/lib/string_utils.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// String manipulation functions used in the RLZ library.
+
+#ifndef RLZ_LIB_STRING_UTILS_H_
+#define RLZ_LIB_STRING_UTILS_H_
+
+#include <string>
+
+namespace rlz_lib {
+
+bool IsAscii(char letter);
+
+bool BytesToString(const unsigned char* data,
+ int data_len,
+ std::string* string);
+
+bool GetHexValue(char letter, int* value);
+
+int HexStringToInteger(const char* text);
+
+}; // namespace
+
+#endif // RLZ_LIB_STRING_UTILS_H_
diff --git a/rlz/lib/string_utils_unittest.cc b/rlz/lib/string_utils_unittest.cc
new file mode 100644
index 0000000..82a6edb
--- /dev/null
+++ b/rlz/lib/string_utils_unittest.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Unit test for string manipulation functions used in the RLZ library.
+
+#include "rlz/lib/string_utils.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+#include "rlz/lib/assert.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(StringUtilsUnittest, IsAscii) {
+ rlz_lib::SetExpectedAssertion("");
+
+ char bad_letters[] = {'\x80', '\xA0', '\xFF'};
+ for (size_t i = 0; i < arraysize(bad_letters); ++i)
+ EXPECT_FALSE(rlz_lib::IsAscii(bad_letters[i]));
+
+ char good_letters[] = {'A', '~', '\n', 0x7F, 0x00};
+ for (size_t i = 0; i < arraysize(good_letters); ++i)
+ EXPECT_TRUE(rlz_lib::IsAscii(good_letters[i]));
+}
+
+TEST(StringUtilsUnittest, HexStringToInteger) {
+ rlz_lib::SetExpectedAssertion("HexStringToInteger: text is NULL.");
+ EXPECT_EQ(0, rlz_lib::HexStringToInteger(NULL));
+
+ rlz_lib::SetExpectedAssertion("");
+ EXPECT_EQ(0, rlz_lib::HexStringToInteger(""));
+ EXPECT_EQ(0, rlz_lib::HexStringToInteger(" "));
+ EXPECT_EQ(0, rlz_lib::HexStringToInteger(" 0x "));
+ EXPECT_EQ(0, rlz_lib::HexStringToInteger(" 0x0 "));
+ EXPECT_EQ(0x12345, rlz_lib::HexStringToInteger("12345"));
+ EXPECT_EQ(0xa34Ed0, rlz_lib::HexStringToInteger("a34Ed0"));
+ EXPECT_EQ(0xa34Ed0, rlz_lib::HexStringToInteger("0xa34Ed0"));
+ EXPECT_EQ(0xa34Ed0, rlz_lib::HexStringToInteger(" 0xa34Ed0"));
+ EXPECT_EQ(0xa34Ed0, rlz_lib::HexStringToInteger("0xa34Ed0 "));
+ EXPECT_EQ(0xa34Ed0, rlz_lib::HexStringToInteger(" 0xa34Ed0 "));
+ EXPECT_EQ(0xa34Ed0, rlz_lib::HexStringToInteger(" 0x000a34Ed0 "));
+ EXPECT_EQ(0xa34Ed0, rlz_lib::HexStringToInteger(" 000a34Ed0 "));
+
+ rlz_lib::SetExpectedAssertion(
+ "HexStringToInteger: text contains non-hex characters.");
+ EXPECT_EQ(0x12ff, rlz_lib::HexStringToInteger("12ffg"));
+ EXPECT_EQ(0x12f, rlz_lib::HexStringToInteger("12f 121"));
+ EXPECT_EQ(0x12f, rlz_lib::HexStringToInteger("12f 121"));
+ EXPECT_EQ(0, rlz_lib::HexStringToInteger("g12f"));
+ EXPECT_EQ(0, rlz_lib::HexStringToInteger(" 0x0 \n"));
+
+ rlz_lib::SetExpectedAssertion("");
+}
+
+TEST(StringUtilsUnittest, TestBytesToString) {
+ unsigned char data[] = {0x1E, 0x00, 0x21, 0x67, 0xFF};
+ std::string result;
+
+ EXPECT_FALSE(rlz_lib::BytesToString(NULL, 5, &result));
+ EXPECT_FALSE(rlz_lib::BytesToString(data, 5, NULL));
+ EXPECT_FALSE(rlz_lib::BytesToString(NULL, 5, NULL));
+
+ EXPECT_TRUE(rlz_lib::BytesToString(data, 5, &result));
+ EXPECT_EQ(std::string("1E002167FF"), result);
+ EXPECT_TRUE(rlz_lib::BytesToString(data, 4, &result));
+ EXPECT_EQ(std::string("1E002167"), result);
+}