Home | History | Annotate | Download | only in lib
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 //
      5 // A library to manage RLZ information for access-points shared
      6 // across different client applications.
      7 
      8 #include "rlz/lib/rlz_lib.h"
      9 
     10 #include "base/strings/string_util.h"
     11 #include "base/strings/stringprintf.h"
     12 #include "rlz/lib/assert.h"
     13 #include "rlz/lib/crc32.h"
     14 #include "rlz/lib/financial_ping.h"
     15 #include "rlz/lib/lib_values.h"
     16 #include "rlz/lib/rlz_value_store.h"
     17 #include "rlz/lib/string_utils.h"
     18 
     19 namespace {
     20 
     21 // Event information returned from ping response.
     22 struct ReturnedEvent {
     23   rlz_lib::AccessPoint access_point;
     24   rlz_lib::Event event_type;
     25 };
     26 
     27 // Helper functions
     28 
     29 bool IsAccessPointSupported(rlz_lib::AccessPoint point) {
     30   switch (point) {
     31   case rlz_lib::NO_ACCESS_POINT:
     32   case rlz_lib::LAST_ACCESS_POINT:
     33 
     34   case rlz_lib::MOBILE_IDLE_SCREEN_BLACKBERRY:
     35   case rlz_lib::MOBILE_IDLE_SCREEN_WINMOB:
     36   case rlz_lib::MOBILE_IDLE_SCREEN_SYMBIAN:
     37     // These AP's are never available on Windows PCs.
     38     return false;
     39 
     40   case rlz_lib::IE_DEFAULT_SEARCH:
     41   case rlz_lib::IE_HOME_PAGE:
     42   case rlz_lib::IETB_SEARCH_BOX:
     43   case rlz_lib::QUICK_SEARCH_BOX:
     44   case rlz_lib::GD_DESKBAND:
     45   case rlz_lib::GD_SEARCH_GADGET:
     46   case rlz_lib::GD_WEB_SERVER:
     47   case rlz_lib::GD_OUTLOOK:
     48   case rlz_lib::CHROME_OMNIBOX:
     49   case rlz_lib::CHROME_HOME_PAGE:
     50     // TODO: Figure out when these settings are set to Google.
     51 
     52   default:
     53     return true;
     54   }
     55 }
     56 
     57 // Current RLZ can only use [a-zA-Z0-9_\-]
     58 // We will be more liberal and allow some additional chars, but not url meta
     59 // chars.
     60 bool IsGoodRlzChar(const char ch) {
     61   if (IsAsciiAlpha(ch) || IsAsciiDigit(ch))
     62     return true;
     63 
     64   switch (ch) {
     65     case '_':
     66     case '-':
     67     case '!':
     68     case '@':
     69     case '$':
     70     case '*':
     71     case '(':
     72     case ')':
     73     case ';':
     74     case '.':
     75     case '<':
     76     case '>':
     77     return true;
     78   }
     79 
     80   return false;
     81 }
     82 
     83 // This function will remove bad rlz chars and also limit the max rlz to some
     84 // reasonable size.  It also assumes that normalized_rlz is at least
     85 // kMaxRlzLength+1 long.
     86 void NormalizeRlz(const char* raw_rlz, char* normalized_rlz) {
     87   size_t index = 0;
     88   for (; raw_rlz[index] != 0 && index < rlz_lib::kMaxRlzLength; ++index) {
     89     char current = raw_rlz[index];
     90     if (IsGoodRlzChar(current)) {
     91       normalized_rlz[index] = current;
     92     } else {
     93       normalized_rlz[index] = '.';
     94     }
     95   }
     96 
     97   normalized_rlz[index] = 0;
     98 }
     99 
    100 void GetEventsFromResponseString(
    101     const std::string& response_line,
    102     const std::string& field_header,
    103     std::vector<ReturnedEvent>* event_array) {
    104   // Get the string of events.
    105   std::string events = response_line.substr(field_header.size());
    106   base::TrimWhitespaceASCII(events, base::TRIM_LEADING, &events);
    107 
    108   int events_length = events.find_first_of("\r\n ");
    109   if (events_length < 0)
    110     events_length = events.size();
    111   events = events.substr(0, events_length);
    112 
    113   // Break this up into individual events
    114   int event_end_index = -1;
    115   do {
    116     int event_begin = event_end_index + 1;
    117     event_end_index = events.find(rlz_lib::kEventsCgiSeparator, event_begin);
    118     int event_end = event_end_index;
    119     if (event_end < 0)
    120       event_end = events_length;
    121 
    122     std::string event_string = events.substr(event_begin,
    123                                              event_end - event_begin);
    124     if (event_string.size() != 3)  // 3 = 2(AP) + 1(E)
    125       continue;
    126 
    127     rlz_lib::AccessPoint point = rlz_lib::NO_ACCESS_POINT;
    128     rlz_lib::Event event = rlz_lib::INVALID_EVENT;
    129     if (!GetAccessPointFromName(event_string.substr(0, 2).c_str(), &point) ||
    130         point == rlz_lib::NO_ACCESS_POINT) {
    131       continue;
    132     }
    133 
    134     if (!GetEventFromName(event_string.substr(event_string.size() - 1).c_str(),
    135                           &event) || event == rlz_lib::INVALID_EVENT) {
    136       continue;
    137     }
    138 
    139     ReturnedEvent current_event = {point, event};
    140     event_array->push_back(current_event);
    141   } while (event_end_index >= 0);
    142 }
    143 
    144 // Event storage functions.
    145 bool RecordStatefulEvent(rlz_lib::Product product, rlz_lib::AccessPoint point,
    146                          rlz_lib::Event event) {
    147   rlz_lib::ScopedRlzValueStoreLock lock;
    148   rlz_lib::RlzValueStore* store = lock.GetStore();
    149   if (!store || !store->HasAccess(rlz_lib::RlzValueStore::kWriteAccess))
    150     return false;
    151 
    152   // Write the new event to the value store.
    153   const char* point_name = GetAccessPointName(point);
    154   const char* event_name = GetEventName(event);
    155   if (!point_name || !event_name)
    156     return false;
    157 
    158   if (!point_name[0] || !event_name[0])
    159     return false;
    160 
    161   std::string new_event_value;
    162   base::StringAppendF(&new_event_value, "%s%s", point_name, event_name);
    163   return store->AddStatefulEvent(product, new_event_value.c_str());
    164 }
    165 
    166 bool GetProductEventsAsCgiHelper(rlz_lib::Product product, char* cgi,
    167                                  size_t cgi_size,
    168                                  rlz_lib::RlzValueStore* store) {
    169   // Prepend the CGI param key to the buffer.
    170   std::string cgi_arg;
    171   base::StringAppendF(&cgi_arg, "%s=", rlz_lib::kEventsCgiVariable);
    172   if (cgi_size <= cgi_arg.size())
    173     return false;
    174 
    175   size_t index;
    176   for (index = 0; index < cgi_arg.size(); ++index)
    177     cgi[index] = cgi_arg[index];
    178 
    179   // Read stored events.
    180   std::vector<std::string> events;
    181   if (!store->ReadProductEvents(product, &events))
    182     return false;
    183 
    184   // Append the events to the buffer.
    185   size_t num_values = 0;
    186 
    187   for (num_values = 0; num_values < events.size(); ++num_values) {
    188     cgi[index] = '\0';
    189 
    190     int divider = num_values > 0 ? 1 : 0;
    191     int size = cgi_size - (index + divider);
    192     if (size <= 0)
    193       return cgi_size >= (rlz_lib::kMaxCgiLength + 1);
    194 
    195     strncpy(cgi + index + divider, events[num_values].c_str(), size);
    196     if (divider)
    197       cgi[index] = rlz_lib::kEventsCgiSeparator;
    198 
    199     index += std::min((int)events[num_values].length(), size) + divider;
    200   }
    201 
    202   cgi[index] = '\0';
    203 
    204   return num_values > 0;
    205 }
    206 
    207 }  // namespace
    208 
    209 namespace rlz_lib {
    210 
    211 #if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
    212 bool SetURLRequestContext(net::URLRequestContextGetter* context) {
    213   return FinancialPing::SetURLRequestContext(context);
    214 }
    215 #endif
    216 
    217 bool GetProductEventsAsCgi(Product product, char* cgi, size_t cgi_size) {
    218   if (!cgi || cgi_size <= 0) {
    219     ASSERT_STRING("GetProductEventsAsCgi: Invalid buffer");
    220     return false;
    221   }
    222 
    223   cgi[0] = 0;
    224 
    225   ScopedRlzValueStoreLock lock;
    226   RlzValueStore* store = lock.GetStore();
    227   if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
    228     return false;
    229 
    230   size_t size_local = std::min(
    231       static_cast<size_t>(kMaxCgiLength + 1), cgi_size);
    232   bool result = GetProductEventsAsCgiHelper(product, cgi, size_local, store);
    233 
    234   if (!result) {
    235     ASSERT_STRING("GetProductEventsAsCgi: Possibly insufficient buffer size");
    236     cgi[0] = 0;
    237     return false;
    238   }
    239 
    240   return true;
    241 }
    242 
    243 bool RecordProductEvent(Product product, AccessPoint point, Event event) {
    244   ScopedRlzValueStoreLock lock;
    245   RlzValueStore* store = lock.GetStore();
    246   if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
    247     return false;
    248 
    249   // Get this event's value.
    250   const char* point_name = GetAccessPointName(point);
    251   const char* event_name = GetEventName(event);
    252   if (!point_name || !event_name)
    253     return false;
    254 
    255   if (!point_name[0] || !event_name[0])
    256     return false;
    257 
    258   std::string new_event_value;
    259   base::StringAppendF(&new_event_value, "%s%s", point_name, event_name);
    260 
    261   // Check whether this event is a stateful event. If so, don't record it.
    262   if (store->IsStatefulEvent(product, new_event_value.c_str())) {
    263     // For a stateful event we skip recording, this function is also
    264     // considered successful.
    265     return true;
    266   }
    267 
    268   // Write the new event to the value store.
    269   return store->AddProductEvent(product, new_event_value.c_str());
    270 }
    271 
    272 bool ClearProductEvent(Product product, AccessPoint point, Event event) {
    273   ScopedRlzValueStoreLock lock;
    274   RlzValueStore* store = lock.GetStore();
    275   if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
    276     return false;
    277 
    278   // Get the event's value store value and delete it.
    279   const char* point_name = GetAccessPointName(point);
    280   const char* event_name = GetEventName(event);
    281   if (!point_name || !event_name)
    282     return false;
    283 
    284   if (!point_name[0] || !event_name[0])
    285     return false;
    286 
    287   std::string event_value;
    288   base::StringAppendF(&event_value, "%s%s", point_name, event_name);
    289   return store->ClearProductEvent(product, event_value.c_str());
    290 }
    291 
    292 // RLZ storage functions.
    293 
    294 bool GetAccessPointRlz(AccessPoint point, char* rlz, size_t rlz_size) {
    295   if (!rlz || rlz_size <= 0) {
    296     ASSERT_STRING("GetAccessPointRlz: Invalid buffer");
    297     return false;
    298   }
    299 
    300   rlz[0] = 0;
    301 
    302   ScopedRlzValueStoreLock lock;
    303   RlzValueStore* store = lock.GetStore();
    304   if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
    305     return false;
    306 
    307   if (!IsAccessPointSupported(point))
    308     return false;
    309 
    310   return store->ReadAccessPointRlz(point, rlz, rlz_size);
    311 }
    312 
    313 bool SetAccessPointRlz(AccessPoint point, const char* new_rlz) {
    314   ScopedRlzValueStoreLock lock;
    315   RlzValueStore* store = lock.GetStore();
    316   if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
    317     return false;
    318 
    319   if (!new_rlz) {
    320     ASSERT_STRING("SetAccessPointRlz: Invalid buffer");
    321     return false;
    322   }
    323 
    324   // Return false if the access point is not set to Google.
    325   if (!IsAccessPointSupported(point)) {
    326     ASSERT_STRING(("SetAccessPointRlz: "
    327                 "Cannot set RLZ for unsupported access point."));
    328     return false;
    329   }
    330 
    331   // Verify the RLZ length.
    332   size_t rlz_length = strlen(new_rlz);
    333   if (rlz_length > kMaxRlzLength) {
    334     ASSERT_STRING("SetAccessPointRlz: RLZ length is exceeds max allowed.");
    335     return false;
    336   }
    337 
    338   char normalized_rlz[kMaxRlzLength + 1];
    339   NormalizeRlz(new_rlz, normalized_rlz);
    340   VERIFY(strlen(new_rlz) == rlz_length);
    341 
    342   // Setting RLZ to empty == clearing.
    343   if (normalized_rlz[0] == 0)
    344     return store->ClearAccessPointRlz(point);
    345   return store->WriteAccessPointRlz(point, normalized_rlz);
    346 }
    347 
    348 // Financial Server pinging functions.
    349 
    350 bool FormFinancialPingRequest(Product product, const AccessPoint* access_points,
    351                               const char* product_signature,
    352                               const char* product_brand,
    353                               const char* product_id,
    354                               const char* product_lang,
    355                               bool exclude_machine_id,
    356                               char* request, size_t request_buffer_size) {
    357   if (!request || request_buffer_size == 0)
    358     return false;
    359 
    360   request[0] = 0;
    361 
    362   std::string request_string;
    363   if (!FinancialPing::FormRequest(product, access_points, product_signature,
    364                                   product_brand, product_id, product_lang,
    365                                   exclude_machine_id, &request_string))
    366     return false;
    367 
    368   if (request_string.size() >= request_buffer_size)
    369     return false;
    370 
    371   strncpy(request, request_string.c_str(), request_buffer_size);
    372   request[request_buffer_size - 1] = 0;
    373   return true;
    374 }
    375 
    376 bool PingFinancialServer(Product product, const char* request, char* response,
    377                          size_t response_buffer_size) {
    378   if (!response || response_buffer_size == 0)
    379     return false;
    380   response[0] = 0;
    381 
    382   // Check if the time is right to ping.
    383   if (!FinancialPing::IsPingTime(product, false))
    384     return false;
    385 
    386   // Send out the ping.
    387   std::string response_string;
    388   if (!FinancialPing::PingServer(request, &response_string))
    389     return false;
    390 
    391   if (response_string.size() >= response_buffer_size)
    392     return false;
    393 
    394   strncpy(response, response_string.c_str(), response_buffer_size);
    395   response[response_buffer_size - 1] = 0;
    396   return true;
    397 }
    398 
    399 bool IsPingResponseValid(const char* response, int* checksum_idx) {
    400   if (!response || !response[0])
    401     return false;
    402 
    403   if (checksum_idx)
    404     *checksum_idx = -1;
    405 
    406   if (strlen(response) > kMaxPingResponseLength) {
    407     ASSERT_STRING("IsPingResponseValid: response is too long to parse.");
    408     return false;
    409   }
    410 
    411   // Find the checksum line.
    412   std::string response_string(response);
    413 
    414   std::string checksum_param("\ncrc32: ");
    415   int calculated_crc;
    416   int checksum_index = response_string.find(checksum_param);
    417   if (checksum_index >= 0) {
    418     // Calculate checksum of message preceeding checksum line.
    419     // (+ 1 to include the \n)
    420     std::string message(response_string.substr(0, checksum_index + 1));
    421     if (!Crc32(message.c_str(), &calculated_crc))
    422       return false;
    423   } else {
    424     checksum_param = "crc32: ";  // Empty response case.
    425     if (!StartsWithASCII(response_string, checksum_param, true))
    426       return false;
    427 
    428     checksum_index = 0;
    429     if (!Crc32("", &calculated_crc))
    430       return false;
    431   }
    432 
    433   // Find the checksum value on the response.
    434   int checksum_end = response_string.find("\n", checksum_index + 1);
    435   if (checksum_end < 0)
    436     checksum_end = response_string.size();
    437 
    438   int checksum_begin = checksum_index + checksum_param.size();
    439   std::string checksum = response_string.substr(checksum_begin,
    440       checksum_end - checksum_begin + 1);
    441   base::TrimWhitespaceASCII(checksum, base::TRIM_ALL, &checksum);
    442 
    443   if (checksum_idx)
    444     *checksum_idx = checksum_index;
    445 
    446   return calculated_crc == HexStringToInteger(checksum.c_str());
    447 }
    448 
    449 // Complex helpers built on top of other functions.
    450 
    451 bool ParseFinancialPingResponse(Product product, const char* response) {
    452   // Update the last ping time irrespective of success.
    453   FinancialPing::UpdateLastPingTime(product);
    454   // Parse the ping response - update RLZs, clear events.
    455   return ParsePingResponse(product, response);
    456 }
    457 
    458 bool SendFinancialPing(Product product, const AccessPoint* access_points,
    459                        const char* product_signature,
    460                        const char* product_brand,
    461                        const char* product_id, const char* product_lang,
    462                        bool exclude_machine_id) {
    463   return SendFinancialPing(product, access_points, product_signature,
    464                            product_brand, product_id, product_lang,
    465                            exclude_machine_id, false);
    466 }
    467 
    468 
    469 bool SendFinancialPing(Product product, const AccessPoint* access_points,
    470                        const char* product_signature,
    471                        const char* product_brand,
    472                        const char* product_id, const char* product_lang,
    473                        bool exclude_machine_id,
    474                        const bool skip_time_check) {
    475   // Create the financial ping request.
    476   std::string request;
    477   if (!FinancialPing::FormRequest(product, access_points, product_signature,
    478                                   product_brand, product_id, product_lang,
    479                                   exclude_machine_id, &request))
    480     return false;
    481 
    482   // Check if the time is right to ping.
    483   if (!FinancialPing::IsPingTime(product, skip_time_check))
    484     return false;
    485 
    486   // Send out the ping, update the last ping time irrespective of success.
    487   FinancialPing::UpdateLastPingTime(product);
    488   std::string response;
    489   if (!FinancialPing::PingServer(request.c_str(), &response))
    490     return false;
    491 
    492   // Parse the ping response - update RLZs, clear events.
    493   return ParsePingResponse(product, response.c_str());
    494 }
    495 
    496 // TODO: Use something like RSA to make sure the response is
    497 // from a Google server.
    498 bool ParsePingResponse(Product product, const char* response) {
    499   rlz_lib::ScopedRlzValueStoreLock lock;
    500   rlz_lib::RlzValueStore* store = lock.GetStore();
    501   if (!store || !store->HasAccess(rlz_lib::RlzValueStore::kWriteAccess))
    502     return false;
    503 
    504   std::string response_string(response);
    505   int response_length = -1;
    506   if (!IsPingResponseValid(response, &response_length))
    507     return false;
    508 
    509   if (0 == response_length)
    510     return true;  // Empty response - no parsing.
    511 
    512   std::string events_variable;
    513   std::string stateful_events_variable;
    514   base::SStringPrintf(&events_variable, "%s: ", kEventsCgiVariable);
    515   base::SStringPrintf(&stateful_events_variable, "%s: ",
    516                       kStatefulEventsCgiVariable);
    517 
    518   int rlz_cgi_length = strlen(kRlzCgiVariable);
    519 
    520   // Split response lines. Expected response format is lines of the form:
    521   // rlzW1: 1R1_____en__252
    522   int line_end_index = -1;
    523   do {
    524     int line_begin = line_end_index + 1;
    525     line_end_index = response_string.find("\n", line_begin);
    526 
    527     int line_end = line_end_index;
    528     if (line_end < 0)
    529       line_end = response_length;
    530 
    531     if (line_end <= line_begin)
    532       continue;  // Empty line.
    533 
    534     std::string response_line;
    535     response_line = response_string.substr(line_begin, line_end - line_begin);
    536 
    537     if (StartsWithASCII(response_line, kRlzCgiVariable, true)) {  // An RLZ.
    538       int separator_index = -1;
    539       if ((separator_index = response_line.find(": ")) < 0)
    540         continue;  // Not a valid key-value pair.
    541 
    542       // Get the access point.
    543       std::string point_name =
    544         response_line.substr(3, separator_index - rlz_cgi_length);
    545       AccessPoint point = NO_ACCESS_POINT;
    546       if (!GetAccessPointFromName(point_name.c_str(), &point) ||
    547           point == NO_ACCESS_POINT)
    548         continue;  // Not a valid access point.
    549 
    550       // Get the new RLZ.
    551       std::string rlz_value(response_line.substr(separator_index + 2));
    552       base::TrimWhitespaceASCII(rlz_value, base::TRIM_LEADING, &rlz_value);
    553 
    554       size_t rlz_length = rlz_value.find_first_of("\r\n ");
    555       if (rlz_length == std::string::npos)
    556         rlz_length = rlz_value.size();
    557 
    558       if (rlz_length > kMaxRlzLength)
    559         continue;  // Too long.
    560 
    561       if (IsAccessPointSupported(point))
    562         SetAccessPointRlz(point, rlz_value.substr(0, rlz_length).c_str());
    563     } else if (StartsWithASCII(response_line, events_variable, true)) {
    564       // Clear events which server parsed.
    565       std::vector<ReturnedEvent> event_array;
    566       GetEventsFromResponseString(response_line, events_variable, &event_array);
    567       for (size_t i = 0; i < event_array.size(); ++i) {
    568         ClearProductEvent(product, event_array[i].access_point,
    569                           event_array[i].event_type);
    570       }
    571     } else if (StartsWithASCII(response_line, stateful_events_variable, true)) {
    572       // Record any stateful events the server send over.
    573       std::vector<ReturnedEvent> event_array;
    574       GetEventsFromResponseString(response_line, stateful_events_variable,
    575                                   &event_array);
    576       for (size_t i = 0; i < event_array.size(); ++i) {
    577         RecordStatefulEvent(product, event_array[i].access_point,
    578                             event_array[i].event_type);
    579       }
    580     }
    581   } while (line_end_index >= 0);
    582 
    583 #if defined(OS_WIN)
    584   // Update the DCC in registry if needed.
    585   SetMachineDealCodeFromPingResponse(response);
    586 #endif
    587 
    588   return true;
    589 }
    590 
    591 bool GetPingParams(Product product, const AccessPoint* access_points,
    592                    char* cgi, size_t cgi_size) {
    593   if (!cgi || cgi_size <= 0) {
    594     ASSERT_STRING("GetPingParams: Invalid buffer");
    595     return false;
    596   }
    597 
    598   cgi[0] = 0;
    599 
    600   if (!access_points) {
    601     ASSERT_STRING("GetPingParams: access_points is NULL");
    602     return false;
    603   }
    604 
    605   // Add the RLZ Exchange Protocol version.
    606   std::string cgi_string(kProtocolCgiArgument);
    607 
    608   // Copy the &rlz= over.
    609   base::StringAppendF(&cgi_string, "&%s=", kRlzCgiVariable);
    610 
    611   {
    612     // Now add each of the RLZ's. Keep the lock during all GetAccessPointRlz()
    613     // calls below.
    614     ScopedRlzValueStoreLock lock;
    615     RlzValueStore* store = lock.GetStore();
    616     if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
    617       return false;
    618     bool first_rlz = true;  // comma before every RLZ but the first.
    619     for (int i = 0; access_points[i] != NO_ACCESS_POINT; i++) {
    620       char rlz[kMaxRlzLength + 1];
    621       if (GetAccessPointRlz(access_points[i], rlz, arraysize(rlz))) {
    622         const char* access_point = GetAccessPointName(access_points[i]);
    623         if (!access_point)
    624           continue;
    625 
    626         base::StringAppendF(&cgi_string, "%s%s%s%s",
    627                             first_rlz ? "" : kRlzCgiSeparator,
    628                             access_point, kRlzCgiIndicator, rlz);
    629         first_rlz = false;
    630       }
    631     }
    632 
    633 #if defined(OS_WIN)
    634     // Report the DCC too if not empty. DCCs are windows-only.
    635     char dcc[kMaxDccLength + 1];
    636     dcc[0] = 0;
    637     if (GetMachineDealCode(dcc, arraysize(dcc)) && dcc[0])
    638       base::StringAppendF(&cgi_string, "&%s=%s", kDccCgiVariable, dcc);
    639 #endif
    640   }
    641 
    642   if (cgi_string.size() >= cgi_size)
    643     return false;
    644 
    645   strncpy(cgi, cgi_string.c_str(), cgi_size);
    646   cgi[cgi_size - 1] = 0;
    647 
    648   return true;
    649 }
    650 
    651 }  // namespace rlz_lib
    652