blob: 74bae5db6ca84acd11a221411bfeb957f7a78da4 [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 COBALT_SRC_OBSERVATION_STORE_OBSERVATION_STORE_H_
#define COBALT_SRC_OBSERVATION_STORE_OBSERVATION_STORE_H_
#include <deque>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "src/lib/crypto_util/random.h"
#include "src/lib/util/encrypted_message_util.h"
#include "src/logger/internal_metrics.h"
#include "src/logger/logger_interface.h"
#include "src/observation_store/observation_store_internal.pb.h"
#include "src/pb/envelope.pb.h"
#include "src/pb/observation_batch.pb.h"
namespace cobalt {
namespace observation_store {
// A specification to identify a single Cobalt report.
struct ReportSpec {
uint32_t customer_id;
uint32_t project_id;
uint32_t metric_id;
uint32_t report_id;
bool operator<(const ReportSpec& o) const {
return std::tie(customer_id, project_id, metric_id, report_id) <
std::tie(o.customer_id, o.project_id, o.metric_id, o.report_id);
}
};
// The expected size in bytes of an EncryptedMessage's |contribution_id| field.
constexpr size_t kContributionIdSize = 8u;
// ObservationStoreWriterInterface is an abstract interface to a store
// of Observations, to be used by code that writes to this store. This is
// isolated into an abstract interface that can be mocked out in tests.
class ObservationStoreWriterInterface {
public:
virtual ~ObservationStoreWriterInterface() = default;
enum StoreStatus {
// StoreObservation() succeeded.
kOk = 0,
// The Observation was not added to the store because it is too big.
kObservationTooBig,
// The observation was not added to the store because it is full. The
// Observation itself is not too big to be added otherwise.
kStoreFull,
// The Observation was not added to the store because of an unspecified
// writing error. It may be a file system error, or some other reason.
kWriteFailed,
};
// StoreObservation takes a (possibly encrypted) observation, and its associated metadata and
// stores it in the store.
//
// The three variants below represent storing a definitely encrypted observation
// (EncyrptedMessage), storing a definitely unenrcypted message (Observation) and storing a
// possibly encrypted message (StoredObservation).
//
// The first two are convenience methods that wrap around the third.
StoreStatus StoreObservation(std::unique_ptr<EncryptedMessage> observation,
std::unique_ptr<ObservationMetadata> metadata,
bool is_contribution) {
auto obs = std::make_unique<StoredObservation>();
obs->set_allocated_encrypted(observation.release());
if (is_contribution) {
obs->set_allocated_contribution_id(new std::string(kContributionIdSize, 0));
random_.RandomString(obs->mutable_contribution_id());
}
return StoreObservation(std::move(obs), std::move(metadata));
}
StoreStatus StoreObservation(std::unique_ptr<Observation> observation,
std::unique_ptr<ObservationMetadata> metadata,
bool is_contribution) {
auto obs = std::make_unique<StoredObservation>();
obs->set_allocated_unencrypted(observation.release());
if (is_contribution) {
obs->set_allocated_contribution_id(new std::string(kContributionIdSize, 0));
random_.RandomString(obs->mutable_contribution_id());
}
return StoreObservation(std::move(obs), std::move(metadata));
}
virtual StoreStatus StoreObservation(std::unique_ptr<StoredObservation> observation,
std::unique_ptr<ObservationMetadata> metadata) = 0;
private:
mutable crypto::Random random_;
};
// ObservationStore is an abstract interface to an underlying store of encrypted observations and
// their metadata. These are organized within the store into Envelopes. Individual (encrypted
// observation, metadata) pairs are added one-at-a-time via the method StoreObservation(). These
// pairs are pooled together and will eventually be combined into an Envelope. These Envelopes are
// then collected into a list, and will be returned one-at-a-time from calls to
// TakeNextEnvelopeHolder(). If there are no envelopes available to return, TakeNextEnvelopeHolder()
// will return nullptr.
//
// The EnvelopeHolders that are returned from this method should be treated as "owned" by the
// caller. When the EnvelopeHolder is destroyed, its underlying data is also deleted. If the
// underlying data should not be deleted (e.g. if the upload failed), the EnvelopeHolder should be
// placed back into the ObservationStore using the ReturnEnvelopeHolder() method.
class ObservationStore : public ObservationStoreWriterInterface {
public:
// EnvelopeHolder holds a reference to a single Envelope and its underlying
// data storage. An instance of EnvelopeHolder is considered to own its
// Envelope. When EnvelopeHolder is deleted, the underlying data storage for
// the owned Envelope will be deleted. The ObservationStore considers the
// envelopes owned by EnvelopeHolders to no longer be in the store.
class EnvelopeHolder {
public:
EnvelopeHolder() = default;
// When this EnvelopeHolder is deleted, the underlying data will be deleted.
virtual ~EnvelopeHolder() = default;
// MergeWith takes posession of the Envelope owned by |other| and merges
// that EnvelopeHolder's underlying data with that of its own. After the
// call completes, |other| no longer owns any Envelope and it is deleted
// without deleting any underlying data.
virtual void MergeWith(std::unique_ptr<EnvelopeHolder> other) = 0;
// Returns a const reference to the Envelope owned by this EnvelopeHolder. This is not
// necessarily a cheap operation and may involve reading from disk or encrypting.
//
// |encrypter| Is used to encrypt observations that were not encrypted when they were added to
// the store. It should not be null. (n.b. It is possible that observations were added to the
// store with a different EncryptedMessageMaker, in this case, the envelope that is produced
// will have observations encrypted with two (or more) different EncryptedMessageMakers.)
//
// TODO(fxb/3842): Make ObservationStore *only* store unencrypted observations.
virtual const Envelope& GetEnvelope(util::EncryptedMessageMaker* encrypter) = 0;
// Returns an estimated size on the wire of the resulting Envelope owned by
// thes EnvelopeHolder.
virtual size_t Size() = 0;
private:
EnvelopeHolder(const EnvelopeHolder&) = delete;
EnvelopeHolder& operator=(const EnvelopeHolder&) = delete;
};
// max_bytes_per_observation. StoreObservation() will return kObservationTooBig if the given
// encrypted Observation's serialized size is bigger than this.
//
// max_bytes_per_envelope. When pooling together observations into an Envelope, the
// ObservationStore will try not to form envelopes larger than this size. This should be used to
// avoid sending messages over HTTP that are too large.
//
// max_bytes_total. This is the maximum size of the Observations in the store. If the size of the
// accumulated Observation data reaches this value then ObservationStore will not accept any more
// Observations: StoreObservation() will return kStoreFull, until enough observations are removed
// from the store.
//
// REQUIRED:
// 0 <= max_bytes_per_observation <= max_bytes_per_envelope <= max_bytes_total
// 0 <= max_bytes_per_envelope
explicit ObservationStore(size_t max_bytes_per_observation, size_t max_bytes_per_envelope,
size_t max_bytes_total);
~ObservationStore() override = default;
// Returns a human-readable name for the StoreStatus.
static std::string StatusDebugString(StoreStatus status);
using ObservationStoreWriterInterface::StoreObservation;
// Adds the given (StoredObservation, ObservationMetadata) pair into the store. If this causes the
// pool of observations to exceed max_bytes_per_envelope, then the ObservationStore will construct
// an EnvelopeHolder to be returned from TakeNextEnvelopeHolder().
//
// N.B. If the store has been disabled (IsDisabled() returns true) this method will always return
// kOk, even though the observation has not been stored.
StoreStatus StoreObservation(std::unique_ptr<StoredObservation> observation,
std::unique_ptr<ObservationMetadata> metadata) override = 0;
// Returns the next EnvelopeHolder from the list of EnvelopeHolders in the
// store. If there are no more EnvelopeHolders available, this will return
// nullptr. A given EnvelopeHolder will only be returned from this function
// *once* unless it is subsequently returned using ReturnEnvelopeHolder.
virtual std::unique_ptr<EnvelopeHolder> TakeNextEnvelopeHolder() = 0;
// ReturnEnvelopeHolder takes an EnvelopeHolder and adds it back to the store
// so that it may be returned by a later call to TakeNextEnvelopeHolder(). Use
// this when an envelope failed to upload, so the underlying data should not
// be deleted.
virtual void ReturnEnvelopeHolder(std::unique_ptr<EnvelopeHolder> envelope) = 0;
// Resets the internal metrics to use the provided logger.
virtual void ResetInternalMetrics(logger::InternalMetrics* internal_metrics) = 0;
[[nodiscard]] virtual const logger::InternalMetrics* internal_metrics() const = 0;
// Returns true when the size of the data in the ObservationStore exceeds 60%
// of max_bytes_total.
[[nodiscard]] bool IsAlmostFull() const;
// Returns an approximation of the size of all the data in the store.
[[nodiscard]] virtual size_t Size() const = 0;
// Returns wether or not the store is entirely empty.
[[nodiscard]] virtual bool Empty() const = 0;
// Returns the number of Observations that have been added to the
// ObservationStore.
[[nodiscard]] uint64_t num_observations_added() const;
// Returns a vector containing the number of Observations that have been added
// to the ObservationStore for each specified report.
[[nodiscard]] std::vector<uint64_t> num_observations_added_for_reports(
const std::vector<ReportSpec>& report_specs) const;
// Resets the count of Observations that have been added to the
// ObservationStore.
void ResetObservationCounter();
// Disable allows enabling/disabling the ObservationStore. When the store is disabled,
// StoreObservation() will return kOk but the observation will not be stored.
void Disable(bool is_disabled);
// IsDisabled returns true if the ObservationStore is disabled and should ignore incoming
// observations, by returning kOk and not storing the data.
bool IsDisabled() { return is_disabled_; }
// DeleteData removes all stored Observations from the device. After this method is called, a call
// to Size() or TakeNextEnvelopeHolder() will return 0 and nullptr respectively.
virtual void DeleteData() = 0;
protected:
// NOLINTNEXTLINE misc-non-private-member-variables-in-classes
const size_t max_bytes_per_observation_;
// NOLINTNEXTLINE misc-non-private-member-variables-in-classes
const size_t max_bytes_per_envelope_;
// NOLINTNEXTLINE misc-non-private-member-variables-in-classes
const size_t max_bytes_total_;
// NOLINTNEXTLINE misc-non-private-member-variables-in-classes
const size_t almost_full_threshold_;
// NOLINTNEXTLINE misc-non-private-member-variables-in-classes
std::map<ReportSpec, uint64_t> num_obs_per_report_;
private:
bool is_disabled_ = false;
};
} // namespace observation_store
} // namespace cobalt
#endif // COBALT_SRC_OBSERVATION_STORE_OBSERVATION_STORE_H_