mirror of
https://github.com/lux-tel/libsession-util.git
synced 2026-10-30 20:25:11 +00:00
• Reverted the previous changes • Added "t" to indicate the timestamp the profile pic was last updated via a user-driven change • Added new "P", "Q" and "T" keys to UserProfile to represent re-uploaded profile pic data • Added logic to auto-update the "t" and "T" timestamps when content is changed
410 lines
20 KiB
C++
410 lines
20 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 {
|
|
|
|
// The maximum size of a single message in storage server. For larger configs we have to split a
|
|
// config message into a multipart message.
|
|
inline constexpr int MAX_MESSAGE_SIZE = 76'800; // 76.8kB = Storage server's limit
|
|
|
|
// Maximum size of a post-compression outgoing multipart config message (before chopping into
|
|
// multiple parts, and so not counting the small encryption and encoding overhead of each piece)
|
|
// that we will allow. This is not a strict network limit, but rather is simply to prevent bugs
|
|
// that have some sort of accidental runaway size. This limit does not apply to *incoming*
|
|
// multipart messages so that if a future version changes the limit it will not break compatibility
|
|
// with existing clients.
|
|
//
|
|
// Also note that we encode part index and length as 1 byte each, so this must be small enough to
|
|
// not exceed 255 parts (but that would be insane anyway).
|
|
inline constexpr int MAX_MULTIPART_SIZE = 5'000'000;
|
|
|
|
// 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;
|
|
};
|
|
/// Type thrown for some bad value in a config (e.g. missing required key, or key with an
|
|
/// unexpected/unhandled value).
|
|
struct config_value_error : config_parse_error {
|
|
using config_parse_error::config_parse_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}};
|
|
|
|
std::optional<std::array<unsigned char, 64>> verified_signature_;
|
|
|
|
// This will be set during construction from configs based on the merge result:
|
|
// nullopt means we had to merge one or more configs together into a new merged config
|
|
// If set to a value then the value is the index of the config we used (i.e. there was only one
|
|
// config, or there were multiple but one of them referenced all the others).
|
|
std::optional<size_t> unmerged_;
|
|
|
|
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(
|
|
std::span<const unsigned char> data, std::span<const unsigned char> signature)>;
|
|
|
|
/// Signing function: this is passed the data to be signed and returns the 64-byte signature.
|
|
using sign_callable =
|
|
std::function<std::vector<unsigned char>(std::span<const unsigned char> 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(
|
|
std::span<const unsigned char> serialized,
|
|
verify_callable verifier = nullptr,
|
|
sign_callable signer = nullptr,
|
|
int lag = DEFAULT_DIFF_LAGS,
|
|
bool trust_signature = 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.
|
|
///
|
|
/// 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<std::span<const unsigned char>>& configs,
|
|
verify_callable verifier = nullptr,
|
|
sign_callable signer = nullptr,
|
|
int lag = DEFAULT_DIFF_LAGS,
|
|
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_; }
|
|
|
|
/// 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 std::nullopt.
|
|
const std::optional<size_t>& unmerged_index() const { return unmerged_; }
|
|
|
|
/// Read-only access to the optional verified signature if this message contained a valid,
|
|
/// verified signature when it was parsed. Returns nullopt otherwise (e.g. not loaded from
|
|
/// verification at all; loaded without a verification function; or had no signature and a
|
|
/// signature wasn't required).
|
|
const std::optional<std::array<unsigned char, 64>>& verified_signature() {
|
|
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 std::vector<unsigned char> serialize(bool enable_signing = true);
|
|
|
|
protected:
|
|
std::vector<unsigned char> 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<std::span<const unsigned char>>& configs,
|
|
verify_callable verifier = nullptr,
|
|
sign_callable signer = nullptr,
|
|
int lag = DEFAULT_DIFF_LAGS,
|
|
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(
|
|
std::span<const unsigned char> config,
|
|
verify_callable verifier = nullptr,
|
|
sign_callable signer = nullptr,
|
|
int lag = DEFAULT_DIFF_LAGS);
|
|
|
|
/// 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.
|
|
const hash_t& hash() override;
|
|
|
|
protected:
|
|
/// Internal version of hash() that takes the already-serialized value, to avoid needing a call
|
|
/// to `serialize()` when such a call has already been done for other reasons.
|
|
const hash_t& hash(std::span<const unsigned char> serialized);
|
|
void increment_impl();
|
|
};
|
|
|
|
/// API: base/verify_config_sig
|
|
///
|
|
/// Verifies a config message signature, throwing a missing_signature or signature_error exception
|
|
/// if the signature is missing or invalid.
|
|
///
|
|
/// A config message signature is always in the "~" key of a config message, which must be the
|
|
/// very last key of the message, and signs the config value up to (but not including) the ~
|
|
/// key-value pair in the serialized config message.
|
|
///
|
|
/// For instance, for a config message of:
|
|
///
|
|
/// d[...configdata...]1:~64:[sigdata]e
|
|
///
|
|
/// the signature signs the value `d[...configdata...]` (i.e. the `1:~64:[sigdata]` signature
|
|
/// pair, and the final closing `e` of the config message, are not included). No keys may
|
|
/// follow the signature key/value.
|
|
///
|
|
/// Inputs:
|
|
/// - `dict` -- a `bt_dict_consumer` positioned at or before the "~" key where the signature is
|
|
/// expected. (If the bt_dict_consumer has already consumed the "~" key then this call will fail
|
|
/// as if the signature was missing).
|
|
/// - `verifier` -- a callback to invoke to verify the signature of the message. If the callback is
|
|
/// empty then the signature will be ignored (it is neither required nor verified).
|
|
/// - `verified_signature` is a pointer to a std::optional array of signature data; if this is
|
|
/// specified and not nullptr then the optional with be emplaced with the signature bytes if the
|
|
/// signature successfully validates.
|
|
/// - `trust_signature` allows setting `verified_signature` when a signature is present but no
|
|
/// verifier is provided to verify it. Without specifying this, `verified_signature` will only be
|
|
/// set when the signature actually validates via a `verifier` call. This is primarily used
|
|
/// during restoring config dumps.
|
|
///
|
|
/// Outputs:
|
|
/// - returns with no value on success
|
|
/// - throws on failure
|
|
void verify_config_sig(
|
|
oxenc::bt_dict_consumer dict,
|
|
const ConfigMessage::verify_callable& verifier,
|
|
std::optional<std::array<unsigned char, 64>>* verified_signature = nullptr,
|
|
bool trust_signature = false);
|
|
|
|
} // namespace session::config
|
|
|
|
namespace oxenc::detail {
|
|
|
|
template <>
|
|
struct bt_serialize<session::config::dict_value> : bt_serialize<session::config::dict_variant> {};
|
|
|
|
} // namespace oxenc::detail
|