From 156c997c94270e214ef74bfabcc34fe11f001b7c Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Wed, 18 Oct 2023 21:13:22 -0300 Subject: [PATCH] Add description to group info --- include/session/config/groups/info.h | 36 +++++++++++++++++ include/session/config/groups/info.hpp | 35 +++++++++++++--- src/config/groups/info.cpp | 56 ++++++++++++++++++++++++++ tests/test_group_keys.cpp | 16 +++++++- 4 files changed, 137 insertions(+), 6 deletions(-) diff --git a/include/session/config/groups/info.h b/include/session/config/groups/info.h index 1efc4a75..32da3ae9 100644 --- a/include/session/config/groups/info.h +++ b/include/session/config/groups/info.h @@ -8,6 +8,9 @@ extern "C" { #include "../profile_pic.h" #include "../util.h" +LIBSESSION_EXPORT extern const size_t GROUP_INFO_NAME_MAX_LENGTH; +LIBSESSION_EXPORT extern const size_t GROUP_INFO_DESCRIPTION_MAX_LENGTH; + /// API: groups/groups_info_init /// /// Constructs a group info config object and sets a pointer to it in `conf`. @@ -56,6 +59,9 @@ LIBSESSION_EXPORT const char* groups_info_get_name(const config_object* conf); /// Sets the group's name to the null-terminated C string. Returns 0 on success, non-zero on /// error (and sets the config_object's error string). /// +/// If the given name is longer than GROUP_INFO_NAME_MAX_LENGTH (100) bytes then it will be +/// truncated. +/// /// Inputs: /// - `conf` -- [in] Pointer to the config object /// - `name` -- [in] Pointer to the name as a null-terminated C string @@ -64,6 +70,36 @@ LIBSESSION_EXPORT const char* groups_info_get_name(const config_object* conf); /// - `int` -- Returns 0 on success, non-zero on error LIBSESSION_EXPORT int groups_info_set_name(config_object* conf, const char* name); +/// API: groups_info/groups_info_get_description +/// +/// Returns a pointer to the currently-set description (null-terminated), or NULL if there is no +/// description at all. Should be copied right away as the pointer may not remain valid beyond +/// other API calls. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `char*` -- Pointer to the currently-set description as a null-terminated string, or NULL if +/// there is no description +LIBSESSION_EXPORT const char* groups_info_get_description(const config_object* conf); + +/// API: groups_info/groups_info_set_description +/// +/// Sets the group's description to the null-terminated C string. Returns 0 on success, non-zero on +/// error (and sets the config_object's error string). +/// +/// If the given description is longer than GROUP_INFO_DESCRIPTION_MAX_LENGTH (2000) bytes then it +/// will be truncated. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `description` -- [in] Pointer to the description as a null-terminated C string +/// +/// Outputs: +/// - `int` -- Returns 0 on success, non-zero on error +LIBSESSION_EXPORT int groups_info_set_description(config_object* conf, const char* description); + /// API: groups_info/groups_info_get_pic /// /// Obtains the current profile pic. The pointers in the returned struct will be NULL if a profile diff --git a/include/session/config/groups/info.hpp b/include/session/config/groups/info.hpp index 9ebdfd7d..4010bf35 100644 --- a/include/session/config/groups/info.hpp +++ b/include/session/config/groups/info.hpp @@ -21,13 +21,19 @@ using namespace std::literals; /// D - delete attachments before - same as above, but specific to attachments. /// E - disappearing message timer (seconds) if the delete-after-send disappearing messages mode is /// enabled for the group. Omitted if disappearing messages is disabled. -/// n - utf8 group name (human-readable) +/// n - utf8 group name (human-readable); may not contain nulls, max length 100. +/// o - utf8 group description (human-readable); may not contain nulls, max length 2000. /// p - group profile url /// q - group profile decryption key (binary) class Info final : public ConfigBase { public: + /// Limits for the name & description strings, in bytes. If longer, we truncate to these + /// lengths: + static constexpr size_t NAME_MAX_LENGTH = 100; // same as base_group_info::NAME_MAX_LENGTH + static constexpr size_t DESCRIPTION_MAX_LENGTH = 2000; + // No default constructor Info() = delete; @@ -98,15 +104,34 @@ class Info final : public ConfigBase { /// /// Sets the group name; if given an empty string then the name is removed. /// - /// Declaration: - /// ```cpp - /// void set_name(std::string_view new_name); - /// ``` + /// If given a name longer than `Info::NAME_MAX_LENGTH` (100) bytes it will be truncated. /// /// Inputs: /// - `new_name` -- The name to be put into the group Info void set_name(std::string_view new_name); + /// API: groups/Info::get_description + /// + /// Returns the group description, or std::nullopt if there is no group description set. + /// + /// If given a description longer than `Info::DESCRIPTION_MAX_LENGTH` (2000) bytes it will be + /// truncated. + /// + /// Inputs: None + /// + /// Outputs: + /// - `std::optional` - Returns the group description if it is set + std::optional get_description() const; + + /// API: groups/Info::set_description + /// + /// Sets the optional group description; if given an empty string then an existing description + /// is removed. + /// + /// Inputs: + /// - `new_desc` -- The new description to be put into the group Info + void set_description(std::string_view new_desc); + /// API: groups/Info::get_profile_pic /// /// Gets the group's current profile pic URL and decryption key. The returned object will diff --git a/src/config/groups/info.cpp b/src/config/groups/info.cpp index be29e581..88b3a1eb 100644 --- a/src/config/groups/info.cpp +++ b/src/config/groups/info.cpp @@ -30,9 +30,23 @@ std::optional Info::get_name() const { } void Info::set_name(std::string_view new_name) { + if (new_name.size() > NAME_MAX_LENGTH) + new_name = new_name.substr(0, NAME_MAX_LENGTH); set_nonempty_str(data["n"], new_name); } +std::optional Info::get_description() const { + if (auto* s = data["o"].string(); s && !s->empty()) + return *s; + return std::nullopt; +} + +void Info::set_description(std::string_view new_desc) { + if (new_desc.size() > DESCRIPTION_MAX_LENGTH) + new_desc = new_desc.substr(0, DESCRIPTION_MAX_LENGTH); + set_nonempty_str(data["o"], new_desc); +} + profile_pic Info::get_profile_pic() const { profile_pic pic{}; if (auto* url = data["p"].string(); url && !url->empty()) @@ -105,6 +119,10 @@ bool Info::is_destroyed() const { using namespace session; using namespace session::config; +LIBSESSION_C_API const size_t GROUP_INFO_NAME_MAX_LENGTH = groups::Info::NAME_MAX_LENGTH; +LIBSESSION_C_API const size_t GROUP_INFO_DESCRIPTION_MAX_LENGTH = + groups::Info::DESCRIPTION_MAX_LENGTH; + LIBSESSION_C_API int groups_info_init( config_object** conf, const unsigned char* ed25519_pubkey, @@ -153,6 +171,44 @@ LIBSESSION_C_API int groups_info_set_name(config_object* conf, const char* name) return 0; } +/// API: groups_info/groups_info_get_description +/// +/// Returns a pointer to the currently-set description (null-terminated), or NULL if there is +/// no description at all. Should be copied right away as the pointer may not remain valid +/// beyond other API calls. +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// +/// Outputs: +/// - `char*` -- Pointer to the currently-set description as a null-terminated string, or NULL +/// if there is no description +LIBSESSION_C_API const char* groups_info_get_description(const config_object* conf) { + if (auto s = unbox(conf)->get_description()) + return s->data(); + return nullptr; +} + +/// API: groups_info/groups_info_set_description +/// +/// Sets the group's description to the null-terminated C string. Returns 0 on success, non-zero on +/// error (and sets the config_object's error string). +/// +/// Inputs: +/// - `conf` -- [in] Pointer to the config object +/// - `description` -- [in] Pointer to the description as a null-terminated C string +/// +/// Outputs: +/// - `int` -- Returns 0 on success, non-zero on error +LIBSESSION_C_API int groups_info_set_description(config_object* conf, const char* description) { + try { + unbox(conf)->set_description(description); + } catch (const std::exception& e) { + return set_error(conf, SESSION_ERR_BAD_VALUE, e); + } + return 0; +} + /// API: groups_info/groups_info_get_pic /// /// Obtains the current profile pic. The pointers in the returned struct will be NULL if a profile diff --git a/tests/test_group_keys.cpp b/tests/test_group_keys.cpp index 05978fc7..c051e1f8 100644 --- a/tests/test_group_keys.cpp +++ b/tests/test_group_keys.cpp @@ -238,6 +238,7 @@ TEST_CASE("Group Keys - C++ API", "[config][groups][keys][cpp]") { // change group info, re-key, distribute admin1.info.set_name("tomatosauce"s); + admin1.info.set_description("this is where you go to play in the tomato sauce, I guess"); CHECK(admin1.info.needs_push()); @@ -258,6 +259,8 @@ TEST_CASE("Group Keys - C++ API", "[config][groups][keys][cpp]") { CHECK(a.info.merge(info_configs) == std::vector{{"fakehash3"s}}); CHECK(a.members.merge(mem_configs) == std::vector{{"fakehash3"s}}); CHECK(a.info.get_name() == "tomatosauce"s); + CHECK(a.info.get_description() == + "this is where you go to play in the tomato sauce, I guess"s); CHECK(a.keys.current_hashes() == std::unordered_set{{"keyhash1"s, "keyhash2"s, "keyhash3"s}}); } @@ -268,6 +271,8 @@ TEST_CASE("Group Keys - C++ API", "[config][groups][keys][cpp]") { CHECK(m.info.merge(info_configs) == std::vector{{"fakehash3"s}}); CHECK(m.members.merge(mem_configs) == std::vector{{"fakehash3"s}}); CHECK(m.info.get_name() == "tomatosauce"s); + CHECK(m.info.get_description() == + "this is where you go to play in the tomato sauce, I guess"s); CHECK(m.keys.current_hashes() == std::unordered_set{{"keyhash2"s, "keyhash3"s}}); } @@ -532,12 +537,21 @@ TEST_CASE("Group Keys - C++ API", "[config][groups][keys][cpp]") { admin1.info.dump(), admin1.members.dump(), admin1.keys.dump()}; - admin1b.info.set_name("Test New Name"); + admin1b.info.set_name( + "Test New Name Really long " + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"); + admin1b.info.set_description(std::string(2050, 'z')); CHECK_NOTHROW(admin1b.info.push()); admin1b.members.set( admin1b.members.get_or_construct("05124076571076017981235497801235098712093870981273590" "8746387172343")); CHECK_NOTHROW(admin1b.members.push()); + + // Test truncation + CHECK(admin1b.info.get_name() == + "Test New Name Really long " + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuv"); + CHECK(admin1b.info.get_description() == std::string(2000, 'z')); } TEST_CASE("Group Keys - C API", "[config][groups][keys][c]") {