blob: 75159b56b24d9bee2475b6a638af9eb81fa0cf1f [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/update_client/update_engine.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/barrier_callback.h"
#include "base/check_op.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/task/sequenced_task_runner.h"
#include "base/uuid.h"
#include "base/values.h"
#include "base/version.h"
#include "components/prefs/pref_service.h"
#include "components/update_client/component.h"
#include "components/update_client/configurator.h"
#include "components/update_client/crx_cache.h"
#include "components/update_client/crx_update_item.h"
#include "components/update_client/persisted_data.h"
#include "components/update_client/protocol_parser.h"
#include "components/update_client/update_checker.h"
#include "components/update_client/update_client.h"
#include "components/update_client/update_client_errors.h"
#include "components/update_client/utils.h"
namespace update_client {
namespace {
base::Value::Dict MakeEvent(UpdateClient::PingParams ping_params,
const base::Version& previous_version) {
base::Value::Dict event;
event.Set("eventtype", ping_params.event_type);
event.Set("eventresult", ping_params.result);
if (ping_params.error_code) {
event.Set("errorcode", ping_params.error_code);
}
if (ping_params.extra_code1) {
event.Set("extracode1", ping_params.extra_code1);
}
if (!ping_params.app_command_id.empty()) {
event.Set("appcommandid", ping_params.app_command_id);
}
event.Set("previousversion", previous_version.GetString());
return event;
}
} // namespace
UpdateContext::UpdateContext(
scoped_refptr<Configurator> config,
bool is_foreground,
bool is_install,
const std::vector<std::string>& ids,
UpdateClient::CrxStateChangeCallback crx_state_change_callback,
UpdateEngine::Callback callback,
PersistedData* persisted_data,
bool is_update_check_only,
base::RepeatingCallback<int64_t(const base::FilePath&)> get_available_space)
: config(config),
is_foreground(is_foreground),
is_install(is_install),
ids(ids),
crx_state_change_callback(crx_state_change_callback),
callback(std::move(callback)),
session_id(base::StrCat(
{"{", base::Uuid::GenerateRandomV4().AsLowercaseString(), "}"})),
persisted_data(persisted_data),
is_update_check_only(is_update_check_only),
get_available_space(get_available_space) {
for (const auto& id : ids) {
components.insert(
std::make_pair(id, std::make_unique<Component>(*this, id)));
}
}
UpdateContext::~UpdateContext() = default;
UpdateEngine::UpdateEngine(
scoped_refptr<Configurator> config,
UpdateChecker::Factory update_checker_factory,
scoped_refptr<PingManager> ping_manager,
const UpdateClient::CrxStateChangeCallback& notify_observers_callback)
: config_(config),
update_checker_factory_(update_checker_factory),
ping_manager_(ping_manager),
notify_observers_callback_(notify_observers_callback) {}
UpdateEngine::~UpdateEngine() = default;
base::RepeatingClosure UpdateEngine::Update(
bool is_foreground,
bool is_install,
const std::vector<std::string>& ids,
UpdateClient::CrxDataCallback crx_data_callback,
UpdateClient::CrxStateChangeCallback crx_state_change_callback,
Callback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return InvokeOperation(is_foreground, /*is_update_check_only=*/false,
is_install, ids, std::move(crx_data_callback),
std::move(crx_state_change_callback),
std::move(callback));
}
void UpdateEngine::CheckForUpdate(
bool is_foreground,
const std::string& id,
UpdateClient::CrxDataCallback crx_data_callback,
UpdateClient::CrxStateChangeCallback crx_state_change_callback,
Callback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
InvokeOperation(is_foreground, /*is_update_check_only=*/true,
/*is_install=*/false, {id}, std::move(crx_data_callback),
std::move(crx_state_change_callback), std::move(callback));
}
base::RepeatingClosure UpdateEngine::InvokeOperation(
bool is_foreground,
bool is_update_check_only,
bool is_install,
const std::vector<std::string>& ids,
UpdateClient::CrxDataCallback crx_data_callback,
UpdateClient::CrxStateChangeCallback crx_state_change_callback,
Callback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (ids.empty()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), Error::INVALID_ARGUMENT));
return base::DoNothing();
}
if (IsThrottled(is_foreground)) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), Error::RETRY_LATER));
return base::DoNothing();
}
scoped_refptr<UpdateContext> update_context =
base::MakeRefCounted<UpdateContext>(
config_, is_foreground, is_install, ids,
crx_state_change_callback
? base::BindRepeating(
[](UpdateClient::CrxStateChangeCallback a,
UpdateClient::CrxStateChangeCallback b,
const CrxUpdateItem& item) {
a.Run(item);
b.Run(item);
},
crx_state_change_callback, notify_observers_callback_)
: notify_observers_callback_,
std::move(callback), config_->GetPersistedData(),
is_update_check_only);
CHECK(!update_context->session_id.empty());
const auto [unused, inserted] = update_contexts_.insert(
std::make_pair(update_context->session_id, update_context));
CHECK(inserted);
// Calls out to get the corresponding CrxComponent data for the components.
std::move(crx_data_callback)
.Run(ids,
base::BindOnce(&UpdateEngine::StartOperation, this, update_context));
return is_update_check_only
? base::DoNothing()
: base::BindRepeating(
[](scoped_refptr<UpdateContext> context) {
context->is_cancelled = true;
for (const auto& entry : context->components) {
entry.second->Cancel();
}
},
update_context);
}
void UpdateEngine::StartOperation(
scoped_refptr<UpdateContext> update_context,
const std::vector<std::optional<CrxComponent>>& crx_components) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (crx_components.size() != update_context->ids.size()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_context->callback),
Error::BAD_CRX_DATA_CALLBACK));
return;
}
for (size_t i = 0; i != update_context->ids.size(); ++i) {
const auto& id = update_context->ids[i];
CHECK_EQ(update_context->components[id]->state(), ComponentState::kNew);
const auto crx_component = crx_components[i];
if (crx_component && (id == GetCrxComponentID(*crx_component))) {
// This component can be checked for updates.
auto& component = update_context->components[id];
component->set_crx_component(*crx_component);
component->set_previous_version(component->crx_component()->version);
component->set_previous_fp(component->crx_component()->fingerprint);
update_context->components_to_check_for_updates.push_back(id);
} else {
// |CrxDataCallback| did not return a CrxComponent instance for this
// component, which most likely, has been uninstalled. This component
// is going to be transitioned to an error state when the its |Handle|
// method is called later on by the engine.
update_context->component_queue.push(id);
}
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(update_context->components_to_check_for_updates.empty()
? &UpdateEngine::HandleComponent
: &UpdateEngine::DoUpdateCheck,
this, update_context));
}
void UpdateEngine::DoUpdateCheck(scoped_refptr<UpdateContext> update_context) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(update_context);
// Make the components transition from |kNew| to |kChecking| state.
for (const auto& id : update_context->components_to_check_for_updates) {
update_context->components[id]->Handle(base::DoNothing());
}
update_context->update_checker = update_checker_factory_.Run(config_);
update_context->update_checker->CheckForUpdates(
update_context, config_->ExtraRequestParams(),
base::BindOnce(&UpdateEngine::UpdateCheckResultsAvailable, this,
update_context));
}
void UpdateEngine::UpdateCheckResultsAvailable(
scoped_refptr<UpdateContext> update_context,
std::optional<ProtocolParser::Results> results,
ErrorCategory error_category,
int error,
int retry_after_sec) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(update_context);
update_context->retry_after_sec = retry_after_sec;
// Only positive values for throttle_sec are effective. 0 means that no
// throttling occurs and it resets the throttle.
// Negative values are not trusted and are ignored.
constexpr int kMaxRetryAfterSec = 24 * 60 * 60; // 24 hours.
const int throttle_sec =
std::min(update_context->retry_after_sec, kMaxRetryAfterSec);
if (throttle_sec >= 0) {
config_->GetPersistedData()->SetThrottleUpdatesUntil(
throttle_sec ? base::Time::Now() + base::Seconds(throttle_sec)
: base::Time());
}
update_context->update_check_error = error;
auto complete = base::BarrierCallback<bool>(
update_context->components_to_check_for_updates.size(),
base::BindOnce([](const std::vector<bool>&) {})
.Then(base::BindOnce(&UpdateEngine::UpdateCheckComplete, this,
update_context)));
if (error) {
CHECK(!results);
for (const auto& id : update_context->components_to_check_for_updates) {
CHECK_EQ(1u, update_context->components.count(id));
auto& component = update_context->components.at(id);
component->SetUpdateCheckResult(std::nullopt, ErrorCategory::kUpdateCheck,
error, complete);
}
return;
}
CHECK(results);
CHECK_EQ(0, error);
std::map<std::string, ProtocolParser::App> id_to_result;
for (const auto& result : results->apps) {
id_to_result[result.app_id] = result;
}
for (const auto& id : update_context->components_to_check_for_updates) {
CHECK_EQ(1u, update_context->components.count(id));
auto& component = update_context->components.at(id);
const auto& it = id_to_result.find(id);
if (it != id_to_result.end()) {
const auto& result = it->second;
const auto& [category, protocol_error] = [](const std::string& status) {
// "ok" and "noupdate" are non-error cases.
if (status == "ok" || status == "noupdate") {
return std::make_pair(ErrorCategory::kNone, ProtocolError::NONE);
}
// Some app status literals can be folded down as an updatecheck status.
if (status == "error-unknownApplication") {
return std::make_pair(ErrorCategory::kUpdateCheck,
ProtocolError::UNKNOWN_APPLICATION);
}
if (status == "restricted") {
return std::make_pair(ErrorCategory::kUpdateCheck,
ProtocolError::RESTRICTED_APPLICATION);
}
if (status == "error-invalidAppId") {
return std::make_pair(ErrorCategory::kUpdateCheck,
ProtocolError::INVALID_APPID);
}
if (status == "error-osnotsupported") {
return std::make_pair(ErrorCategory::kUpdateCheck,
ProtocolError::OS_NOT_SUPPORTED);
}
if (status == "error-hwnotsupported") {
return std::make_pair(ErrorCategory::kUpdateCheck,
ProtocolError::HW_NOT_SUPPORTED);
}
if (status == "error-hash") {
return std::make_pair(ErrorCategory::kUpdateCheck,
ProtocolError::NO_HASH);
}
if (status == "error-unsupportedprotocol") {
return std::make_pair(ErrorCategory::kUpdateCheck,
ProtocolError::UNSUPPORTED_PROTOCOL);
}
if (status == "error-internal") {
return std::make_pair(ErrorCategory::kUpdateCheck,
ProtocolError::INTERNAL);
}
if (status == "error-inexpressible") {
return std::make_pair(ErrorCategory::kUpdateCheck,
ProtocolError::INEXPRESSIBLE);
}
// Otherwise, this is an unknown status.
return std::make_pair(ErrorCategory::kUpdateCheck,
ProtocolError::UNKNOWN_ERROR);
}(result.status);
component->SetUpdateCheckResult(
result, category, static_cast<int>(protocol_error), complete);
} else {
component->SetUpdateCheckResult(
std::nullopt, ErrorCategory::kUpdateCheck,
static_cast<int>(ProtocolError::UPDATE_RESPONSE_NOT_FOUND), complete);
}
}
}
void UpdateEngine::UpdateCheckComplete(
scoped_refptr<UpdateContext> update_context) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(update_context);
for (const auto& id : update_context->components_to_check_for_updates) {
update_context->component_queue.push(id);
// Handle the |kChecking| state and transition the component to the
// next state, depending on the update check results.
CHECK_EQ(1u, update_context->components.count(id));
auto& component = update_context->components.at(id);
CHECK_EQ(component->state(), ComponentState::kChecking);
component->Handle(base::DoNothing());
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&UpdateEngine::HandleComponent, this, update_context));
}
void UpdateEngine::HandleComponent(
scoped_refptr<UpdateContext> update_context) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(update_context);
auto& queue = update_context->component_queue;
if (queue.empty()) {
const Error error = update_context->update_check_error
? Error::UPDATE_CHECK_ERROR
: Error::NONE;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&UpdateEngine::UpdateComplete, this,
update_context, error));
return;
}
const auto& id = queue.front();
CHECK_EQ(1u, update_context->components.count(id));
const auto& component = update_context->components.at(id);
CHECK(component);
auto& next_update_delay = update_context->next_update_delay;
if (!next_update_delay.is_zero() && component->IsUpdateAvailable()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&UpdateEngine::HandleComponent, this, update_context),
next_update_delay);
next_update_delay = base::TimeDelta();
return;
}
component->Handle(base::BindOnce(&UpdateEngine::HandleComponentComplete, this,
update_context));
}
void UpdateEngine::HandleComponentComplete(
scoped_refptr<UpdateContext> update_context) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(update_context);
auto& queue = update_context->component_queue;
CHECK(!queue.empty());
const auto& id = queue.front();
CHECK_EQ(1u, update_context->components.count(id));
const auto& component = update_context->components.at(id);
CHECK(component);
base::OnceClosure callback =
base::BindOnce(&UpdateEngine::HandleComponent, this, update_context);
if (component->IsHandled()) {
update_context->next_update_delay = component->GetUpdateDuration();
queue.pop();
if (!component->events().empty()) {
CHECK(component->crx_component());
ping_manager_->SendPing(component->session_id(),
*component->crx_component(),
component->GetEvents(), std::move(callback));
return;
}
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(callback));
}
void UpdateEngine::UpdateComplete(scoped_refptr<UpdateContext> update_context,
Error error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(update_context);
const auto num_erased = update_contexts_.erase(update_context->session_id);
CHECK_EQ(1u, num_erased);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_context->callback), error));
}
bool UpdateEngine::GetUpdateState(const std::string& id,
CrxUpdateItem* update_item) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (const auto& context : update_contexts_) {
const auto& components = context.second->components;
const auto it = components.find(id);
if (it != components.end()) {
*update_item = it->second->GetCrxUpdateItem();
return true;
}
}
return false;
}
bool UpdateEngine::IsThrottled(bool is_foreground) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::Time throttle_updates_until =
config_->GetPersistedData()->GetThrottleUpdatesUntil();
if (is_foreground || throttle_updates_until.is_null()) {
return false;
}
const auto now(base::Time::Now());
// Throttle the calls in the interval (t - 1 day, t) to limit the effect of
// unset clocks or clock drift.
return throttle_updates_until - base::Days(1) < now &&
now < throttle_updates_until;
}
void UpdateEngine::SendPing(const CrxComponent& crx_component,
UpdateClient::PingParams ping_params,
Callback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::vector<base::Value::Dict> events;
events.push_back(MakeEvent(ping_params, crx_component.version));
ping_manager_->SendPing(
base::StrCat(
{"{", base::Uuid::GenerateRandomV4().AsLowercaseString(), "}"}),
crx_component, std::move(events),
base::BindOnce(std::move(callback), Error::NONE));
}
} // namespace update_client