Skip to content

Add blinded contact records to Contacts and ConvoInfoVolatile #52

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions include/session/config/contacts.h
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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;
Expand Down
132 changes: 131 additions & 1 deletion include/session/config/contacts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
#include <session/config.hpp>

#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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<const unsigned char> 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<const unsigned char> 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:
Expand Down Expand Up @@ -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<const unsigned char>* get_pubkey = nullptr) const;

public:
/// API: contacts/Contacts::blinded_contacts
///
/// Retrieves a list of all known blinded contacts.
///
/// Inputs: None
///
/// Outputs:
/// - `std::vector<blinded_contact_info>` - Returns a list of blinded_contact_info
std::vector<blinded_contact_info> 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<blinded_contact_info>` - Returns nullopt if blinded session ID was not
/// found, otherwise a filled out blinded_contact_info
std::optional<blinded_contact_info> 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
///
Expand Down
Loading