diff --git a/include/session/config/contacts.h b/include/session/config/contacts.h index e275215..d2f213a 100644 --- a/include/session/config/contacts.h +++ b/include/session/config/contacts.h @@ -36,6 +36,31 @@ typedef struct contacts_contact { } contacts_contact; +typedef struct contacts_blinded_contact { + char session_id[67]; // in hex; 66 hex chars + null terminator. + char base_url[268]; // null-terminated (max length 267), normalized (i.e. always lower-case, + // only has port if non-default, has trailing / removed) + unsigned char pubkey[32]; // 32 bytes (not terminated, can contain nulls) + + char name[101]; // This will be a 0-length string when unset + user_profile_pic profile_pic; + + bool legacy_blinding; + int64_t created; // unix timestamp (seconds) + +} contacts_blinded_contact; + +/// Struct containing a list of contacts_blinded_contact structs. Typically where this is returned +/// by this API it must be freed (via `free()`) when done with it. +/// +/// When returned as a pointer by a libsession-util function this is allocated in such a way that +/// just the outer contacts_blinded_contact_list can be free()d to free both the list *and* the +/// inner `value` and pointed-at values. +typedef struct contacts_blinded_contact_list { + contacts_blinded_contact** value; // array of blinded contacts + size_t len; // length of `value` +} contacts_blinded_contact_list; + /// API: contacts/contacts_init /// /// Constructs a contacts config object and sets a pointer to it in `conf`. @@ -208,6 +233,104 @@ LIBSESSION_EXPORT bool contacts_erase(config_object* conf, const char* session_i /// - `size_t` -- number of contacts LIBSESSION_EXPORT size_t contacts_size(const config_object* conf); +/// API: contacts/contacts_blinded_contacts +/// +/// Retrieves a list of blinded contact records. +/// +/// Declaration: +/// ```cpp +/// contacts_blinded_contact_list* contacts_blinded_contacts( +/// [in] config_object* conf +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in, out] Pointer to config_object object +/// +/// Outputs: +/// - `contacts_blinded_contact_list*` -- pointer to the list of blinded contact structs; the +/// pointer belongs to the caller and must be freed when done with it. +LIBSESSION_EXPORT contacts_blinded_contact_list* contacts_blinded_contacts( + const config_object* conf); + +/// API: contacts/contacts_get_blinded_contact +/// +/// Fills `blinded_contact` with the blinded contact info given a blinded session ID (specified as a +/// null-terminated hex string), if the blinded contact exists, and returns true. If the contact +/// does not exist then `blinded_contact` is left unchanged and false is returned. +/// +/// Declaration: +/// ```cpp +/// BOOL contacts_get_blinded_contact( +/// [in] config_object* conf, +/// [in] const char* blinded_session_id, +/// [in] bool legacy_blinding, +/// [out] contacts_blinded_contact* blinded_contact +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `blinded_session_id` -- [in] null terminated hex string +/// - `legacy_blinding` -- [in] null terminated hex string +/// - `blinded_contact` -- [out] the blinded contact info data +/// +/// Output: +/// - `bool` -- Returns true if blinded contact exists +LIBSESSION_EXPORT bool contacts_get_blinded_contact( + config_object* conf, + const char* blinded_session_id, + bool legacy_blinding, + contacts_blinded_contact* blinded_contact) LIBSESSION_WARN_UNUSED; + +/// API: contacts/contacts_set_blinded_contact +/// +/// Adds or updates a blinded contact from the given contact info struct. +/// +/// Declaration: +/// ```cpp +/// BOOL contacts_set_blinded_contact( +/// [in] config_object* conf, +/// [in] contacts_blinded_contact* bc +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `blinded_contact` -- [in] the blinded contact info data +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool contacts_set_blinded_contact( + config_object* conf, const contacts_blinded_contact* bc); + +/// API: contacts/contacts_erase_blinded_contact +/// +/// Erases a blinded contact from the blinded contact list. blinded_id is in hex. Returns true if +/// the blinded contact was found and removed, false if the blinded contact was not present. +/// +/// Declaration: +/// ```cpp +/// BOOL contacts_erase_blinded_contact( +/// [in, out] config_object* conf, +/// [in] const char* base_url, +/// [in] const char* blinded_id, +/// [in] bool legacy_blinding +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in, out] Pointer to the config object +/// - `base_url` -- [in] Text containing null terminated base url for the community this blinded +/// contact originated from +/// - `blinded_id` -- [in] Text containing null terminated hex string +/// - `legacy_blinding` -- [in] Flag indicating whether this blinded contact used legacy blinding +/// +/// Outputs: +/// - `bool` -- True if erasing was successful +LIBSESSION_EXPORT bool contacts_erase_blinded_contact( + config_object* conf, const char* base_url, const char* blinded_id, bool legacy_blinding); + typedef struct contacts_iterator { void* _internals; } contacts_iterator; diff --git a/include/session/config/contacts.hpp b/include/session/config/contacts.hpp index 757e6cd..f038ec1 100644 --- a/include/session/config/contacts.hpp +++ b/include/session/config/contacts.hpp @@ -7,12 +7,14 @@ #include #include "base.hpp" +#include "community.hpp" #include "expiring.hpp" #include "namespaces.hpp" #include "notify.hpp" #include "profile_pic.hpp" extern "C" struct contacts_contact; +extern "C" struct contacts_blinded_contact; using namespace std::literals; @@ -44,8 +46,23 @@ namespace session::config { /// E - Disappearing message timer, in seconds. Omitted when `e` is omitted. /// j - Unix timestamp (seconds) when the contact was created ("j" to match user_groups /// equivalent "j"oined field). Omitted if 0. +/// +/// b - dict of blinded contacts. This is a nested dict where the outer keys are the BASE_URL of +/// the community the blinded contact originated from and the outer value is a dict containing: +/// `#` - the 32-byte server pubkey +/// `R` - dict of blinded contacts from the server; each key is the blinded session pubkey +/// without the prefix ("R" to match user_groups equivalent "R"oom field, and to make use of +/// existing community iterators, binary, 32 bytes), value is a dict containing keys: +/// +/// n - contact name (string). This is always serialized, even if empty (but empty indicates +/// no name) so that we always have at least one key set (required to keep the dict value +/// alive as empty dicts get pruned). +/// p - profile url (string) +/// q - profile decryption key (binary) +/// j - Unix timestamp (seconds) when the contact was created ("j" to match user_groups +/// equivalent "j"oined field). Omitted if 0. +/// y - flag indicating whether the blinded message request is using legac"y" blinding. -/// Struct containing contact info. struct contact_info { static constexpr size_t MAX_NAME_LENGTH = 100; @@ -97,6 +114,55 @@ struct contact_info { void load(const dict& info_dict); }; +struct blinded_contact_info { + community comm; + + const std::string session_id() const; // in hex + std::string name; + profile_pic profile_picture; + bool legacy_blinding; + int64_t created = 0; // Unix timestamp (seconds) when this contact was added + + blinded_contact_info() = default; + explicit blinded_contact_info( + std::string_view base_url, + std::string_view blinded_id, + std::span pubkey, + bool legacy_blinding); + + // Internal ctor/method for C API implementations: + blinded_contact_info(const struct contacts_blinded_contact& c); // From c struct + + /// API: contacts/blinded_contact_info::into + /// + /// converts the contact info into a c struct + /// + /// Inputs: + /// - `c` -- Return Parameter that will be filled with data in blinded_contact_info + void into(contacts_blinded_contact& c) const; + + /// API: contacts/contact_info::set_name + /// + /// Sets a name; this is exactly the same as assigning to .name directly, + /// except that we throw an exception if the given name is longer than MAX_NAME_LENGTH. + /// + /// Inputs: + /// - `name` -- Name to assign to the contact + void set_name(std::string name); + + /// These functions are here so we can use the `comm_iterator_helper` for loading data + /// into this struct + void set_base_url(std::string_view base_url); + void set_room(std::string_view room); + void set_pubkey(std::span pubkey); + void set_pubkey(std::string_view pubkey); + + private: + friend class Contacts; + friend struct session::config::comm_iterator_helper; + void load(const dict& info_dict); +}; + class Contacts : public ConfigBase { public: @@ -339,6 +405,70 @@ class Contacts : public ConfigBase { bool accepts_protobuf() const override { return true; } + protected: + // Drills into the nested dicts to access community details + DictFieldProxy blinded_contact_field( + const blinded_contact_info& bc, + std::span* get_pubkey = nullptr) const; + + public: + /// API: contacts/Contacts::blinded_contacts + /// + /// Retrieves a list of all known blinded contacts. + /// + /// Inputs: None + /// + /// Outputs: + /// - `std::vector` - Returns a list of blinded_contact_info + std::vector blinded_contacts() const; + + /// API: contacts/Contacts::get_blinded + /// + /// Looks up and returns a blinded contact by blinded session ID (hex). Returns nullopt if the + /// blinded session ID was not found, otherwise returns a filled out `blinded_contact_info`. + /// + /// Inputs: + /// - `pubkey_hex` -- hex string of the session id + /// - `legacy_blinding` -- flag indicating whether the pubkey is using legacy blinding + /// + /// Outputs: + /// - `std::optional` - Returns nullopt if blinded session ID was not + /// found, otherwise a filled out blinded_contact_info + std::optional get_blinded( + std::string_view pubkey_hex, bool legacy_blinding) const; + + /// API: contacts/contacts::set_blinded_contact + /// + /// Sets or updates multiple blinded contact info values at once with the given info. The usual + /// use is to access the current info, change anything desired, then pass it back into + /// set_blinded_contact, e.g.: + /// + ///```cpp + /// auto c = contacts.get_blinded(pubkey, legacy_blinding); + /// c.name = "Session User 42"; + /// contacts.set_blinded_contact(c); + ///``` + /// + /// Inputs: + /// - `bc` -- set_blinded_contact value to set + bool set_blinded_contact(const blinded_contact_info& bc); + + /// API: contacts/contacts::erase_blinded_contact + /// + /// Removes a blinded contact, if present. Returns true if it was found and removed, false + /// otherwise. Note that this removes all fields related to a blinded contact, even fields we do + /// not know about. + /// + /// Inputs: + /// - `base_url` -- the base url for the community this blinded contact originated from + /// - `blinded_id` -- hex string of the blinded id + /// - `legacy_blinding` -- flag indicating whether `blinded_id` is using legacy blinding + /// + /// Outputs: + /// - `bool` - Returns true if contact was found and removed, false otherwise + bool erase_blinded_contact( + std::string_view base_url, std::string_view blinded_id, bool legacy_blinding); + struct iterator; /// API: contacts/contacts::begin /// diff --git a/include/session/config/convo_info_volatile.h b/include/session/config/convo_info_volatile.h index 952b6ff..94de509 100644 --- a/include/session/config/convo_info_volatile.h +++ b/include/session/config/convo_info_volatile.h @@ -38,6 +38,14 @@ typedef struct convo_info_volatile_legacy_group { bool unread; // true if marked unread } convo_info_volatile_legacy_group; +typedef struct convo_info_volatile_blinded_1to1 { + char blinded_session_id[67]; // in hex; 66 hex chars + null terminator. + bool legacy_blinding; + + int64_t last_read; // ms since unix epoch + bool unread; // true if the conversation is explicitly marked unread +} convo_info_volatile_blinded_1to1; + /// API: convo_info_volatile/convo_info_volatile_init /// /// Constructs a conversations config object and sets a pointer to it in `conf`. @@ -345,6 +353,76 @@ LIBSESSION_EXPORT bool convo_info_volatile_get_or_construct_legacy_group( convo_info_volatile_legacy_group* convo, const char* id) LIBSESSION_WARN_UNUSED; +/// API: convo_info_volatile/convo_info_volatile_get_blinded_1to1 +/// +/// Fills `convo` with the conversation info given a blinded session ID (specified as a +/// null-terminated hex string), if the conversation exists, and returns true. If the conversation +/// does not exist then `convo` is left unchanged and false is returned. If an error occurs, false +/// is returned and `conf->last_error` will be set to non-NULL containing the error string (if no +/// error occurs, such as in the case where the conversation merely doesn't exist, `last_error` will +/// be set to NULL). +/// +/// Declaration: +/// ```cpp +/// BOOL convo_info_volatile_get_blinded_1to1( +/// [in] config_object* conf, +/// [out] convo_info_volatile_blinded_1to1* convo, +/// [in] const char* blinded_session_id +/// [in] bool legacy_blinding +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `convo` -- [out] Pointer to conversation info +/// - `blinded_session_id` -- [in] Null terminated hex string of the session_id +/// - `legacy_blinding` -- flag indicating whether this blinded contact should use legacy blinding +/// +/// Outputs: +/// - `bool` - Returns true if the conversation exists +LIBSESSION_EXPORT bool convo_info_volatile_get_blinded_1to1( + config_object* conf, + convo_info_volatile_blinded_1to1* convo, + const char* blinded_session_id, + bool legacy_blinding) LIBSESSION_WARN_UNUSED; + +/// API: convo_info_volatile/convo_info_volatile_get_or_construct_blinded_1to1 +/// +/// Same as the above convo_info_volatile_get_blinded_1to1 except that when the conversation does +/// not exist, this sets all the convo fields to defaults and loads it with the given +/// blinded_session_id. +/// +/// Returns true as long as it is given a valid blinded_session_id. A false return is considered an +/// error, and means the blinded_session_id was not a valid blinded_session_id. In such a case +/// `conf->last_error` will be set to an error string. +/// +/// This is the method that should usually be used to create or update a conversation, followed by +/// setting fields in the convo, and then giving it to convo_info_volatile_set(). +/// +/// Declaration: +/// ```cpp +/// BOOL convo_info_volatile_get_or_construct_1to1( +/// [in] config_object* conf, +/// [out] convo_info_volatile_blinded_1to1* convo, +/// [in] const char* blinded_session_id +/// [in] bool legacy_blinding +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `convo` -- [out] Pointer to conversation info +/// - `blinded_session_id` -- [in] Null terminated hex string of the blinded session id +/// - `legacy_blinding` -- flag indicating whether this blinded contact should use legacy blinding +/// +/// Outputs: +/// - `bool` - Returns true if the conversation exists +LIBSESSION_EXPORT bool convo_info_volatile_get_or_construct_blinded_1to1( + config_object* conf, + convo_info_volatile_blinded_1to1* convo, + const char* blinded_session_id, + bool legacy_blinding) LIBSESSION_WARN_UNUSED; + /// API: convo_info_volatile/convo_info_volatile_set_1to1 /// /// Adds or updates a conversation from the given convo info @@ -429,6 +507,27 @@ LIBSESSION_EXPORT bool convo_info_volatile_set_group( LIBSESSION_EXPORT bool convo_info_volatile_set_legacy_group( config_object* conf, const convo_info_volatile_legacy_group* convo); +/// API: convo_info_volatile/convo_info_volatile_set_blinded_1to1 +/// +/// Adds or updates a conversation from the given convo info +/// +/// Declaration: +/// ```cpp +/// VOID convo_info_volatile_set_blinded_1to1( +/// [in] config_object* conf, +/// [in] const convo_info_volatile_blidned_1to1* convo +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `convo` -- [in] Pointer to conversation info structure +/// +/// Output: +/// - `bool` -- Returns true if the call succeeds, false if an error occurs. +LIBSESSION_EXPORT bool convo_info_volatile_set_blinded_1to1( + config_object* conf, const convo_info_volatile_blinded_1to1* convo); + /// API: convo_info_volatile/convo_info_volatile_erase_1to1 /// /// Erases a conversation from the conversation list. Returns true if the conversation was found @@ -520,6 +619,31 @@ LIBSESSION_EXPORT bool convo_info_volatile_erase_group(config_object* conf, cons LIBSESSION_EXPORT bool convo_info_volatile_erase_legacy_group( config_object* conf, const char* group_id); +/// API: convo_info_volatile/convo_info_volatile_erase_blinded_1to1 +/// +/// Erases a conversation from the conversation list. Returns true if the conversation was found +/// and removed, false if the conversation was not present. You must not call this during +/// iteration; see details below. +/// +/// Declaration: +/// ```cpp +/// BOOL convo_info_volatile_erase_blinded_1to1( +/// [in] config_object* conf, +/// [in] const char* blinded_session_id +/// [in] bool legacy_blinding +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `blinded_session_id` -- [in] Null terminated hex string +/// - `legacy_blinding` -- flag indicating whether the blinded contact used legacy blinding +/// +/// Outputs: +/// - `bool` - Returns true if conversation was found and removed +LIBSESSION_EXPORT bool convo_info_volatile_erase_blinded_1to1( + config_object* conf, const char* blinded_session_id, bool legacy_blinding); + /// API: convo_info_volatile/convo_info_volatile_size /// /// Returns the number of conversations. @@ -610,6 +734,24 @@ LIBSESSION_EXPORT size_t convo_info_volatile_size_groups(const config_object* co /// - `size_t` -- number of legacy groups LIBSESSION_EXPORT size_t convo_info_volatile_size_legacy_groups(const config_object* conf); +/// API: convo_info_volatile/convo_info_volatile_size_blinded_1to1 +/// +/// Returns the number of conversations. +/// +/// Declaration: +/// ```cpp +/// SIZE_T convo_info_volatile_size_blinded_1to1( +/// [in] const config_object* conf +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `size_t` -- number of conversations +LIBSESSION_EXPORT size_t convo_info_volatile_size_blinded_1to1(const config_object* conf); + typedef struct convo_info_volatile_iterator convo_info_volatile_iterator; /// API: convo_info_volatile/convo_info_volatile_iterator_new @@ -622,6 +764,7 @@ typedef struct convo_info_volatile_iterator convo_info_volatile_iterator; /// convo_info_volatile_community c2; /// convo_info_volatile_group c3; /// convo_info_volatile_legacy_group c4; +/// convo_info_volatile_blinded_1to1 c5; /// convo_info_volatile_iterator *it = convo_info_volatile_iterator_new(my_convos); /// for (; !convo_info_volatile_iterator_done(it); convo_info_volatile_iterator_advance(it)) { /// if (convo_info_volatile_it_is_1to1(it, &c1)) { @@ -632,6 +775,8 @@ typedef struct convo_info_volatile_iterator convo_info_volatile_iterator; /// // use c3.whatever /// } else if (convo_info_volatile_it_is_legacy_group(it, &c4)) { /// // use c4.whatever +/// } else if (convo_info_volatile_it_is_blinded_1to1(it, &c5)) { +/// // use c5.whatever /// } /// } /// convo_info_volatile_iterator_free(it); @@ -747,6 +892,29 @@ LIBSESSION_EXPORT convo_info_volatile_iterator* convo_info_volatile_iterator_new LIBSESSION_EXPORT convo_info_volatile_iterator* convo_info_volatile_iterator_new_legacy_groups( const config_object* conf); +/// API: convo_info_volatile/convo_info_volatile_iterator_new_blinded_1to1 +/// +/// The same as `convo_info_volatile_iterator_new` except that this iterates *only* over one type of +/// conversation. You still need to use `convo_info_volatile_it_is_blinded_1to1` (or the +/// alternatives) to load the data in each pass of the loop. (You can, however, safely ignore the +/// bool return value of the `it_is_whatever` function: it will always be true for the particular +/// type being iterated over). +/// +/// Declaration: +/// ```cpp +/// CONVO_INFO_VOLATILE_ITERATOR* convo_info_volatile_iterator_new_blinded_1to1( +/// [in] const config_object* conf +/// ); +/// ``` +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `convo_info_volatile_iterator*` -- Iterator +LIBSESSION_EXPORT convo_info_volatile_iterator* convo_info_volatile_iterator_new_blinded_1to1( + const config_object* conf); + /// API: convo_info_volatile/convo_info_volatile_iterator_free /// /// Frees an iterator once no longer needed. @@ -883,6 +1051,28 @@ LIBSESSION_EXPORT bool convo_info_volatile_it_is_group( LIBSESSION_EXPORT bool convo_info_volatile_it_is_legacy_group( convo_info_volatile_iterator* it, convo_info_volatile_legacy_group* c); +/// API: convo_info_volatile/convo_info_volatile_it_is_blinded_1to1 +/// +/// If the current iterator record is a blinded 1-to-1 conversation this sets the details into `c` +/// and returns true. Otherwise it returns false. +/// +/// Declaration: +/// ```cpp +/// BOOL convo_info_volatile_it_is_blinded_1to1( +/// [in] convo_info_volatile_iterator* it, +/// [out] convo_info_volatile_blinded_1to1* c +/// ); +/// ``` +/// +/// Inputs: +/// - `it` -- [in] The convo_info_volatile_iterator +/// - `c` -- [out] Pointer to the convo_info_volatile, will be populated if true +/// +/// Outputs: +/// - `bool` -- True if the record is a blinded 1-to-1 conversation +LIBSESSION_EXPORT bool convo_info_volatile_it_is_blinded_1to1( + convo_info_volatile_iterator* it, convo_info_volatile_blinded_1to1* c); + #ifdef __cplusplus } // extern "C" #endif diff --git a/include/session/config/convo_info_volatile.hpp b/include/session/config/convo_info_volatile.hpp index 3871a69..ce954dd 100644 --- a/include/session/config/convo_info_volatile.hpp +++ b/include/session/config/convo_info_volatile.hpp @@ -16,6 +16,7 @@ struct convo_info_volatile_1to1; struct convo_info_volatile_community; struct convo_info_volatile_group; struct convo_info_volatile_legacy_group; +struct convo_info_volatile_blinded_1to1; } namespace session::config { @@ -55,6 +56,13 @@ class val_loader; /// r - the unix timestamp (integer milliseconds) of the last-read message. Always included, /// but will be 0 if no messages are read. /// u - will be present and set to 1 if this conversation is specifically marked unread. +/// +/// b - outgoing blinded message request conversations. The key is the blinded Session ID without +/// the prefix. Values are dicts with keys: +/// r - the unix timestamp (integer milliseconds) of the last-read message. Always included, +/// but will be 0 if no messages are read. +/// u - will be present and set to 1 if this conversation is specifically marked unread. +/// y - flag indicating whether the blinded message request is using legac"y" blinding. namespace convo { @@ -149,7 +157,34 @@ namespace convo { void into(convo_info_volatile_legacy_group& c) const; // Into c struct }; - using any = std::variant; + struct blinded_one_to_one : base { + std::string blinded_session_id; // in hex + bool legacy_blinding; + + /// API: convo_info_volatile/blinded_one_to_one::blinded_one_to_one + /// + /// Constructs an empty blinded_one_to_one from a blinded_session_id. Session ID can be + /// either bytes (33) or hex (66). + /// + /// Declaration: + /// ```cpp + /// explicit blinded_one_to_one(std::string&& blinded_session_id); + /// explicit blinded_one_to_one(std::string_view blinded_session_id); + /// ``` + /// + /// Inputs: + /// - `blinded_session_id` -- Hex string of the blinded session id + /// - `legacy_blinding` -- flag indicating whether this blinded contact should use legacy + /// blinding + explicit blinded_one_to_one(std::string&& blinded_session_id, bool legacy_blinding); + explicit blinded_one_to_one(std::string_view blinded_session_id, bool legacy_blinding); + + // Internal ctor/method for C API implementations: + blinded_one_to_one(const struct convo_info_volatile_blinded_1to1& c); // From c struct + void into(convo_info_volatile_blinded_1to1& c) const; // Into c struct + }; + + using any = std::variant; } // namespace convo class ConvoInfoVolatile : public ConfigBase { @@ -298,6 +333,22 @@ class ConvoInfoVolatile : public ConfigBase { /// - `std::optional` - Returns a group std::optional get_legacy_group(std::string_view pubkey_hex) const; + /// API: convo_info_volatile/ConvoInfoVolatile::get_blinded_1to1 + /// + /// Looks up and returns a blinded contact by blinded session ID (hex). Returns nullopt if the + /// blinded session ID was not found, otherwise returns a filled out + /// `convo::blinded_one_to_one`. + /// + /// Inputs: + /// - `blinded_session_id` -- Hex string of the blinded Session ID + /// - `legacy_blinding` -- flag indicating whether this blinded contact should use legacy + /// blinding + /// + /// Outputs: + /// - `std::optional` - Returns a contact + std::optional get_blinded_1to1( + std::string_view blinded_session_id, bool legacy_blinding) const; + /// API: convo_info_volatile/ConvoInfoVolatile::get_or_construct_1to1 /// /// These are the same as the above `get` methods (without "_or_construct" in the name), except @@ -385,6 +436,22 @@ class ConvoInfoVolatile : public ConfigBase { /// - `convo::community` - Returns a group convo::community get_or_construct_community(std::string_view full_url) const; + /// API: convo_info_volatile/ConvoInfoVolatile::get_or_construct_blinded_1to1 + /// + /// These are the same as the above `get` methods (without "_or_construct" in the name), except + /// that when the conversation doesn't exist a new one is created, prefilled with the + /// pubkey/url/etc. + /// + /// Inputs: + /// - `blinded_session_id` -- Hex string blinded Session ID + /// - `legacy_blinding` -- flag indicating whether this blinded contact should use legacy + /// blinding + /// + /// Outputs: + /// - `convo::blinded_one_to_one` - Returns a blinded contact + convo::blinded_one_to_one get_or_construct_blinded_1to1( + std::string_view blinded_session_id, bool legacy_blinding) const; + /// API: convo_info_volatile/ConvoInfoVolatile::set /// /// Inserts or replaces existing conversation info. For example, to update a 1-to-1 @@ -402,6 +469,7 @@ class ConvoInfoVolatile : public ConfigBase { /// void set(const convo::group& c); /// void set(const convo::legacy_group& c); /// void set(const convo::community& c); + /// void set(const convo::blinded_one_to_one& c); /// void set(const convo::any& c); // Variant which can be any of the above /// ``` /// @@ -411,6 +479,7 @@ class ConvoInfoVolatile : public ConfigBase { void set(const convo::legacy_group& c); void set(const convo::group& c); void set(const convo::community& c); + void set(const convo::blinded_one_to_one& c); void set(const convo::any& c); // Variant which can be any of the above protected: @@ -469,6 +538,19 @@ class ConvoInfoVolatile : public ConfigBase { /// - `bool` - Returns true if found and removed, otherwise false bool erase_legacy_group(std::string_view pubkey_hex); + /// API: convo_info_volatile/ConvoInfoVolatile::erase_blinded_1to1 + /// + /// Removes a blinded one-to-one conversation. Returns true if found and removed, false if not + /// present. + /// + /// Inputs: + /// - `pubkey` -- hex blinded session id + /// - `legacy_blinding` -- flag indicating whether this blinded contact is using legacy blinding + /// + /// Outputs: + /// - `bool` - Returns true if found and removed, otherwise false + bool erase_blinded_1to1(std::string_view pubkey, bool legacy_blinding); + /// API: convo_info_volatile/ConvoInfoVolatile::erase /// /// Removes a conversation taking the convo::whatever record (rather than the pubkey/url). @@ -478,6 +560,7 @@ class ConvoInfoVolatile : public ConfigBase { /// bool erase(const convo::one_to_one& c); /// bool erase(const convo::community& c); /// bool erase(const convo::legacy_group& c); + /// bool erase(const convo::blinded_one_to_one& c); /// bool erase(const convo::any& c); // Variant of any of them /// ``` /// @@ -490,6 +573,7 @@ class ConvoInfoVolatile : public ConfigBase { bool erase(const convo::community& c); bool erase(const convo::group& c); bool erase(const convo::legacy_group& c); + bool erase(const convo::blinded_one_to_one& c); bool erase(const convo::any& c); // Variant of any of them @@ -506,6 +590,7 @@ class ConvoInfoVolatile : public ConfigBase { /// size_t size_communities() const; /// size_t size_groups() const; /// size_t size_legacy_groups() const; + /// size_t size_blinded_1to1() const; /// ``` /// /// Inputs: None @@ -520,6 +605,7 @@ class ConvoInfoVolatile : public ConfigBase { size_t size_communities() const; size_t size_groups() const; size_t size_legacy_groups() const; + size_t size_blinded_1to1() const; /// API: convo_info_volatile/ConvoInfoVolatile::empty /// @@ -549,6 +635,8 @@ class ConvoInfoVolatile : public ConfigBase { /// // use cg->id, cg->last_read /// } else if (const auto* lcg = std::get_if(&convo)) { /// // use lcg->id, lcg->last_read + /// } else if (const auto* bc = std::get_if(&convo)) { + /// // use bc->id, bc->last_read /// } /// } /// ``` @@ -570,6 +658,7 @@ class ConvoInfoVolatile : public ConfigBase { /// subtype_iterator begin_communities() const; /// subtype_iterator begin_groups() const; /// subtype_iterator begin_legacy_groups() const; + /// subtype_iterator begin_blinded_one_to_one() const; /// ``` /// /// Inputs: None @@ -597,10 +686,15 @@ class ConvoInfoVolatile : public ConfigBase { subtype_iterator begin_communities() const { return {data}; } subtype_iterator begin_groups() const { return {data}; } subtype_iterator begin_legacy_groups() const { return {data}; } + subtype_iterator begin_blinded_1to1() const { return {data}; } using iterator_category = std::input_iterator_tag; - using value_type = - std::variant; + using value_type = std::variant< + convo::one_to_one, + convo::community, + convo::group, + convo::legacy_group, + convo::blinded_one_to_one>; using reference = value_type&; using pointer = value_type*; using difference_type = std::ptrdiff_t; @@ -609,7 +703,7 @@ class ConvoInfoVolatile : public ConfigBase { protected: std::shared_ptr _val; std::optional _it_11, _end_11, _it_group, _end_group, _it_lgroup, - _end_lgroup; + _end_lgroup, _it_b11, _end_b11; std::optional _it_comm; void _load_val(); iterator() = default; // Constructs an end tombstone @@ -618,8 +712,10 @@ class ConvoInfoVolatile : public ConfigBase { bool oneto1, bool communities, bool groups, - bool legacy_groups); - explicit iterator(const DictFieldRoot& data) : iterator(data, true, true, true, true) {} + bool legacy_groups, + bool blinded_1to1); + explicit iterator(const DictFieldRoot& data) : + iterator(data, true, true, true, true, true) {} friend class ConvoInfoVolatile; public: @@ -645,7 +741,8 @@ class ConvoInfoVolatile : public ConfigBase { std::is_same_v, std::is_same_v, std::is_same_v, - std::is_same_v) {} + std::is_same_v, + std::is_same_v) {} friend class ConvoInfoVolatile; public: diff --git a/src/config/contacts.cpp b/src/config/contacts.cpp index 093c0a9..0a70f1b 100644 --- a/src/config/contacts.cpp +++ b/src/config/contacts.cpp @@ -1,8 +1,12 @@ #include "session/config/contacts.hpp" +#include +#include #include #include +#include +#include #include #include "internal.hpp" @@ -14,8 +18,7 @@ using namespace std::literals; using namespace session::config; - -LIBSESSION_C_API const size_t CONTACT_MAX_NAME_LENGTH = contact_info::MAX_NAME_LENGTH; +using namespace oxen::log::literals; // Check for agreement between various C/C++ types static_assert(sizeof(contacts_contact::name) == contact_info::MAX_NAME_LENGTH + 1); @@ -61,15 +64,6 @@ Contacts::Contacts( load_key(ed25519_secretkey); } -LIBSESSION_C_API int contacts_init( - config_object** conf, - const unsigned char* ed25519_secretkey_bytes, - const unsigned char* dumpstr, - size_t dumplen, - char* error) { - return c_wrapper_init(conf, ed25519_secretkey_bytes, dumpstr, dumplen, error); -} - void contact_info::load(const dict& info_dict) { name = maybe_string(info_dict, "n").value_or(""); nickname = maybe_string(info_dict, "N").value_or(""); @@ -167,6 +161,89 @@ contact_info::contact_info(const contacts_contact& c) : session_id{c.session_id, created = to_epoch_seconds(c.created); } +blinded_contact_info::blinded_contact_info( + std::string_view base_url, + std::string_view blinded_id, + std::span pubkey, + bool legacy_blinding) : + comm{community(std::move(base_url), blinded_id.substr(2), std::move(pubkey))}, + legacy_blinding{legacy_blinding} { + check_session_id(blinded_id, legacy_blinding ? "15" : "25"); +} + +const std::string blinded_contact_info::session_id() const { + return "{}{}"_format(legacy_blinding ? "15" : "25", comm.room()); +} + +void blinded_contact_info::set_name(std::string n) { + if (n.size() > contact_info::MAX_NAME_LENGTH) + name = utf8_truncate(std::move(n), contact_info::MAX_NAME_LENGTH); + else + name = std::move(n); +} + +void blinded_contact_info::set_base_url(std::string_view base_url) { + comm.set_base_url(base_url); +} + +void blinded_contact_info::set_room(std::string_view room) { + comm.set_room(room); +} + +void blinded_contact_info::set_pubkey(std::span pubkey) { + comm.set_pubkey(pubkey); +} + +void blinded_contact_info::set_pubkey(std::string_view pubkey) { + comm.set_pubkey(pubkey); +} + +void blinded_contact_info::load(const dict& info_dict) { + name = maybe_string(info_dict, "n").value_or(""); + + auto url = maybe_string(info_dict, "p"); + auto key = maybe_vector(info_dict, "q"); + if (url && key && !url->empty() && key->size() == 32) { + profile_picture.url = std::move(*url); + profile_picture.key = std::move(*key); + } else { + profile_picture.clear(); + } + legacy_blinding = maybe_int(info_dict, "y").value_or(0); + created = to_epoch_seconds(maybe_int(info_dict, "j").value_or(0)); +} + +void blinded_contact_info::into(contacts_blinded_contact& c) const { + copy_c_str(c.base_url, comm.base_url()); + c.session_id[0] = (legacy_blinding ? '1' : '2'); + c.session_id[1] = '5'; + std::memcpy(c.session_id + 2, session_id().data(), 64); + c.session_id[66] = '\0'; + std::memcpy(c.pubkey, comm.pubkey().data(), 32); + copy_c_str(c.name, name); + if (profile_picture) { + copy_c_str(c.profile_pic.url, profile_picture.url); + std::memcpy(c.profile_pic.key, profile_picture.key.data(), 32); + } else { + copy_c_str(c.profile_pic.url, ""); + } + c.legacy_blinding = legacy_blinding; + c.created = to_epoch_seconds(created); +} + +blinded_contact_info::blinded_contact_info(const contacts_blinded_contact& c) { + comm = community(c.base_url, {c.session_id + 2, 64}, c.pubkey); + assert(std::strlen(c.name) <= contact_info::MAX_NAME_LENGTH); + name = c.name; + assert(std::strlen(c.profile_pic.url) <= profile_pic::MAX_URL_LENGTH); + if (std::strlen(c.profile_pic.url)) { + profile_picture.url = c.profile_pic.url; + profile_picture.key.assign(c.profile_pic.key, c.profile_pic.key + 32); + } + legacy_blinding = c.legacy_blinding; + created = to_epoch_seconds(c.created); +} + std::optional Contacts::get(std::string_view pubkey_hex) const { std::string pubkey = session_id_to_bytes(pubkey_hex); @@ -179,20 +256,6 @@ std::optional Contacts::get(std::string_view pubkey_hex) const { return result; } -LIBSESSION_C_API bool contacts_get( - config_object* conf, contacts_contact* contact, const char* session_id) { - return wrap_exceptions( - conf, - [&] { - if (auto c = unbox(conf)->get(session_id)) { - c->into(*contact); - return true; - } - return false; - }, - false); -} - contact_info Contacts::get_or_construct(std::string_view pubkey_hex) const { if (auto maybe = get(pubkey_hex)) return *std::move(maybe); @@ -200,17 +263,6 @@ contact_info Contacts::get_or_construct(std::string_view pubkey_hex) const { return contact_info{std::string{pubkey_hex}}; } -LIBSESSION_C_API bool contacts_get_or_construct( - config_object* conf, contacts_contact* contact, const char* session_id) { - return wrap_exceptions( - conf, - [&] { - unbox(conf)->get_or_construct(session_id).into(*contact); - return true; - }, - false); -} - void Contacts::set(const contact_info& contact) { std::string pk = session_id_to_bytes(contact.session_id); auto info = data["c"][pk]; @@ -249,16 +301,6 @@ void Contacts::set(const contact_info& contact) { set_positive_int(info["j"], to_epoch_seconds(contact.created)); } -LIBSESSION_C_API bool contacts_set(config_object* conf, const contacts_contact* contact) { - return wrap_exceptions( - conf, - [&] { - unbox(conf)->set(contact_info{*contact}); - return true; - }, - false); -} - void Contacts::set_name(std::string_view session_id, std::string name) { auto c = get_or_construct(session_id); c.set_name(std::move(name)); @@ -329,22 +371,92 @@ bool Contacts::erase(std::string_view session_id) { return ret; } -LIBSESSION_C_API bool contacts_erase(config_object* conf, const char* session_id) { - try { - return unbox(conf)->erase(session_id); - } catch (...) { - return false; - } -} - size_t Contacts::size() const { if (auto* c = data["c"].dict()) return c->size(); return 0; } -LIBSESSION_C_API size_t contacts_size(const config_object* conf) { - return unbox(conf)->size(); +ConfigBase::DictFieldProxy Contacts::blinded_contact_field( + const blinded_contact_info& bc, std::span* get_pubkey) const { + auto record = data["b"][bc.comm.base_url()]; + if (get_pubkey) { + auto pkrec = record["#"]; + if (auto pk = pkrec.string_view_or(""); pk.size() == 32) + *get_pubkey = std::span{ + reinterpret_cast(pk.data()), pk.size()}; + } + return record["R"][bc.comm.room()]; // The `room` value is the blinded id without the prefix +} + +using any_blinded_contact = std::variant; + +std::optional Contacts::get_blinded( + std::string_view pubkey_hex, bool legacy_blinding) const { + check_session_id(pubkey_hex, legacy_blinding ? "15" : "25"); + + if (auto* b = data["b"].dict()) { + auto comm = comm_iterator_helper{b->begin(), b->end()}; + std::shared_ptr val; + + while (!comm.done()) { + if (comm.load(val)) + if (auto* ptr = std::get_if(val.get()); + ptr && ptr->session_id() == pubkey_hex) + return *ptr; + comm.advance(); + } + } + + return std::nullopt; +} + +std::vector Contacts::blinded_contacts() const { + std::vector ret; + + if (auto* b = data["b"].dict()) { + auto comm = comm_iterator_helper{b->begin(), b->end()}; + std::shared_ptr val; + + while (!comm.done()) { + if (comm.load(val)) + if (auto* ptr = std::get_if(val.get())) + ret.emplace_back(*ptr); + comm.advance(); + } + } + + return ret; +} + +bool Contacts::set_blinded_contact(const blinded_contact_info& bc) { + data["b"][bc.comm.base_url()]["#"] = bc.comm.pubkey(); + auto info = blinded_contact_field(bc); // data["b"][base]["R"][bc_session_id_without_prefix] + + // Always set the name, even if empty, to keep the dict from getting pruned if there are no + // other entries. + info["n"] = bc.name.substr(0, contact_info::MAX_NAME_LENGTH); + + set_pair_if( + bc.profile_picture, + info["p"], + bc.profile_picture.url, + info["q"], + bc.profile_picture.key); + + set_positive_int(info["y"], bc.legacy_blinding); + set_positive_int(info["j"], to_epoch_seconds(bc.created)); +} + +bool Contacts::erase_blinded_contact( + std::string_view base_url_, std::string_view blinded_id, bool legacy_blinding) { + std::string pk = session_id_to_bytes(blinded_id, legacy_blinding ? "15" : "25").substr(2); + + auto base_url = community::canonical_url(base_url_); + auto info = data["d"][base_url]["R"][pk]; + bool ret = info.exists(); + info.erase(); + return ret; } /// Load _val from the current iterator position; if it is invalid, skip to the next key until we @@ -387,6 +499,149 @@ Contacts::iterator& Contacts::iterator::operator++() { return *this; } +extern "C" { + +LIBSESSION_C_API const size_t CONTACT_MAX_NAME_LENGTH = contact_info::MAX_NAME_LENGTH; + +LIBSESSION_C_API int contacts_init( + config_object** conf, + const unsigned char* ed25519_secretkey_bytes, + const unsigned char* dumpstr, + size_t dumplen, + char* error) { + return c_wrapper_init(conf, ed25519_secretkey_bytes, dumpstr, dumplen, error); +} + +LIBSESSION_C_API bool contacts_get( + config_object* conf, contacts_contact* contact, const char* session_id) { + return wrap_exceptions( + conf, + [&] { + if (auto c = unbox(conf)->get(session_id)) { + c->into(*contact); + return true; + } + return false; + }, + false); +} + +LIBSESSION_C_API bool contacts_get_or_construct( + config_object* conf, contacts_contact* contact, const char* session_id) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->get_or_construct(session_id).into(*contact); + return true; + }, + false); +} + +LIBSESSION_C_API bool contacts_set(config_object* conf, const contacts_contact* contact) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set(contact_info{*contact}); + return true; + }, + false); +} + +LIBSESSION_C_API bool contacts_erase(config_object* conf, const char* session_id) { + try { + return unbox(conf)->erase(session_id); + } catch (...) { + return false; + } +} + +LIBSESSION_C_API size_t contacts_size(const config_object* conf) { + return unbox(conf)->size(); +} + +LIBSESSION_C_API bool contacts_get_blinded_contact( + config_object* conf, + const char* blinded_session_id, + bool legacy_blinding, + contacts_blinded_contact* blinded_contact) { + return wrap_exceptions( + conf, + [&] { + if (auto bc = unbox(conf)->get_blinded( + blinded_session_id, legacy_blinding)) { + bc->into(*blinded_contact); + return true; + } + return false; + }, + false); +} + +LIBSESSION_C_API contacts_blinded_contact_list* contacts_blinded_contacts( + const config_object* conf) { + try { + auto cpp_contacts = unbox(conf)->blinded_contacts(); + + if (cpp_contacts.empty()) + return nullptr; + + // We malloc space for the contacts_blinded_contact_list struct itself, plus the required + // number of contacts_blinded_contact pointers to store its records, and the space to + // actually contain a copy of the data. When we're done, the malloced memory we grab is + // going to look like this: + // + // {contacts_blinded_contact_list} + // {pointer1}{pointer2}... + // {contacts_blinded_contact data 1\0}{contacts_blinded_contact data 2\0}... + // + // where contacts_blinded_contact.value points at the beginning of {pointer1}, and each + // pointerN points at the beginning of the {contacts_blinded_contact data N\0} struct. + // + // Since we malloc it all at once, when the user frees it, they also free the entire thing. + size_t sz = sizeof(contacts_blinded_contact_list) + + (cpp_contacts.size() * sizeof(contacts_blinded_contact*)) + + (cpp_contacts.size() * sizeof(contacts_blinded_contact)); + auto* ret = static_cast(std::malloc(sz)); + ret->len = cpp_contacts.size(); + + // value points at the space immediately after the struct itself, which is the first element + // in the array of contacts_blinded_contact pointers. + ret->value = reinterpret_cast(ret + 1); + contacts_blinded_contact* next_struct = + reinterpret_cast(ret->value + ret->len); + + for (size_t i = 0; i < cpp_contacts.size(); ++i) { + ret->value[i] = next_struct; + cpp_contacts[i].into(*next_struct); + next_struct++; + } + + return ret; + } catch (...) { + return nullptr; + } +} + +LIBSESSION_C_API bool contacts_set_blinded_contact( + config_object* conf, const contacts_blinded_contact* bc) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set_blinded_contact(blinded_contact_info{*bc}); + return true; + }, + false); +} + +LIBSESSION_C_API bool contacts_erase_blinded_contact( + config_object* conf, const char* base_url, const char* blinded_id, bool legacy_blinding) { + try { + return unbox(conf)->erase_blinded_contact(base_url, blinded_id, legacy_blinding); + } catch (...) { + return false; + } +} + LIBSESSION_C_API contacts_iterator* contacts_iterator_new(const config_object* conf) { auto* it = new contacts_iterator{}; it->_internals = new Contacts::iterator{unbox(conf)->begin()}; @@ -409,3 +664,5 @@ LIBSESSION_C_API bool contacts_iterator_done(contacts_iterator* it, contacts_con LIBSESSION_C_API void contacts_iterator_advance(contacts_iterator* it) { ++*static_cast(it->_internals); } + +} // extern "C" \ No newline at end of file diff --git a/src/config/convo_info_volatile.cpp b/src/config/convo_info_volatile.cpp index 8d1206c..776e969 100644 --- a/src/config/convo_info_volatile.cpp +++ b/src/config/convo_info_volatile.cpp @@ -82,6 +82,26 @@ namespace convo { c.unread = unread; } + blinded_one_to_one::blinded_one_to_one(std::string&& sid, bool legacy_blinding) : + blinded_session_id{std::move(sid)}, legacy_blinding{legacy_blinding} { + check_session_id(blinded_session_id, legacy_blinding ? "15" : "25"); + } + blinded_one_to_one::blinded_one_to_one(std::string_view sid, bool legacy_blinding) : + blinded_session_id{sid}, legacy_blinding{legacy_blinding} { + check_session_id(blinded_session_id, legacy_blinding ? "15" : "25"); + } + blinded_one_to_one::blinded_one_to_one(const convo_info_volatile_blinded_1to1& c) : + base{c.last_read, c.unread}, + blinded_session_id{c.blinded_session_id, 66}, + legacy_blinding{c.legacy_blinding} {} + + void blinded_one_to_one::into(convo_info_volatile_blinded_1to1& c) const { + std::memcpy(c.blinded_session_id, blinded_session_id.data(), 67); + c.last_read = last_read; + c.unread = unread; + c.legacy_blinding = legacy_blinding; + } + void base::load(const dict& info_dict) { last_read = maybe_int(info_dict, "r").value_or(0); unread = (bool)maybe_int(info_dict, "u").value_or(0); @@ -213,6 +233,28 @@ convo::legacy_group ConvoInfoVolatile::get_or_construct_legacy_group( return convo::legacy_group{std::string{pubkey_hex}}; } +std::optional ConvoInfoVolatile::get_blinded_1to1( + std::string_view pubkey_hex, bool legacy_blinding) const { + std::string pubkey = session_id_to_bytes(pubkey_hex, legacy_blinding ? "15" : "25"); + + auto* info_dict = data["b"][pubkey].dict(); + if (!info_dict) + return std::nullopt; + + auto result = + std::make_optional(std::string{pubkey_hex}, legacy_blinding); + result->load(*info_dict); + return result; +} + +convo::blinded_one_to_one ConvoInfoVolatile::get_or_construct_blinded_1to1( + std::string_view pubkey_hex, bool legacy_blinding) const { + if (auto maybe = get_blinded_1to1(pubkey_hex, legacy_blinding)) + return *std::move(maybe); + + return convo::blinded_one_to_one{std::string{pubkey_hex}, legacy_blinding}; +} + void ConvoInfoVolatile::set(const convo::one_to_one& c) { auto info = data["1"][session_id_to_bytes(c.session_id)]; set_base(c, info); @@ -286,6 +328,14 @@ void ConvoInfoVolatile::set(const convo::legacy_group& c) { set_base(c, info); } +void ConvoInfoVolatile::set(const convo::blinded_one_to_one& c) { + std::string pubkey = session_id_to_bytes(c.blinded_session_id, c.legacy_blinding ? "15" : "25"); + + auto info = data["b"][pubkey]; + set_nonzero_int(info["y"], c.legacy_blinding); + set_base(c, info); +} + template static bool erase_impl(Field convo) { bool ret = convo.exists(); @@ -315,6 +365,11 @@ bool ConvoInfoVolatile::erase(const convo::group& c) { bool ConvoInfoVolatile::erase(const convo::legacy_group& c) { return erase_impl(data["C"][session_id_to_bytes(c.id)]); } +bool ConvoInfoVolatile::erase(const convo::blinded_one_to_one& c) { + std::string pubkey = session_id_to_bytes(c.blinded_session_id, c.legacy_blinding ? "15" : "25"); + + return erase_impl(data["b"][pubkey]); +} bool ConvoInfoVolatile::erase(const convo::any& c) { return std::visit([this](const auto& c) { return erase(c); }, c); @@ -331,6 +386,10 @@ bool ConvoInfoVolatile::erase_group(std::string_view id) { bool ConvoInfoVolatile::erase_legacy_group(std::string_view id) { return erase(convo::legacy_group{id}); } +bool ConvoInfoVolatile::erase_blinded_1to1( + std::string_view blinded_session_id, bool legacy_blinding) { + return erase(convo::blinded_one_to_one{blinded_session_id, legacy_blinding}); +} size_t ConvoInfoVolatile::size_1to1() const { if (auto* d = data["1"].dict()) @@ -366,12 +425,24 @@ size_t ConvoInfoVolatile::size_legacy_groups() const { return 0; } +size_t ConvoInfoVolatile::size_blinded_1to1() const { + if (auto* d = data["b"].dict()) + return d->size(); + return 0; +} + size_t ConvoInfoVolatile::size() const { - return size_1to1() + size_communities() + size_legacy_groups() + size_groups(); + return size_1to1() + size_communities() + size_legacy_groups() + size_groups() + + size_blinded_1to1(); } ConvoInfoVolatile::iterator::iterator( - const DictFieldRoot& data, bool oneto1, bool communities, bool groups, bool legacy_groups) { + const DictFieldRoot& data, + bool oneto1, + bool communities, + bool groups, + bool legacy_groups, + bool blinded_1to1) { if (oneto1) if (auto* d = data["1"].dict()) { _it_11 = d->begin(); @@ -390,6 +461,11 @@ ConvoInfoVolatile::iterator::iterator( _it_lgroup = d->begin(); _end_lgroup = d->end(); } + if (blinded_1to1) + if (auto* d = data["b"].dict()) { + _it_b11 = d->begin(); + _end_b11 = d->end(); + } _load_val(); } @@ -400,7 +476,8 @@ class val_loader { std::shared_ptr& val, std::optional& it, std::optional& end, - char prefix) { + char prefix, + std::optional legacy_prefix = std::nullopt) { while (it) { if (*it == *end) { it.reset(); @@ -410,9 +487,13 @@ class val_loader { auto& [k, v] = **it; - if (k.size() == 33 && k[0] == prefix) { + if (k.size() == 33 && (k[0] == prefix || (legacy_prefix && k[0] == *legacy_prefix))) { if (auto* info_dict = std::get_if(&v)) { - val = std::make_shared(ConvoType{oxenc::to_hex(k)}); + if constexpr (std::is_same_v) + val = std::make_shared(ConvoType{ + oxenc::to_hex(k), (legacy_prefix && k[0] == *legacy_prefix)}); + else + val = std::make_shared(ConvoType{oxenc::to_hex(k)}); std::get(*val).load(*info_dict); return true; } @@ -425,7 +506,7 @@ class val_loader { /// Load _val from the current iterator position; if it is invalid, skip to the next key until we /// find one that is valid (or hit the end). We also span across four different iterators: we -/// exhaust, in order: _it_11, _it_group, _it_comm, _it_lgroup. +/// exhaust, in order: _it_11, _it_group, _it_comm, _it_lgroup, _it_b11. /// /// We *always* call this after incrementing the iterator (and after iterator initialization), and /// this is responsible for making sure that _it_11, _it_group, etc. are only set to non-nullopt if @@ -448,15 +529,18 @@ void ConvoInfoVolatile::iterator::_load_val() { if (val_loader::load(_val, _it_lgroup, _end_lgroup, 0x05)) return; + + if (val_loader::load(_val, _it_b11, _end_b11, 0x25, 0x15)) + return; } bool ConvoInfoVolatile::iterator::operator==(const iterator& other) const { return _it_11 == other._it_11 && _it_group == other._it_group && _it_comm == other._it_comm && - _it_lgroup == other._it_lgroup; + _it_lgroup == other._it_lgroup && _it_b11 == other._it_b11; } bool ConvoInfoVolatile::iterator::done() const { - return !_it_11 && !_it_group && (!_it_comm || _it_comm->done()) && !_it_lgroup; + return !_it_11 && !_it_group && (!_it_comm || _it_comm->done()) && !_it_lgroup && !_it_b11; } ConvoInfoVolatile::iterator& ConvoInfoVolatile::iterator::operator++() { @@ -466,9 +550,11 @@ ConvoInfoVolatile::iterator& ConvoInfoVolatile::iterator::operator++() { ++*_it_group; else if (_it_comm && !_it_comm->done()) _it_comm->advance(); - else { - assert(_it_lgroup); + else if (_it_lgroup) ++*_it_lgroup; + else { + assert(_it_b11); + ++*_it_b11; } _load_val(); return *this; @@ -604,6 +690,40 @@ LIBSESSION_C_API bool convo_info_volatile_get_or_construct_legacy_group( false); } +LIBSESSION_C_API bool convo_info_volatile_get_blinded_1to1( + config_object* conf, + convo_info_volatile_blinded_1to1* convo, + const char* blinded_session_id, + bool legacy_blinding) { + return wrap_exceptions( + conf, + [&] { + if (auto c = unbox(conf)->get_blinded_1to1( + blinded_session_id, legacy_blinding)) { + c->into(*convo); + return true; + } + return false; + }, + false); +} + +LIBSESSION_C_API bool convo_info_volatile_get_or_construct_blinded_1to1( + config_object* conf, + convo_info_volatile_blinded_1to1* convo, + const char* blinded_session_id, + bool legacy_blinding) { + return wrap_exceptions( + conf, + [&] { + unbox(conf) + ->get_or_construct_blinded_1to1(blinded_session_id, legacy_blinding) + .into(*convo); + return true; + }, + false); +} + LIBSESSION_C_API bool convo_info_volatile_set_1to1( config_object* conf, const convo_info_volatile_1to1* convo) { return wrap_exceptions( @@ -645,6 +765,17 @@ LIBSESSION_C_API bool convo_info_volatile_set_legacy_group( false); } +LIBSESSION_C_API bool convo_info_volatile_set_blinded_1to1( + config_object* conf, const convo_info_volatile_blinded_1to1* convo) { + return wrap_exceptions( + conf, + [&] { + unbox(conf)->set(convo::blinded_one_to_one{*convo}); + return true; + }, + false); +} + LIBSESSION_C_API bool convo_info_volatile_erase_1to1(config_object* conf, const char* session_id) { return wrap_exceptions( conf, [&] { return unbox(conf)->erase_1to1(session_id); }, false); @@ -667,6 +798,16 @@ LIBSESSION_C_API bool convo_info_volatile_erase_legacy_group( [&] { return unbox(conf)->erase_legacy_group(group_id); }, false); } +LIBSESSION_C_API bool convo_info_volatile_erase_blinded_1to1( + config_object* conf, const char* blinded_session_id, bool legacy_blinding) { + return wrap_exceptions( + conf, + [&] { + return unbox(conf)->erase_blinded_1to1( + blinded_session_id, legacy_blinding); + }, + false); +} LIBSESSION_C_API size_t convo_info_volatile_size(const config_object* conf) { return unbox(conf)->size(); @@ -683,6 +824,9 @@ LIBSESSION_C_API size_t convo_info_volatile_size_groups(const config_object* con LIBSESSION_C_API size_t convo_info_volatile_size_legacy_groups(const config_object* conf) { return unbox(conf)->size_legacy_groups(); } +LIBSESSION_C_API size_t convo_info_volatile_size_blinded_1to1(const config_object* conf) { + return unbox(conf)->size_blinded_1to1(); +} LIBSESSION_C_API convo_info_volatile_iterator* convo_info_volatile_iterator_new( const config_object* conf) { @@ -718,6 +862,13 @@ LIBSESSION_C_API convo_info_volatile_iterator* convo_info_volatile_iterator_new_ new ConvoInfoVolatile::iterator{unbox(conf)->begin_legacy_groups()}; return it; } +LIBSESSION_C_API convo_info_volatile_iterator* convo_info_volatile_iterator_new_blinded_1to1( + const config_object* conf) { + auto* it = new convo_info_volatile_iterator{}; + it->_internals = + new ConvoInfoVolatile::iterator{unbox(conf)->begin_blinded_1to1()}; + return it; +} LIBSESSION_C_API void convo_info_volatile_iterator_free(convo_info_volatile_iterator* it) { delete static_cast(it->_internals); @@ -764,3 +915,8 @@ LIBSESSION_C_API bool convo_info_volatile_it_is_legacy_group( convo_info_volatile_iterator* it, convo_info_volatile_legacy_group* c) { return convo_info_volatile_it_is_impl(it, c); } + +LIBSESSION_C_API bool convo_info_volatile_it_is_blinded_1to1( + convo_info_volatile_iterator* it, convo_info_volatile_blinded_1to1* c) { + return convo_info_volatile_it_is_impl(it, c); +}