|  | // Copyright 2015 The Weave 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 "src/privet/privet_handler.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <memory> | 
|  | #include <set> | 
|  | #include <string> | 
|  | #include <utility> | 
|  |  | 
|  | #include <base/bind.h> | 
|  | #include <base/location.h> | 
|  | #include <base/strings/stringprintf.h> | 
|  | #include <base/values.h> | 
|  | #include <weave/enum_to_string.h> | 
|  | #include <weave/provider/task_runner.h> | 
|  |  | 
|  | #include "src/config.h" | 
|  | #include "src/http_constants.h" | 
|  | #include "src/privet/cloud_delegate.h" | 
|  | #include "src/privet/constants.h" | 
|  | #include "src/privet/device_delegate.h" | 
|  | #include "src/privet/device_ui_kind.h" | 
|  | #include "src/privet/security_delegate.h" | 
|  | #include "src/privet/wifi_delegate.h" | 
|  | #include "src/string_utils.h" | 
|  | #include "src/utils.h" | 
|  |  | 
|  | namespace weave { | 
|  | namespace privet { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const char kInfoVersionKey[] = "version"; | 
|  | const char kInfoVersionValue[] = "3.0"; | 
|  |  | 
|  | const char kNameKey[] = "name"; | 
|  | const char kDescrptionKey[] = "description"; | 
|  | const char kLocationKey[] = "location"; | 
|  |  | 
|  | const char kGcdKey[] = "gcd"; | 
|  | const char kWifiKey[] = "wifi"; | 
|  | const char kStatusKey[] = "status"; | 
|  | const char kErrorKey[] = "error"; | 
|  | const char kCryptoKey[] = "crypto"; | 
|  | const char kStatusErrorValue[] = "error"; | 
|  |  | 
|  | const char kInfoIdKey[] = "id"; | 
|  | const char kInfoServicesKey[] = "services"; | 
|  |  | 
|  | const char kInfoEndpointsKey[] = "endpoints"; | 
|  | const char kInfoEndpointsHttpPortKey[] = "httpPort"; | 
|  | const char kInfoEndpointsHttpUpdatePortKey[] = "httpUpdatesPort"; | 
|  | const char kInfoEndpointsHttpsPortKey[] = "httpsPort"; | 
|  | const char kInfoEndpointsHttpsUpdatePortKey[] = "httpsUpdatesPort"; | 
|  |  | 
|  | const char kInfoModelIdKey[] = "modelManifestId"; | 
|  | const char kInfoModelManifestKey[] = "basicModelManifest"; | 
|  | const char kInfoManifestUiDeviceKind[] = "uiDeviceKind"; | 
|  | const char kInfoManifestOemName[] = "oemName"; | 
|  | const char kInfoManifestModelName[] = "modelName"; | 
|  |  | 
|  | const char kInfoAuthenticationKey[] = "authentication"; | 
|  |  | 
|  | const char kInfoAuthAnonymousMaxScopeKey[] = "anonymousMaxScope"; | 
|  |  | 
|  | const char kInfoWifiCapabilitiesKey[] = "capabilities"; | 
|  | const char kInfoWifiSsidKey[] = "ssid"; | 
|  | const char kInfoWifiHostedSsidKey[] = "hostedSsid"; | 
|  | const char kInfoTimeKey[] = "time"; | 
|  | const char kInfoSessionIdKey[] = "sessionId"; | 
|  |  | 
|  | const char kPairingKey[] = "pairing"; | 
|  | const char kPairingSessionIdKey[] = "sessionId"; | 
|  | const char kPairingDeviceCommitmentKey[] = "deviceCommitment"; | 
|  | const char kPairingClientCommitmentKey[] = "clientCommitment"; | 
|  | const char kPairingFingerprintKey[] = "certFingerprint"; | 
|  | const char kPairingSignatureKey[] = "certSignature"; | 
|  |  | 
|  | const char kAuthModeKey[] = "mode"; | 
|  | const char kAuthCodeKey[] = "authCode"; | 
|  | const char kAuthRequestedScopeKey[] = "requestedScope"; | 
|  | const char kAuthScopeAutoValue[] = "auto"; | 
|  |  | 
|  | const char kAuthAccessTokenKey[] = "accessToken"; | 
|  | const char kAuthTokenTypeKey[] = "tokenType"; | 
|  | const char kAuthExpiresInKey[] = "expiresIn"; | 
|  | const char kAuthScopeKey[] = "scope"; | 
|  | const char kAuthClientTokenKey[] = "clientToken"; | 
|  |  | 
|  | const char kAuthorizationHeaderPrefix[] = "Privet"; | 
|  |  | 
|  | const char kErrorDebugInfoKey[] = "debugInfo"; | 
|  |  | 
|  | const char kSetupStartSsidKey[] = "ssid"; | 
|  | const char kSetupStartPassKey[] = "passphrase"; | 
|  | const char kSetupStartTicketIdKey[] = "ticketId"; | 
|  | const char kSetupStartUserKey[] = "user"; | 
|  |  | 
|  | const char kFingerprintKey[] = "fingerprint"; | 
|  | const char kStateKey[] = "state"; | 
|  | const char kCommandsKey[] = "commands"; | 
|  | const char kTraitsKey[] = "traits"; | 
|  | const char kComponentsKey[] = "components"; | 
|  | const char kCommandsIdKey[] = "id"; | 
|  | const char kPathKey[] = "path"; | 
|  | const char kFilterKey[] = "filter"; | 
|  |  | 
|  | const char kStateFingerprintKey[] = "stateFingerprint"; | 
|  | const char kCommandsFingerprintKey[] = "commandsFingerprint"; | 
|  | const char kTraitsFingerprintKey[] = "traitsFingerprint"; | 
|  | const char kComponentsFingerprintKey[] = "componentsFingerprint"; | 
|  | const char kWaitTimeoutKey[] = "waitTimeout"; | 
|  |  | 
|  | const char kInvalidParamValueFormat[] = "Invalid parameter: '%s'='%s'"; | 
|  |  | 
|  | template <class Container> | 
|  | std::unique_ptr<base::ListValue> ToValue(const Container& list) { | 
|  | std::unique_ptr<base::ListValue> value_list(new base::ListValue()); | 
|  | for (const std::string& val : list) | 
|  | value_list->AppendString(val); | 
|  | return value_list; | 
|  | } | 
|  |  | 
|  | struct { | 
|  | const char* const reason; | 
|  | int code; | 
|  | } kReasonToCode[] = { | 
|  | {errors::kInvalidClientCommitment, http::kForbidden}, | 
|  | {errors::kInvalidFormat, http::kBadRequest}, | 
|  | {errors::kMissingAuthorization, http::kDenied}, | 
|  | {errors::kInvalidAuthorization, http::kDenied}, | 
|  | {errors::kInvalidAuthorizationScope, http::kForbidden}, | 
|  | {errors::kAuthorizationExpired, http::kForbidden}, | 
|  | {errors::kCommitmentMismatch, http::kForbidden}, | 
|  | {errors::kUnknownSession, http::kNotFound}, | 
|  | {errors::kInvalidAuthCode, http::kForbidden}, | 
|  | {errors::kInvalidAuthMode, http::kBadRequest}, | 
|  | {errors::kInvalidRequestedScope, http::kBadRequest}, | 
|  | {errors::kAccessDenied, http::kForbidden}, | 
|  | {errors::kInvalidParams, http::kBadRequest}, | 
|  | {errors::kSetupUnavailable, http::kBadRequest}, | 
|  | {errors::kDeviceBusy, http::kServiceUnavailable}, | 
|  | {errors::kInvalidState, http::kInternalServerError}, | 
|  | {errors::kNotFound, http::kNotFound}, | 
|  | {errors::kNotImplemented, http::kNotSupported}, | 
|  | {errors::kAlreadyClaimed, http::kDenied}, | 
|  | }; | 
|  |  | 
|  | std::string GetAuthTokenFromAuthHeader(const std::string& auth_header) { | 
|  | return SplitAtFirst(auth_header, " ", true).second; | 
|  | } | 
|  |  | 
|  | // Creates JSON similar to GCD server error format. | 
|  | std::unique_ptr<base::DictionaryValue> ErrorToJson(const Error& error) { | 
|  | std::unique_ptr<base::DictionaryValue> output{ErrorInfoToJson(error)}; | 
|  |  | 
|  | // Optional debug information. | 
|  | std::unique_ptr<base::ListValue> errors{new base::ListValue}; | 
|  | for (const Error* it = &error; it; it = it->GetInnerError()) { | 
|  | std::unique_ptr<base::DictionaryValue> inner{ErrorInfoToJson(*it)}; | 
|  | tracked_objects::Location location{it->GetLocation().function_name.c_str(), | 
|  | it->GetLocation().file_name.c_str(), | 
|  | it->GetLocation().line_number, nullptr}; | 
|  | inner->SetString(kErrorDebugInfoKey, location.ToString()); | 
|  | errors->Append(inner.release()); | 
|  | } | 
|  | output->Set(kErrorDebugInfoKey, errors.release()); | 
|  | return output; | 
|  | } | 
|  |  | 
|  | template <class T> | 
|  | void SetStateProperties(const T& state, base::DictionaryValue* parent) { | 
|  | if (!state.error()) { | 
|  | parent->SetString(kStatusKey, EnumToString(state.status())); | 
|  | return; | 
|  | } | 
|  | parent->SetString(kStatusKey, kStatusErrorValue); | 
|  | parent->Set(kErrorKey, ErrorToJson(*state.error()).release()); | 
|  | } | 
|  |  | 
|  | void ReturnError(const Error& error, | 
|  | const PrivetHandler::RequestCallback& callback) { | 
|  | int code = http::kInternalServerError; | 
|  | for (const auto& it : kReasonToCode) { | 
|  | if (error.HasError(it.reason)) { | 
|  | code = it.code; | 
|  | break; | 
|  | } | 
|  | } | 
|  | std::unique_ptr<base::DictionaryValue> output{new base::DictionaryValue}; | 
|  | output->Set(kErrorKey, ErrorToJson(error).release()); | 
|  | callback.Run(code, *output); | 
|  | } | 
|  |  | 
|  | void OnCommandRequestSucceeded(const PrivetHandler::RequestCallback& callback, | 
|  | const base::DictionaryValue& output, | 
|  | ErrorPtr error) { | 
|  | if (!error) | 
|  | return callback.Run(http::kOk, output); | 
|  |  | 
|  | if (error->HasError("unknown_command")) { | 
|  | Error::AddTo(&error, FROM_HERE, errors::kNotFound, "Unknown command ID"); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  | if (error->HasError("access_denied")) { | 
|  | Error::AddTo(&error, FROM_HERE, errors::kAccessDenied, error->GetMessage()); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> CreateManifestSection( | 
|  | const CloudDelegate& cloud) { | 
|  | std::unique_ptr<base::DictionaryValue> manifest(new base::DictionaryValue()); | 
|  | manifest->SetString(kInfoManifestUiDeviceKind, | 
|  | GetDeviceUiKind(cloud.GetModelId())); | 
|  | manifest->SetString(kInfoManifestOemName, cloud.GetOemName()); | 
|  | manifest->SetString(kInfoManifestModelName, cloud.GetModelName()); | 
|  | return manifest; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> CreateEndpointsSection( | 
|  | const DeviceDelegate& device) { | 
|  | std::unique_ptr<base::DictionaryValue> endpoints(new base::DictionaryValue()); | 
|  | auto http_endpoint = device.GetHttpEnpoint(); | 
|  | endpoints->SetInteger(kInfoEndpointsHttpPortKey, http_endpoint.first); | 
|  | endpoints->SetInteger(kInfoEndpointsHttpUpdatePortKey, http_endpoint.second); | 
|  |  | 
|  | auto https_endpoint = device.GetHttpsEnpoint(); | 
|  | endpoints->SetInteger(kInfoEndpointsHttpsPortKey, https_endpoint.first); | 
|  | endpoints->SetInteger(kInfoEndpointsHttpsUpdatePortKey, | 
|  | https_endpoint.second); | 
|  |  | 
|  | return endpoints; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> CreateInfoAuthSection( | 
|  | const SecurityDelegate& security, | 
|  | AuthScope anonymous_max_scope) { | 
|  | std::unique_ptr<base::DictionaryValue> auth(new base::DictionaryValue()); | 
|  |  | 
|  | auth->SetString(kInfoAuthAnonymousMaxScopeKey, | 
|  | EnumToString(anonymous_max_scope)); | 
|  |  | 
|  | std::unique_ptr<base::ListValue> pairing_types(new base::ListValue()); | 
|  | for (PairingType type : security.GetPairingTypes()) | 
|  | pairing_types->AppendString(EnumToString(type)); | 
|  | auth->Set(kPairingKey, pairing_types.release()); | 
|  |  | 
|  | std::unique_ptr<base::ListValue> auth_types(new base::ListValue()); | 
|  | for (AuthType type : security.GetAuthTypes()) | 
|  | auth_types->AppendString(EnumToString(type)); | 
|  | auth->Set(kAuthModeKey, auth_types.release()); | 
|  |  | 
|  | std::unique_ptr<base::ListValue> crypto_types(new base::ListValue()); | 
|  | for (CryptoType type : security.GetCryptoTypes()) | 
|  | crypto_types->AppendString(EnumToString(type)); | 
|  | auth->Set(kCryptoKey, crypto_types.release()); | 
|  |  | 
|  | return auth; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> CreateWifiSection( | 
|  | const WifiDelegate& wifi) { | 
|  | std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue()); | 
|  |  | 
|  | std::unique_ptr<base::ListValue> capabilities(new base::ListValue()); | 
|  | for (WifiType type : wifi.GetTypes()) | 
|  | capabilities->AppendString(EnumToString(type)); | 
|  | result->Set(kInfoWifiCapabilitiesKey, capabilities.release()); | 
|  |  | 
|  | result->SetString(kInfoWifiSsidKey, wifi.GetCurrentlyConnectedSsid()); | 
|  |  | 
|  | std::string hosted_ssid = wifi.GetHostedSsid(); | 
|  | const ConnectionState& state = wifi.GetConnectionState(); | 
|  | if (!hosted_ssid.empty()) { | 
|  | DCHECK(!state.IsStatusEqual(ConnectionState::kDisabled)); | 
|  | DCHECK(!state.IsStatusEqual(ConnectionState::kOnline)); | 
|  | result->SetString(kInfoWifiHostedSsidKey, hosted_ssid); | 
|  | } | 
|  | SetStateProperties(state, result.get()); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> CreateGcdSection( | 
|  | const CloudDelegate& cloud) { | 
|  | std::unique_ptr<base::DictionaryValue> gcd(new base::DictionaryValue()); | 
|  | gcd->SetString(kInfoIdKey, cloud.GetCloudId()); | 
|  | SetStateProperties(cloud.GetConnectionState(), gcd.get()); | 
|  | return gcd; | 
|  | } | 
|  |  | 
|  | AuthScope GetAnonymousMaxScope(const CloudDelegate& cloud, | 
|  | const WifiDelegate* wifi) { | 
|  | if (wifi && !wifi->GetHostedSsid().empty()) | 
|  | return AuthScope::kNone; | 
|  | return cloud.GetAnonymousMaxScope(); | 
|  | } | 
|  |  | 
|  | // Forward-declaration. | 
|  | std::unique_ptr<base::DictionaryValue> CloneComponentTree( | 
|  | const base::DictionaryValue& parent, | 
|  | const std::set<std::string>& filter); | 
|  |  | 
|  | // Clones a particular component JSON object in a manner similar to that of | 
|  | // DeepCopy(), except it includes only sub-objects specified in |filter| (if not | 
|  | // empty) and has special handling for "components" sub-dictionary. | 
|  | std::unique_ptr<base::DictionaryValue> CloneComponent( | 
|  | const base::DictionaryValue& component, | 
|  | const std::set<std::string>& filter) { | 
|  | std::unique_ptr<base::DictionaryValue> clone{new base::DictionaryValue}; | 
|  | for (base::DictionaryValue::Iterator it(component); !it.IsAtEnd(); | 
|  | it.Advance()) { | 
|  | if (filter.empty() || filter.find(it.key()) != filter.end()) { | 
|  | if (it.key() == kComponentsKey) { | 
|  | // Handle "components" separately as we need to recursively clone | 
|  | // sub-components. | 
|  | const base::DictionaryValue* sub_components = nullptr; | 
|  | CHECK(it.value().GetAsDictionary(&sub_components)); | 
|  | clone->SetWithoutPathExpansion( | 
|  | it.key(), CloneComponentTree(*sub_components, filter).release()); | 
|  | } else { | 
|  | clone->SetWithoutPathExpansion(it.key(), it.value().DeepCopy()); | 
|  | } | 
|  | } | 
|  | } | 
|  | return clone; | 
|  | } | 
|  |  | 
|  | // Clones a dictionary containing a bunch of component JSON objects in a manner | 
|  | // similar to that of DeepCopy(). Calls CloneComponent() on each instance of | 
|  | // the component sub-object. | 
|  | std::unique_ptr<base::DictionaryValue> CloneComponentTree( | 
|  | const base::DictionaryValue& parent, | 
|  | const std::set<std::string>& filter) { | 
|  | std::unique_ptr<base::DictionaryValue> clone{new base::DictionaryValue}; | 
|  | for (base::DictionaryValue::Iterator it(parent); !it.IsAtEnd(); | 
|  | it.Advance()) { | 
|  | const base::DictionaryValue* component = nullptr; | 
|  | CHECK(it.value().GetAsDictionary(&component)); | 
|  | clone->SetWithoutPathExpansion( | 
|  | it.key(), CloneComponent(*component, filter).release()); | 
|  | } | 
|  | return clone; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | std::vector<std::string> PrivetHandler::GetHttpPaths() const { | 
|  | std::vector<std::string> result; | 
|  | for (const auto& pair : handlers_) { | 
|  | if (!pair.second.https_only) | 
|  | result.push_back(pair.first); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | std::vector<std::string> PrivetHandler::GetHttpsPaths() const { | 
|  | std::vector<std::string> result; | 
|  | for (const auto& pair : handlers_) | 
|  | result.push_back(pair.first); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | PrivetHandler::PrivetHandler(CloudDelegate* cloud, | 
|  | DeviceDelegate* device, | 
|  | SecurityDelegate* security, | 
|  | WifiDelegate* wifi, | 
|  | base::Clock* clock) | 
|  | : cloud_(cloud), | 
|  | device_(device), | 
|  | security_(security), | 
|  | wifi_(wifi), | 
|  | clock_(clock ? clock : &default_clock_) { | 
|  | CHECK(cloud_); | 
|  | CHECK(device_); | 
|  | CHECK(security_); | 
|  | CHECK(clock_); | 
|  | cloud_observer_.Add(cloud_); | 
|  |  | 
|  | AddHandler("/privet/info", &PrivetHandler::HandleInfo, AuthScope::kNone); | 
|  | AddHandler("/privet/v3/pairing/start", &PrivetHandler::HandlePairingStart, | 
|  | AuthScope::kNone); | 
|  | AddHandler("/privet/v3/pairing/confirm", &PrivetHandler::HandlePairingConfirm, | 
|  | AuthScope::kNone); | 
|  | AddHandler("/privet/v3/pairing/cancel", &PrivetHandler::HandlePairingCancel, | 
|  | AuthScope::kNone); | 
|  |  | 
|  | AddSecureHandler("/privet/v3/auth", &PrivetHandler::HandleAuth, | 
|  | AuthScope::kNone); | 
|  | AddSecureHandler("/privet/v3/accessControl/claim", | 
|  | &PrivetHandler::HandleAccessControlClaim, AuthScope::kOwner); | 
|  | AddSecureHandler("/privet/v3/accessControl/confirm", | 
|  | &PrivetHandler::HandleAccessControlConfirm, | 
|  | AuthScope::kOwner); | 
|  | AddSecureHandler("/privet/v3/setup/start", &PrivetHandler::HandleSetupStart, | 
|  | AuthScope::kManager); | 
|  | AddSecureHandler("/privet/v3/setup/status", &PrivetHandler::HandleSetupStatus, | 
|  | AuthScope::kManager); | 
|  | AddSecureHandler("/privet/v3/state", &PrivetHandler::HandleState, | 
|  | AuthScope::kViewer); | 
|  | AddSecureHandler("/privet/v3/commandDefs", &PrivetHandler::HandleCommandDefs, | 
|  | AuthScope::kViewer); | 
|  | AddSecureHandler("/privet/v3/commands/execute", | 
|  | &PrivetHandler::HandleCommandsExecute, AuthScope::kViewer); | 
|  | AddSecureHandler("/privet/v3/commands/status", | 
|  | &PrivetHandler::HandleCommandsStatus, AuthScope::kViewer); | 
|  | AddSecureHandler("/privet/v3/commands/cancel", | 
|  | &PrivetHandler::HandleCommandsCancel, AuthScope::kViewer); | 
|  | AddSecureHandler("/privet/v3/commands/list", | 
|  | &PrivetHandler::HandleCommandsList, AuthScope::kViewer); | 
|  | AddSecureHandler("/privet/v3/checkForUpdates", | 
|  | &PrivetHandler::HandleCheckForUpdates, AuthScope::kViewer); | 
|  | AddSecureHandler("/privet/v3/traits", &PrivetHandler::HandleTraits, | 
|  | AuthScope::kViewer); | 
|  | AddSecureHandler("/privet/v3/components", &PrivetHandler::HandleComponents, | 
|  | AuthScope::kViewer); | 
|  | } | 
|  |  | 
|  | PrivetHandler::~PrivetHandler() { | 
|  | for (const auto& req : update_requests_) | 
|  | ReplyToUpdateRequest(req.callback); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::OnTraitDefsChanged() { | 
|  | ++traits_fingerprint_; | 
|  | auto pred = [this](const UpdateRequestParameters& params) { | 
|  | return params.traits_fingerprint == 0; | 
|  | }; | 
|  | auto last = | 
|  | std::partition(update_requests_.begin(), update_requests_.end(), pred); | 
|  | for (auto p = last; p != update_requests_.end(); ++p) | 
|  | ReplyToUpdateRequest(p->callback); | 
|  | update_requests_.erase(last, update_requests_.end()); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::OnStateChanged() { | 
|  | // State updates also change the component tree, so update both fingerprints. | 
|  | ++state_fingerprint_; | 
|  | ++components_fingerprint_; | 
|  | auto pred = [this](const UpdateRequestParameters& params) { | 
|  | return params.state_fingerprint == 0 && params.components_fingerprint == 0; | 
|  | }; | 
|  | auto last = | 
|  | std::partition(update_requests_.begin(), update_requests_.end(), pred); | 
|  | for (auto p = last; p != update_requests_.end(); ++p) | 
|  | ReplyToUpdateRequest(p->callback); | 
|  | update_requests_.erase(last, update_requests_.end()); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::OnComponentTreeChanged() { | 
|  | ++components_fingerprint_; | 
|  | auto pred = [this](const UpdateRequestParameters& params) { | 
|  | return params.components_fingerprint == 0; | 
|  | }; | 
|  | auto last = | 
|  | std::partition(update_requests_.begin(), update_requests_.end(), pred); | 
|  | for (auto p = last; p != update_requests_.end(); ++p) | 
|  | ReplyToUpdateRequest(p->callback); | 
|  | update_requests_.erase(last, update_requests_.end()); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleRequest(const std::string& api, | 
|  | const std::string& auth_header, | 
|  | const base::DictionaryValue* input, | 
|  | const RequestCallback& callback) { | 
|  | ErrorPtr error; | 
|  | if (!input) { | 
|  | Error::AddTo(&error, FROM_HERE, errors::kInvalidFormat, "Malformed JSON"); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  | auto handler = handlers_.find(api); | 
|  | if (handler == handlers_.end()) { | 
|  | Error::AddTo(&error, FROM_HERE, errors::kNotFound, "Path not found"); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  | if (auth_header.empty()) { | 
|  | Error::AddTo(&error, FROM_HERE, errors::kMissingAuthorization, | 
|  | "Authorization header must not be empty"); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  | std::string token = GetAuthTokenFromAuthHeader(auth_header); | 
|  | if (token.empty()) { | 
|  | Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidAuthorization, | 
|  | "Invalid authorization header: %s", auth_header.c_str()); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  | UserInfo user_info; | 
|  | if (token != EnumToString(AuthType::kAnonymous)) { | 
|  | if (!security_->ParseAccessToken(token, &user_info, &error)) | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  |  | 
|  | if (handler->second.scope > user_info.scope()) { | 
|  | Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidAuthorizationScope, | 
|  | "Scope '%s' does not allow '%s'", | 
|  | EnumToString(user_info.scope()).c_str(), api.c_str()); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  | (this->*handler->second.handler)(*input, user_info, callback); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::AddHandler(const std::string& path, | 
|  | ApiHandler handler, | 
|  | AuthScope scope) { | 
|  | HandlerParameters params; | 
|  | params.handler = handler; | 
|  | params.scope = scope; | 
|  | params.https_only = false; | 
|  | CHECK(handlers_.insert(std::make_pair(path, params)).second); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::AddSecureHandler(const std::string& path, | 
|  | ApiHandler handler, | 
|  | AuthScope scope) { | 
|  | HandlerParameters params; | 
|  | params.handler = handler; | 
|  | params.scope = scope; | 
|  | params.https_only = true; | 
|  | CHECK(handlers_.insert(std::make_pair(path, params)).second); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleInfo(const base::DictionaryValue&, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | base::DictionaryValue output; | 
|  |  | 
|  | std::string name = cloud_->GetName(); | 
|  | std::string model_id = cloud_->GetModelId(); | 
|  |  | 
|  | output.SetString(kInfoVersionKey, kInfoVersionValue); | 
|  | output.SetString(kInfoIdKey, cloud_->GetDeviceId()); | 
|  | output.SetString(kNameKey, name); | 
|  |  | 
|  | std::string description{cloud_->GetDescription()}; | 
|  | if (!description.empty()) | 
|  | output.SetString(kDescrptionKey, description); | 
|  |  | 
|  | std::string location{cloud_->GetLocation()}; | 
|  | if (!location.empty()) | 
|  | output.SetString(kLocationKey, location); | 
|  |  | 
|  | output.SetString(kInfoModelIdKey, model_id); | 
|  | output.Set(kInfoModelManifestKey, CreateManifestSection(*cloud_).release()); | 
|  | output.Set( | 
|  | kInfoServicesKey, | 
|  | ToValue(std::vector<std::string>{GetDeviceUiKind(cloud_->GetModelId())}) | 
|  | .release()); | 
|  |  | 
|  | output.Set( | 
|  | kInfoAuthenticationKey, | 
|  | CreateInfoAuthSection(*security_, GetAnonymousMaxScope(*cloud_, wifi_)) | 
|  | .release()); | 
|  |  | 
|  | output.Set(kInfoEndpointsKey, CreateEndpointsSection(*device_).release()); | 
|  |  | 
|  | if (wifi_) | 
|  | output.Set(kWifiKey, CreateWifiSection(*wifi_).release()); | 
|  |  | 
|  | output.Set(kGcdKey, CreateGcdSection(*cloud_).release()); | 
|  |  | 
|  | output.SetDouble(kInfoTimeKey, clock_->Now().ToJsTime()); | 
|  | output.SetString(kInfoSessionIdKey, security_->CreateSessionId()); | 
|  |  | 
|  | callback.Run(http::kOk, output); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandlePairingStart(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | ErrorPtr error; | 
|  |  | 
|  | std::string pairing_str; | 
|  | input.GetString(kPairingKey, &pairing_str); | 
|  |  | 
|  | std::string crypto_str; | 
|  | input.GetString(kCryptoKey, &crypto_str); | 
|  |  | 
|  | PairingType pairing; | 
|  | std::set<PairingType> modes = security_->GetPairingTypes(); | 
|  | if (!StringToEnum(pairing_str, &pairing) || | 
|  | modes.find(pairing) == modes.end()) { | 
|  | Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidParams, | 
|  | kInvalidParamValueFormat, kPairingKey, | 
|  | pairing_str.c_str()); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  |  | 
|  | CryptoType crypto; | 
|  | std::set<CryptoType> cryptos = security_->GetCryptoTypes(); | 
|  | if (!StringToEnum(crypto_str, &crypto) || | 
|  | cryptos.find(crypto) == cryptos.end()) { | 
|  | Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidParams, | 
|  | kInvalidParamValueFormat, kCryptoKey, | 
|  | crypto_str.c_str()); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  |  | 
|  | std::string id; | 
|  | std::string commitment; | 
|  | if (!security_->StartPairing(pairing, crypto, &id, &commitment, &error)) | 
|  | return ReturnError(*error, callback); | 
|  |  | 
|  | base::DictionaryValue output; | 
|  | output.SetString(kPairingSessionIdKey, id); | 
|  | output.SetString(kPairingDeviceCommitmentKey, commitment); | 
|  | callback.Run(http::kOk, output); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandlePairingConfirm(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | std::string id; | 
|  | input.GetString(kPairingSessionIdKey, &id); | 
|  |  | 
|  | std::string commitment; | 
|  | input.GetString(kPairingClientCommitmentKey, &commitment); | 
|  |  | 
|  | std::string fingerprint; | 
|  | std::string signature; | 
|  | ErrorPtr error; | 
|  | if (!security_->ConfirmPairing(id, commitment, &fingerprint, &signature, | 
|  | &error)) { | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  |  | 
|  | base::DictionaryValue output; | 
|  | output.SetString(kPairingFingerprintKey, fingerprint); | 
|  | output.SetString(kPairingSignatureKey, signature); | 
|  | callback.Run(http::kOk, output); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandlePairingCancel(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | std::string id; | 
|  | input.GetString(kPairingSessionIdKey, &id); | 
|  |  | 
|  | ErrorPtr error; | 
|  | if (!security_->CancelPairing(id, &error)) | 
|  | return ReturnError(*error, callback); | 
|  |  | 
|  | base::DictionaryValue output; | 
|  | callback.Run(http::kOk, output); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleAuth(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | ErrorPtr error; | 
|  |  | 
|  | std::string auth_code_type; | 
|  | AuthType auth_type{}; | 
|  | if (!input.GetString(kAuthModeKey, &auth_code_type) || | 
|  | !StringToEnum(auth_code_type, &auth_type)) { | 
|  | Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidAuthMode, | 
|  | kInvalidParamValueFormat, kAuthModeKey, | 
|  | auth_code_type.c_str()); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  |  | 
|  | AuthScope desired_scope = AuthScope::kOwner; | 
|  | AuthScope acceptable_scope = AuthScope::kViewer; | 
|  |  | 
|  | std::string requested_scope; | 
|  | input.GetString(kAuthRequestedScopeKey, &requested_scope); | 
|  | if (requested_scope != kAuthScopeAutoValue) { | 
|  | if (!StringToEnum(requested_scope, &desired_scope)) { | 
|  | Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidRequestedScope, | 
|  | kInvalidParamValueFormat, kAuthRequestedScopeKey, | 
|  | requested_scope.c_str()); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  | acceptable_scope = std::max(desired_scope, acceptable_scope); | 
|  | } | 
|  |  | 
|  | if (auth_type == AuthType::kAnonymous) | 
|  | desired_scope = GetAnonymousMaxScope(*cloud_, wifi_); | 
|  |  | 
|  | std::string auth_code; | 
|  | input.GetString(kAuthCodeKey, &auth_code); | 
|  |  | 
|  | std::string access_token; | 
|  | base::TimeDelta access_token_ttl; | 
|  | AuthScope access_token_scope = AuthScope::kNone; | 
|  | if (!security_->CreateAccessToken(auth_type, auth_code, desired_scope, | 
|  | &access_token, &access_token_scope, | 
|  | &access_token_ttl, &error)) { | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  |  | 
|  | if (access_token_scope < acceptable_scope) { | 
|  | Error::AddToPrintf(&error, FROM_HERE, errors::kAccessDenied, | 
|  | "Scope '%s' is not allowed", | 
|  | EnumToString(access_token_scope).c_str()); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  |  | 
|  | base::DictionaryValue output; | 
|  | output.SetString(kAuthAccessTokenKey, access_token); | 
|  | output.SetString(kAuthTokenTypeKey, kAuthorizationHeaderPrefix); | 
|  | output.SetInteger(kAuthExpiresInKey, access_token_ttl.InSeconds()); | 
|  | output.SetString(kAuthScopeKey, EnumToString(access_token_scope)); | 
|  |  | 
|  | callback.Run(http::kOk, output); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleAccessControlClaim(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | ErrorPtr error; | 
|  | auto token = security_->ClaimRootClientAuthToken(&error); | 
|  | if (token.empty()) | 
|  | return ReturnError(*error, callback); | 
|  |  | 
|  | base::DictionaryValue output; | 
|  | output.SetString(kAuthClientTokenKey, token); | 
|  | callback.Run(http::kOk, output); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleAccessControlConfirm( | 
|  | const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | ErrorPtr error; | 
|  |  | 
|  | std::string token; | 
|  | if (!input.GetString(kAuthClientTokenKey, &token)) { | 
|  | Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidParams, | 
|  | kInvalidParamValueFormat, kAuthClientTokenKey, | 
|  | token.c_str()); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  |  | 
|  | if (!security_->ConfirmClientAuthToken(token, &error)) | 
|  | return ReturnError(*error, callback); | 
|  |  | 
|  | base::DictionaryValue output; | 
|  | callback.Run(http::kOk, output); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleSetupStart(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | std::string name{cloud_->GetName()}; | 
|  | input.GetString(kNameKey, &name); | 
|  |  | 
|  | std::string description{cloud_->GetDescription()}; | 
|  | input.GetString(kDescrptionKey, &description); | 
|  |  | 
|  | std::string location{cloud_->GetLocation()}; | 
|  | input.GetString(kLocationKey, &location); | 
|  |  | 
|  | std::string ssid; | 
|  | std::string passphrase; | 
|  | std::string ticket; | 
|  | std::string user; | 
|  |  | 
|  | const base::DictionaryValue* wifi = nullptr; | 
|  | if (input.GetDictionary(kWifiKey, &wifi)) { | 
|  | if (!wifi_ || wifi_->GetTypes().empty()) { | 
|  | ErrorPtr error; | 
|  | Error::AddTo(&error, FROM_HERE, errors::kSetupUnavailable, | 
|  | "WiFi setup unavailable"); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  | wifi->GetString(kSetupStartSsidKey, &ssid); | 
|  | if (ssid.empty()) { | 
|  | ErrorPtr error; | 
|  | Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidParams, | 
|  | kInvalidParamValueFormat, kSetupStartSsidKey, ""); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  | wifi->GetString(kSetupStartPassKey, &passphrase); | 
|  | } | 
|  |  | 
|  | const base::DictionaryValue* registration = nullptr; | 
|  | if (input.GetDictionary(kGcdKey, ®istration)) { | 
|  | if (user_info.scope() < AuthScope::kOwner) { | 
|  | ErrorPtr error; | 
|  | Error::AddTo(&error, FROM_HERE, errors::kInvalidAuthorizationScope, | 
|  | "Only owner can register device"); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  | registration->GetString(kSetupStartTicketIdKey, &ticket); | 
|  | if (ticket.empty()) { | 
|  | ErrorPtr error; | 
|  | Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidParams, | 
|  | kInvalidParamValueFormat, kSetupStartTicketIdKey, ""); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  | registration->GetString(kSetupStartUserKey, &user); | 
|  | } | 
|  |  | 
|  | cloud_->UpdateDeviceInfo(name, description, location); | 
|  |  | 
|  | ErrorPtr error; | 
|  | if (!ssid.empty() && !wifi_->ConfigureCredentials(ssid, passphrase, &error)) | 
|  | return ReturnError(*error, callback); | 
|  |  | 
|  | if (!ticket.empty() && !cloud_->Setup(ticket, user, &error)) | 
|  | return ReturnError(*error, callback); | 
|  |  | 
|  | ReplyWithSetupStatus(callback); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleSetupStatus(const base::DictionaryValue&, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | ReplyWithSetupStatus(callback); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::ReplyWithSetupStatus( | 
|  | const RequestCallback& callback) const { | 
|  | base::DictionaryValue output; | 
|  |  | 
|  | const SetupState& state = cloud_->GetSetupState(); | 
|  | if (!state.IsStatusEqual(SetupState::kNone)) { | 
|  | base::DictionaryValue* gcd = new base::DictionaryValue; | 
|  | output.Set(kGcdKey, gcd); | 
|  | SetStateProperties(state, gcd); | 
|  | if (state.IsStatusEqual(SetupState::kSuccess)) | 
|  | gcd->SetString(kInfoIdKey, cloud_->GetCloudId()); | 
|  | } | 
|  |  | 
|  | if (wifi_) { | 
|  | const SetupState& state = wifi_->GetSetupState(); | 
|  | if (!state.IsStatusEqual(SetupState::kNone)) { | 
|  | base::DictionaryValue* wifi = new base::DictionaryValue; | 
|  | output.Set(kWifiKey, wifi); | 
|  | SetStateProperties(state, wifi); | 
|  | if (state.IsStatusEqual(SetupState::kSuccess)) | 
|  | wifi->SetString(kInfoWifiSsidKey, wifi_->GetCurrentlyConnectedSsid()); | 
|  | } | 
|  | } | 
|  |  | 
|  | callback.Run(http::kOk, output); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleState(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | base::DictionaryValue output; | 
|  | output.Set(kStateKey, cloud_->GetLegacyState().DeepCopy()); | 
|  | output.SetString(kFingerprintKey, std::to_string(state_fingerprint_)); | 
|  |  | 
|  | callback.Run(http::kOk, output); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleTraits(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | base::DictionaryValue output; | 
|  | output.Set(kTraitsKey, cloud_->GetTraits().DeepCopy()); | 
|  | output.SetString(kFingerprintKey, std::to_string(traits_fingerprint_)); | 
|  |  | 
|  | callback.Run(http::kOk, output); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleComponents(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | std::string path; | 
|  | std::set<std::string> filter; | 
|  | std::unique_ptr<base::DictionaryValue> components; | 
|  |  | 
|  | input.GetString(kPathKey, &path); | 
|  | const base::ListValue* filter_items = nullptr; | 
|  | if (input.GetList(kFilterKey, &filter_items)) { | 
|  | for (const base::Value* value : *filter_items) { | 
|  | std::string filter_item; | 
|  | if (value->GetAsString(&filter_item)) | 
|  | filter.insert(filter_item); | 
|  | } | 
|  | } | 
|  | const base::DictionaryValue* component = nullptr; | 
|  | if (!path.empty()) { | 
|  | ErrorPtr error; | 
|  | component = cloud_->FindComponent(path, &error); | 
|  | if (!component) | 
|  | return ReturnError(*error, callback); | 
|  | components.reset(new base::DictionaryValue); | 
|  | // Get the last element of the path and use it as a dictionary key here. | 
|  | auto parts = Split(path, ".", true, false); | 
|  | components->Set(parts.back(), CloneComponent(*component, filter).release()); | 
|  | } else { | 
|  | components = CloneComponentTree(cloud_->GetComponents(), filter); | 
|  | } | 
|  | base::DictionaryValue output; | 
|  | output.Set(kComponentsKey, components.release()); | 
|  | output.SetString(kFingerprintKey, std::to_string(components_fingerprint_)); | 
|  |  | 
|  | callback.Run(http::kOk, output); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleCommandDefs(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | base::DictionaryValue output; | 
|  | output.Set(kCommandsKey, cloud_->GetLegacyCommandDef().DeepCopy()); | 
|  | // Use traits fingerprint since right now we treat traits and command defs | 
|  | // as being equivalent. | 
|  | output.SetString(kFingerprintKey, std::to_string(traits_fingerprint_)); | 
|  |  | 
|  | callback.Run(http::kOk, output); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleCommandsExecute(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | cloud_->AddCommand(input, user_info, | 
|  | base::Bind(&OnCommandRequestSucceeded, callback)); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleCommandsStatus(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | std::string id; | 
|  | if (!input.GetString(kCommandsIdKey, &id)) { | 
|  | ErrorPtr error; | 
|  | Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidParams, | 
|  | kInvalidParamValueFormat, kCommandsIdKey, id.c_str()); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  | cloud_->GetCommand(id, user_info, | 
|  | base::Bind(&OnCommandRequestSucceeded, callback)); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleCommandsList(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | cloud_->ListCommands(user_info, | 
|  | base::Bind(&OnCommandRequestSucceeded, callback)); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleCommandsCancel(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | std::string id; | 
|  | if (!input.GetString(kCommandsIdKey, &id)) { | 
|  | ErrorPtr error; | 
|  | Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidParams, | 
|  | kInvalidParamValueFormat, kCommandsIdKey, id.c_str()); | 
|  | return ReturnError(*error, callback); | 
|  | } | 
|  | cloud_->CancelCommand(id, user_info, | 
|  | base::Bind(&OnCommandRequestSucceeded, callback)); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::HandleCheckForUpdates(const base::DictionaryValue& input, | 
|  | const UserInfo& user_info, | 
|  | const RequestCallback& callback) { | 
|  | int timeout_seconds = -1; | 
|  | input.GetInteger(kWaitTimeoutKey, &timeout_seconds); | 
|  | base::TimeDelta timeout = device_->GetHttpRequestTimeout(); | 
|  | // Allow 10 seconds to cut the timeout short to make sure HTTP server doesn't | 
|  | // kill the connection before we have a chance to respond. 10 seconds chosen | 
|  | // at random here without any scientific basis for the value. | 
|  | const base::TimeDelta safety_gap = base::TimeDelta::FromSeconds(10); | 
|  | if (timeout != base::TimeDelta::Max()) { | 
|  | if (timeout > safety_gap) | 
|  | timeout -= safety_gap; | 
|  | else | 
|  | timeout = base::TimeDelta::FromSeconds(0); | 
|  | } | 
|  | if (timeout_seconds >= 0) | 
|  | timeout = std::min(timeout, base::TimeDelta::FromSeconds(timeout_seconds)); | 
|  | if (timeout == base::TimeDelta{}) | 
|  | return ReplyToUpdateRequest(callback); | 
|  |  | 
|  | std::string state_fingerprint; | 
|  | std::string commands_fingerprint; | 
|  | std::string traits_fingerprint; | 
|  | std::string components_fingerprint; | 
|  | input.GetString(kStateFingerprintKey, &state_fingerprint); | 
|  | input.GetString(kCommandsFingerprintKey, &commands_fingerprint); | 
|  | input.GetString(kTraitsFingerprintKey, &traits_fingerprint); | 
|  | input.GetString(kComponentsFingerprintKey, &components_fingerprint); | 
|  | const bool ignore_state = state_fingerprint.empty(); | 
|  | const bool ignore_commands = commands_fingerprint.empty(); | 
|  | const bool ignore_traits = traits_fingerprint.empty(); | 
|  | const bool ignore_components = components_fingerprint.empty(); | 
|  | // If all fingerprints are missing, nothing to wait for, return immediately. | 
|  | if (ignore_state && ignore_commands && ignore_traits && ignore_components) | 
|  | return ReplyToUpdateRequest(callback); | 
|  | // If the current state fingerprint is different from the requested one, | 
|  | // return new fingerprints. | 
|  | if (!ignore_state && state_fingerprint != std::to_string(state_fingerprint_)) | 
|  | return ReplyToUpdateRequest(callback); | 
|  | // If the current commands fingerprint is different from the requested one, | 
|  | // return new fingerprints. | 
|  | // NOTE: We are using traits fingerprint for command fingerprint as well. | 
|  | if (!ignore_commands && | 
|  | commands_fingerprint != std::to_string(traits_fingerprint_)) { | 
|  | return ReplyToUpdateRequest(callback); | 
|  | } | 
|  | // If the current traits fingerprint is different from the requested one, | 
|  | // return new fingerprints. | 
|  | if (!ignore_traits && | 
|  | traits_fingerprint != std::to_string(traits_fingerprint_)) { | 
|  | return ReplyToUpdateRequest(callback); | 
|  | } | 
|  | // If the current components fingerprint is different from the requested one, | 
|  | // return new fingerprints. | 
|  | if (!ignore_components && | 
|  | components_fingerprint != std::to_string(components_fingerprint_)) { | 
|  | return ReplyToUpdateRequest(callback); | 
|  | } | 
|  |  | 
|  | UpdateRequestParameters params; | 
|  | params.request_id = ++last_update_request_id_; | 
|  | params.callback = callback; | 
|  | params.traits_fingerprint = | 
|  | (ignore_traits && ignore_commands) ? 0 : traits_fingerprint_; | 
|  | params.state_fingerprint = ignore_state ? 0 : state_fingerprint_; | 
|  | params.components_fingerprint = | 
|  | ignore_components ? 0 : components_fingerprint_; | 
|  | update_requests_.push_back(params); | 
|  | if (timeout != base::TimeDelta::Max()) { | 
|  | device_->PostDelayedTask( | 
|  | FROM_HERE, | 
|  | base::Bind(&PrivetHandler::OnUpdateRequestTimeout, | 
|  | weak_ptr_factory_.GetWeakPtr(), last_update_request_id_), | 
|  | timeout); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PrivetHandler::ReplyToUpdateRequest( | 
|  | const RequestCallback& callback) const { | 
|  | base::DictionaryValue output; | 
|  | output.SetString(kStateFingerprintKey, std::to_string(state_fingerprint_)); | 
|  | output.SetString(kCommandsFingerprintKey, | 
|  | std::to_string(traits_fingerprint_)); | 
|  | output.SetString(kTraitsFingerprintKey, std::to_string(traits_fingerprint_)); | 
|  | output.SetString(kComponentsFingerprintKey, | 
|  | std::to_string(components_fingerprint_)); | 
|  | callback.Run(http::kOk, output); | 
|  | } | 
|  |  | 
|  | void PrivetHandler::OnUpdateRequestTimeout(int update_request_id) { | 
|  | auto pred = [update_request_id](const UpdateRequestParameters& params) { | 
|  | return params.request_id != update_request_id; | 
|  | }; | 
|  | auto last = | 
|  | std::partition(update_requests_.begin(), update_requests_.end(), pred); | 
|  | for (auto p = last; p != update_requests_.end(); ++p) | 
|  | ReplyToUpdateRequest(p->callback); | 
|  | update_requests_.erase(last, update_requests_.end()); | 
|  | } | 
|  |  | 
|  | }  // namespace privet | 
|  | }  // namespace weave |