session-ios/SessionMessagingKit/LibSessionUtil/libsession-util.xcframework/session/config.hpp

353 lines
17 KiB
C++

#pragma once
#include <oxenc/bt_serialize.h>
#include <oxenc/bt_value.h>
#include <array>
#include <cassert>
#include <optional>
#include <set>
#include <stdexcept>
#include <variant>
#include <vector>
#include "types.hpp"
namespace session::config {
// FIXME: for multi-message we encode to longer and then split it up
inline constexpr int MAX_MESSAGE_SIZE = 76800; // 76.8kB = Storage server's limit
// Application data data types:
using scalar = std::variant<int64_t, std::string>;
using set = std::set<scalar>;
struct dict_value;
using dict = std::map<std::string, dict_value>;
using dict_variant = std::variant<dict, set, scalar>;
struct dict_value : dict_variant {
using dict_variant::dict_variant;
using dict_variant::operator=;
};
// Helpers for gcc-10 and earlier which don't like visiting a std::variant subtype:
constexpr inline dict_variant& unwrap(dict_value& v) {
return static_cast<dict_variant&>(v);
}
constexpr inline const dict_variant& unwrap(const dict_value& v) {
return static_cast<const dict_variant&>(v);
}
using hash_t = std::array<unsigned char, 32>;
using seqno_hash_t = std::pair<seqno_t, hash_t>;
class MutableConfigMessage;
/// Base type for all errors that can happen during config parsing
struct config_error : std::runtime_error {
using std::runtime_error::runtime_error;
};
/// Type thrown for bad signatures (bad or missing signature).
struct signature_error : config_error {
using config_error::config_error;
};
/// Type thrown for a missing signature when a signature is required.
struct missing_signature : signature_error {
using signature_error::signature_error;
};
/// Type thrown for an unparseable config (e.g. keys with invalid types, or keys before "#" or after
/// "~").
struct config_parse_error : config_error {
using config_error::config_error;
};
/// Class for a parsed, read-only config message; also serves as the base class of a
/// MutableConfigMessage which allows setting values.
class ConfigMessage {
public:
using lagged_diffs_t = std::map<seqno_hash_t, oxenc::bt_dict>;
#ifndef SESSION_TESTING_EXPOSE_INTERNALS
protected:
#endif
dict data_;
// diff data for *this* message, parsed during construction. Subclasses may use this for
// managing their own diff in the `diff()` method.
oxenc::bt_dict diff_;
// diffs of previous messages that are included in this message.
lagged_diffs_t lagged_diffs_;
// Unknown top-level config keys which we preserve even though we don't understand what they
// mean.
oxenc::bt_dict unknown_;
/// Seqno and hash of the message; we calculate this when loading. Subclasses put the hash here
/// (so that they can return a reference to it).
seqno_hash_t seqno_hash_{0, {0}};
bool verified_signature_ = false;
// This will be set during construction from configs based on the merge result:
// -1 means we had to merge one or more configs together into a new merged config
// >= 0 indicates the index of the config we used if we did not merge (i.e. there was only one
// config, or there were multiple but one of them referenced all the others).
int unmerged_ = -1;
public:
constexpr static int DEFAULT_DIFF_LAGS = 5;
/// Verification function: this is passed the data that should have been signed and the 64-byte
/// signature. Should return true to accept the signature, false to reject it and skip the
/// message. It can also throw to abort message construction (that is: returning false skips
/// the message when loading multiple messages, but can still continue with other messages;
/// throwing aborts the entire construction).
using verify_callable = std::function<bool(ustring_view data, ustring_view signature)>;
/// Signing function: this is passed the data to be signed and returns the 64-byte signature.
using sign_callable = std::function<ustring(ustring_view data)>;
ConfigMessage();
ConfigMessage(const ConfigMessage&) = default;
ConfigMessage& operator=(const ConfigMessage&) = default;
ConfigMessage(ConfigMessage&&) = default;
ConfigMessage& operator=(ConfigMessage&&) = default;
virtual ~ConfigMessage() = default;
/// Initializes a config message by parsing a serialized message. Throws on any error. See the
/// vector version below for argument descriptions.
explicit ConfigMessage(
ustring_view serialized,
verify_callable verifier = nullptr,
sign_callable signer = nullptr,
int lag = DEFAULT_DIFF_LAGS,
bool signature_optional = false);
/// Constructs a new ConfigMessage by loading and potentially merging multiple serialized
/// ConfigMessages together, according to the config conflict resolution rules. The result
/// of this call can either be one of the config messages directly (if one is found that
/// includes all the others), or can be a new config message that merges multiple configs
/// together. You can check `.merged()` to see which happened.
///
/// This constructor always requires at least one valid config from the given inputs; if all are
/// empty,
///
/// verifier - a signature verification function. If provided and not nullptr this will be
/// called to verify each signature in the provided messages: any that are missing a signature
/// or for which the verifier returns false will be dropped from consideration for merging. If
/// *all* messages fail verification an exception is raised.
///
/// signer - a signature generation function. This is not used directly by the ConfigMessage,
/// but providing it will allow it to be passed automatically to any MutableConfigMessage
/// derived from this ConfigMessage.
///
/// lag - the lag setting controlling the config merging rules. Any config message with lagged
/// diffs that exceeding this lag value will have those early lagged diffs dropping during
/// loading.
///
/// signature_optional - if true then accept a message with no signature even when a verifier is
/// set, thus allowing unsigned messages (though messages with an invalid signature are still
/// not allowed). This option is ignored when verifier is not set.
///
/// error_handler - if set then any config message parsing error will be passed to this function
/// for handling with the index of `configs` that failed and the error exception: the callback
/// typically warns and, if the overall construction should abort, rethrows the error. If this
/// function is omitted then the default skips (without failing) individual parse errors and
/// only aborts construction if *all* messages fail to parse. A simple handler such as
/// `[](size_t, const auto& e) { throw e; }` can be used to make any parse error of any message
/// fatal.
explicit ConfigMessage(
const std::vector<ustring_view>& configs,
verify_callable verifier = nullptr,
sign_callable signer = nullptr,
int lag = DEFAULT_DIFF_LAGS,
bool signature_optional = false,
std::function<void(size_t, const config_error&)> error_handler = nullptr);
/// Returns a read-only reference to the contained data. (To get a mutable config object use
/// MutableConfigMessage).
const dict& data() const { return data_; }
/// The verify function; if loading a message with a signature and this is set then it will
/// be called to verify the signature of the message. Takes a pointer to the signing data,
/// the data length, and a pointer to the 64-byte signature.
verify_callable verifier;
/// The signing function; this is not directly used by the non-mutable base class, but will be
/// propagated to mutable config messages that are derived e.g. by calling `.increment()`. This
/// is called when serializing a config message to add a signature. If it is nullptr then no
/// signature is added to the serialized data.
sign_callable signer;
/// How many lagged config diffs that should be carried forward to resolve conflicts,
/// including this message. If 0 then config messages won't have any diffs and will not be
/// mergeable.
int lag = DEFAULT_DIFF_LAGS;
/// The diff structure for changes in *this* config message. Subclasses that need to override
/// should populate into `diff_` and return a reference to it (internal code assumes `diff_` is
/// correct immediately after a call to this).
virtual const oxenc::bt_dict& diff();
/// Returns the seqno of this message
const seqno_t& seqno() const { return seqno_hash_.first; }
/// Calculates the hash of the current message. For a ConfigMessage this is calculated when the
/// message is first loaded; for a MutableConfigMessage this serializes the current value to
/// properly compute the current hash. Subclasses must ensure that seqno_hash_.second is set to
/// the correct value when this is called (and typically return a reference to it).
virtual const hash_t& hash() { return seqno_hash_.second; }
/// After loading multiple config files this flag indicates whether or not we had to produce a
/// new, merged configuration message (true) or did not need to merge (false). (For config
/// messages that were not loaded from serialized data this is always true).
bool merged() const { return unmerged_ == -1; }
/// After loading multiple config files this field contains the index of the single config we
/// used if we didn't need to merge (that is: there was only one config or one config that
/// superceded all the others). If we had to merge (or this wasn't loaded from serialized
/// data), this will return -1.
int unmerged_index() const { return unmerged_; }
/// Returns true if this message contained a valid, verified signature when it was parsed.
/// Returns false otherwise (e.g. not loaded from verification at all; loaded without a
/// verification function; or had no signature and a signature wasn't required).
bool verified_signature() const { return verified_signature_; }
/// Constructs a new MutableConfigMessage from this config message with an incremented seqno.
/// The new config message's diff will reflect changes made after this construction.
virtual MutableConfigMessage increment() const;
/// Serializes this config's data. Note that if the ConfigMessage was constructed from signed,
/// serialized input, this will only produce an exact copy of the original serialized input if
/// it uses the identical, deterministic signing function used to construct the original.
///
/// The optional `enable_signing` argument can be specified as false to disable signing (this is
/// typically for a local serialization value that isn't being pushed to the server). Note that
/// signing is always disabled if there is no signing callback set, regardless of the value of
/// this argument.
virtual ustring serialize(bool enable_signing = true);
protected:
ustring serialize_impl(const oxenc::bt_dict& diff, bool enable_signing = true);
};
// Constructor tag
struct increment_seqno_t {};
struct retain_seqno_t {};
inline constexpr increment_seqno_t increment_seqno{};
inline constexpr retain_seqno_t retain_seqno{};
class MutableConfigMessage : public ConfigMessage {
protected:
dict orig_data_{data_};
friend class ConfigMessage;
public:
MutableConfigMessage(const MutableConfigMessage&) = default;
MutableConfigMessage& operator=(const MutableConfigMessage&) = default;
MutableConfigMessage(MutableConfigMessage&&) = default;
MutableConfigMessage& operator=(MutableConfigMessage&&) = default;
/// Constructs a new, empty config message. Takes various fields to pre-fill the various
/// properties during construction (these are for convenience and equivalent to setting them via
/// properties/methods after construction).
///
/// seqno -- the message's seqno, default 0
/// lags -- number of lags to keep (when deriving messages, e.g. via increment())
/// signer -- if specified and not nullptr then this message will be signed when serialized
/// using the given signing function. If omitted no signing takes place.
explicit MutableConfigMessage(
seqno_t seqno = 0, int lag = DEFAULT_DIFF_LAGS, sign_callable signer = nullptr) {
this->lag = lag;
this->seqno(seqno);
this->signer = signer;
}
/// Wraps the ConfigMessage constructor with the same arguments but always produces a
/// MutableConfigMessage. In particular this means that if the base constructor performed a
/// merge (and thus incremented seqno) then the config stays as is, but contained in a Mutable
/// message that can be changed. If it did *not* merge (i.e. the highest seqno message it found
/// did not conflict with any other messages) then this construction is equivalent to doing a
/// base load followed by a .increment() call. In other words: this constructor *always* gives
/// you an incremented seqno value from the highest valid input config message.
///
/// This is almost equivalent to ConfigMessage{args...}.increment(), except that this
/// constructor only increments seqno once while the indirect version would increment twice in
/// the case of a required merge conflict resolution.
explicit MutableConfigMessage(
const std::vector<ustring_view>& configs,
verify_callable verifier = nullptr,
sign_callable signer = nullptr,
int lag = DEFAULT_DIFF_LAGS,
bool signature_optional = false,
std::function<void(size_t, const config_error&)> error_handler = nullptr);
/// Wrapper around the above that takes a single string view to load a single message, doesn't
/// take an error handler and instead always throws on parse errors (the above also throws for
/// an erroneous single message, but with a less specific "no valid config messages" error).
explicit MutableConfigMessage(
ustring_view config,
verify_callable verifier = nullptr,
sign_callable signer = nullptr,
int lag = DEFAULT_DIFF_LAGS,
bool signature_optional = false);
/// Does the same as the base incrementing, but also records any diff info from the current
/// MutableConfigMessage. *this* object gets pruned and signed as part of this call. If the
/// sign argument is omitted/nullptr then the current object's `sign` callback gets copied into
/// the new object. After this call you typically do not want to further modify *this (because
/// any modifications will change the hash, making *this no longer a parent of the new object).
MutableConfigMessage increment() const override;
/// Constructor that does the same thing as the `m.increment()` factory method. The second
/// value should be the literal `increment_seqno` value (to select this constructor).
explicit MutableConfigMessage(const ConfigMessage& m, const increment_seqno_t&);
/// Constructor that moves a immutable message into a mutable one, retaining the current seqno.
/// This is typically used in situations where the ConfigMessage has had some implicit seqno
/// increment already (e.g. from merging) and we want it to become mutable without incrementing
/// the seqno again. The second value should be the literal `retain_seqno` value (to select
/// this constructor).
explicit MutableConfigMessage(ConfigMessage&& m, const retain_seqno_t&);
using ConfigMessage::data;
/// Returns a mutable reference to the underlying config data.
dict& data() { return data_; }
using ConfigMessage::seqno;
/// Sets the seqno of the message to a specific value. You usually want to use `.increment()`
/// from an existing config message rather than manually adjusting the seqno.
void seqno(seqno_t new_seqno) { seqno_hash_.first = new_seqno; }
/// Returns the current diff for this data relative to its original data. The data is pruned
/// implicitly by this call.
const oxenc::bt_dict& diff() override;
/// Prunes empty dicts/sets from data. This is called automatically when serializing or
/// calculating a diff. Returns true if the data was actually changed, false if nothing needed
/// pruning.
bool prune();
/// Calculates the hash of the current message. Can optionally be given the already-serialized
/// value, if available; if empty/omitted, `serialize()` will be called to compute it.
const hash_t& hash() override;
protected:
const hash_t& hash(ustring_view serialized);
void increment_impl();
};
} // namespace session::config
namespace oxenc::detail {
template <>
struct bt_serialize<session::config::dict_value> : bt_serialize<session::config::dict_variant> {};
} // namespace oxenc::detail