tomato/toxcore/group_pack.c
Green Sky 9ddeea3d06 Squashed 'external/toxcore/c-toxcore/' changes from d4b06edc2a..adbd5b32d8
adbd5b32d8 feat: add ngc events
15ee46d431 add simple test for max sized lossy custom group packet
01e7950c67 increase lossy custom packet size in ngc to the toxcore common max of 1373
9b3c1089f1 Make group saving/loading more forgiving with data errors
55a76003b0 Replace memset(int32_t*, -1, _) with a for-loop
66453439ac fix: also Install header for private/experimental API functions with autotools
3983369103 fix: Enable debug flag for ubsan.
4d1db21102 Update tox-boostrapd hash
e700c31b70 Fix memory leak in group connection
2994441d9c Fix memory leak in save-generator
d0400df13d Fix memory leak in tox-bootstrapd
7a6d50ebe3 Install header for private/experimental API functions
d89677fb5f Remove defunct IRC channel from README.md
26d41fc604 Replace DEFAULT_TCP_RELAY_PORTS_COUNT with a compile-time calculation
63fb2941ca Clarify disabling of static assert checks
65b3375b98 refactor: Use Bin_Pack for packing Node_format.
84ba154f6a group connection queries now return our own connection type
a4df2862ed Replace tabs with spaces
1b6dee7594 Update tox-bootstrapd's base Docker images
a030cdee5c Fix Docker tox-bootstrapd hash update failing when using BuildKit
7cfe35dff2 cleanup: Remove explicit layering_check feature.
d390947245 chore: Upgrade sonar-scan jvm to java 17.
d1e850c56c fix: Add missing `htons` call when adding configured TCP relay.
814090f2b8 chore: Cancel old PR builds on docker and sonar-scan workflows.
83efb17367 perf: Add a KVM FreeBSD build on cirrus ci.
a927183233 test: Add a test for encrypting 100MB of data.
28f39049f6 chore: Retry freebsd tests 2 times.
47e77d1bb0 chore: Use C99 on MSVC instead of C11.
7155f7f60e test: Add an s390x build (on alpine) for CI.
6c35cef63f chore: Add a compcert docker run script.
41e6ea865e cleanup: Use tcc docker image for CI.
e726b197b0 refactor: Store time in Mono_Time in milliseconds.
REVERT: d4b06edc2a feat: add ngc events

git-subtree-dir: external/toxcore/c-toxcore
git-subtree-split: adbd5b32d85d9c13800f5ece17c0a9dce99faacd
2023-12-15 15:21:40 +01:00

468 lines
14 KiB
C

/* SPDX-License-Identifier: GPL-3.0-or-later
* Copyright © 2016-2020 The TokTok team.
* Copyright © 2015 Tox project.
*/
/**
* Packer and unpacker functions for saving and loading groups.
*/
#include "group_pack.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "bin_pack.h"
#include "bin_unpack.h"
#include "ccompat.h"
#include "util.h"
Group_Privacy_State group_privacy_state_from_int(uint8_t value)
{
switch (value) {
case 0:
return GI_PUBLIC;
case 1:
return GI_PRIVATE;
default:
return GI_PUBLIC;
}
}
Group_Voice_State group_voice_state_from_int(uint8_t value)
{
switch (value) {
case 0:
return GV_ALL;
case 1:
return GV_MODS;
case 2:
return GV_FOUNDER;
default:
return GV_ALL;
}
}
non_null()
static bool load_unpack_state_values(GC_Chat *chat, Bin_Unpack *bu)
{
if (!bin_unpack_array_fixed(bu, 8, nullptr)) {
LOGGER_ERROR(chat->log, "Group state values array malformed");
return false;
}
bool manually_disconnected = false;
uint8_t privacy_state = 0;
uint8_t voice_state = 0;
if (!(bin_unpack_bool(bu, &manually_disconnected)
&& bin_unpack_u16(bu, &chat->shared_state.group_name_len)
&& bin_unpack_u08(bu, &privacy_state)
&& bin_unpack_u16(bu, &chat->shared_state.maxpeers)
&& bin_unpack_u16(bu, &chat->shared_state.password_length)
&& bin_unpack_u32(bu, &chat->shared_state.version)
&& bin_unpack_u32(bu, &chat->shared_state.topic_lock)
&& bin_unpack_u08(bu, &voice_state))) {
LOGGER_ERROR(chat->log, "Failed to unpack state value");
return false;
}
chat->connection_state = manually_disconnected ? CS_DISCONNECTED : CS_CONNECTING;
chat->shared_state.privacy_state = group_privacy_state_from_int(privacy_state);
chat->shared_state.voice_state = group_voice_state_from_int(voice_state);
// we always load saved groups as private in case the group became private while we were offline.
// this will have no detrimental effect if the group is public, as the correct privacy
// state will be set via sync.
chat->join_type = HJ_PRIVATE;
return true;
}
non_null()
static bool load_unpack_state_bin(GC_Chat *chat, Bin_Unpack *bu)
{
if (!bin_unpack_array_fixed(bu, 5, nullptr)) {
LOGGER_ERROR(chat->log, "Group state binary array malformed");
return false;
}
if (!bin_unpack_bin_fixed(bu, chat->shared_state_sig, SIGNATURE_SIZE)) {
LOGGER_ERROR(chat->log, "Failed to unpack shared state signature");
return false;
}
if (!bin_unpack_bin_fixed(bu, chat->shared_state.founder_public_key, EXT_PUBLIC_KEY_SIZE)) {
LOGGER_ERROR(chat->log, "Failed to unpack founder public key");
return false;
}
if (!(bin_unpack_bin_max(bu, chat->shared_state.group_name, &chat->shared_state.group_name_len, sizeof(chat->shared_state.group_name))
&& bin_unpack_bin_max(bu, chat->shared_state.password, &chat->shared_state.password_length, sizeof(chat->shared_state.password))
&& bin_unpack_bin_fixed(bu, chat->shared_state.mod_list_hash, MOD_MODERATION_HASH_SIZE))) {
LOGGER_ERROR(chat->log, "Failed to unpack state binary data");
return false;
}
return true;
}
non_null()
static bool load_unpack_topic_info(GC_Chat *chat, Bin_Unpack *bu)
{
if (!bin_unpack_array_fixed(bu, 6, nullptr)) {
LOGGER_ERROR(chat->log, "Group topic array malformed");
return false;
}
if (!(bin_unpack_u32(bu, &chat->topic_info.version)
&& bin_unpack_u16(bu, &chat->topic_info.length)
&& bin_unpack_u16(bu, &chat->topic_info.checksum)
&& bin_unpack_bin_max(bu, chat->topic_info.topic, &chat->topic_info.length, sizeof(chat->topic_info.topic))
&& bin_unpack_bin_fixed(bu, chat->topic_info.public_sig_key, SIG_PUBLIC_KEY_SIZE)
&& bin_unpack_bin_fixed(bu, chat->topic_sig, SIGNATURE_SIZE))) {
LOGGER_ERROR(chat->log, "Failed to unpack topic info");
return false;
}
return true;
}
non_null()
static bool load_unpack_mod_list(GC_Chat *chat, Bin_Unpack *bu)
{
uint32_t actual_size = 0;
if (!bin_unpack_array_fixed(bu, 2, &actual_size)) {
LOGGER_ERROR(chat->log, "Group mod list array malformed: %d != 2", actual_size);
return false;
}
if (!bin_unpack_u16(bu, &chat->moderation.num_mods)) {
LOGGER_ERROR(chat->log, "Failed to unpack mod list value");
return false;
}
if (chat->moderation.num_mods == 0) {
bin_unpack_nil(bu);
return true;
}
if (chat->moderation.num_mods > MOD_MAX_NUM_MODERATORS) {
LOGGER_ERROR(chat->log, "moderation count %u exceeds maximum %u", chat->moderation.num_mods, MOD_MAX_NUM_MODERATORS);
chat->moderation.num_mods = MOD_MAX_NUM_MODERATORS;
}
uint8_t *packed_mod_list = (uint8_t *)malloc(chat->moderation.num_mods * MOD_LIST_ENTRY_SIZE);
if (packed_mod_list == nullptr) {
LOGGER_ERROR(chat->log, "Failed to allocate memory for packed mod list");
return false;
}
const size_t packed_size = chat->moderation.num_mods * MOD_LIST_ENTRY_SIZE;
if (!bin_unpack_bin_fixed(bu, packed_mod_list, packed_size)) {
LOGGER_ERROR(chat->log, "Failed to unpack mod list binary data");
free(packed_mod_list);
return false;
}
if (mod_list_unpack(&chat->moderation, packed_mod_list, packed_size, chat->moderation.num_mods) == -1) {
LOGGER_ERROR(chat->log, "Failed to unpack mod list info");
free(packed_mod_list);
return false;
}
free(packed_mod_list);
return true;
}
non_null()
static bool load_unpack_keys(GC_Chat *chat, Bin_Unpack *bu)
{
if (!bin_unpack_array_fixed(bu, 4, nullptr)) {
LOGGER_ERROR(chat->log, "Group keys array malformed");
return false;
}
if (!(bin_unpack_bin_fixed(bu, chat->chat_public_key, EXT_PUBLIC_KEY_SIZE)
&& bin_unpack_bin_fixed(bu, chat->chat_secret_key, EXT_SECRET_KEY_SIZE)
&& bin_unpack_bin_fixed(bu, chat->self_public_key, EXT_PUBLIC_KEY_SIZE)
&& bin_unpack_bin_fixed(bu, chat->self_secret_key, EXT_SECRET_KEY_SIZE))) {
LOGGER_ERROR(chat->log, "Failed to unpack keys");
return false;
}
return true;
}
non_null()
static bool load_unpack_self_info(GC_Chat *chat, Bin_Unpack *bu)
{
if (!bin_unpack_array_fixed(bu, 4, nullptr)) {
LOGGER_ERROR(chat->log, "Group self info array malformed");
return false;
}
uint8_t self_nick[MAX_GC_NICK_SIZE];
uint16_t self_nick_len = 0;
uint8_t self_role = GR_USER;
uint8_t self_status = GS_NONE;
if (!(bin_unpack_u16(bu, &self_nick_len)
&& bin_unpack_u08(bu, &self_role)
&& bin_unpack_u08(bu, &self_status))) {
LOGGER_ERROR(chat->log, "Failed to unpack self values");
return false;
}
if (self_nick_len > MAX_GC_NICK_SIZE) {
LOGGER_ERROR(chat->log, "self_nick too big (%u bytes), truncating to %d", self_nick_len, MAX_GC_NICK_SIZE);
self_nick_len = MAX_GC_NICK_SIZE;
}
if (!bin_unpack_bin_fixed(bu, self_nick, self_nick_len)) {
LOGGER_ERROR(chat->log, "Failed to unpack self nick bytes");
return false;
}
// we have to add ourself before setting self info
if (peer_add(chat, nullptr, chat->self_public_key) != 0) {
LOGGER_ERROR(chat->log, "Failed to add self to peer list");
return false;
}
if (chat->numpeers == 0) {
LOGGER_ERROR(chat->log, "Failed to unpack self: numpeers should be > 0");
return false;
}
GC_Peer *self = &chat->group[0];
memcpy(self->gconn.addr.public_key, chat->self_public_key, EXT_PUBLIC_KEY_SIZE);
memcpy(self->nick, self_nick, self_nick_len);
self->nick_length = self_nick_len;
self->role = (Group_Role)self_role;
self->status = (Group_Peer_Status)self_status;
self->gconn.confirmed = true;
return true;
}
non_null()
static bool load_unpack_saved_peers(GC_Chat *chat, Bin_Unpack *bu)
{
if (!bin_unpack_array_fixed(bu, 2, nullptr)) {
LOGGER_ERROR(chat->log, "Group saved peers array malformed");
return false;
}
// Saved peers
uint16_t saved_peers_size = 0;
if (!bin_unpack_u16(bu, &saved_peers_size)) {
LOGGER_ERROR(chat->log, "Failed to unpack saved peers value");
return false;
}
if (saved_peers_size == 0) {
bin_unpack_nil(bu);
return true;
}
uint8_t *saved_peers = (uint8_t *)malloc(saved_peers_size * GC_SAVED_PEER_SIZE);
if (saved_peers == nullptr) {
LOGGER_ERROR(chat->log, "Failed to allocate memory for saved peer list");
return false;
}
if (!bin_unpack_bin_fixed(bu, saved_peers, saved_peers_size)) {
LOGGER_ERROR(chat->log, "Failed to unpack saved peers binary data");
free(saved_peers);
return false;
}
if (unpack_gc_saved_peers(chat, saved_peers, saved_peers_size) == -1) {
LOGGER_ERROR(chat->log, "Failed to unpack saved peers"); // recoverable error
}
free(saved_peers);
return true;
}
bool gc_load_unpack_group(GC_Chat *chat, Bin_Unpack *bu)
{
uint32_t actual_size;
if (!bin_unpack_array_fixed(bu, 7, &actual_size)) {
LOGGER_ERROR(chat->log, "Group info array malformed: %d != 7", actual_size);
return false;
}
return load_unpack_state_values(chat, bu)
&& load_unpack_state_bin(chat, bu)
&& load_unpack_topic_info(chat, bu)
&& load_unpack_mod_list(chat, bu)
&& load_unpack_keys(chat, bu)
&& load_unpack_self_info(chat, bu)
&& load_unpack_saved_peers(chat, bu);
}
non_null()
static void save_pack_state_values(const GC_Chat *chat, Bin_Pack *bp)
{
bin_pack_array(bp, 8);
bin_pack_bool(bp, chat->connection_state == CS_DISCONNECTED); // 1
bin_pack_u16(bp, chat->shared_state.group_name_len); // 2
bin_pack_u08(bp, chat->shared_state.privacy_state); // 3
bin_pack_u16(bp, chat->shared_state.maxpeers); // 4
bin_pack_u16(bp, chat->shared_state.password_length); // 5
bin_pack_u32(bp, chat->shared_state.version); // 6
bin_pack_u32(bp, chat->shared_state.topic_lock); // 7
bin_pack_u08(bp, chat->shared_state.voice_state); // 8
}
non_null()
static void save_pack_state_bin(const GC_Chat *chat, Bin_Pack *bp)
{
bin_pack_array(bp, 5);
bin_pack_bin(bp, chat->shared_state_sig, SIGNATURE_SIZE); // 1
bin_pack_bin(bp, chat->shared_state.founder_public_key, EXT_PUBLIC_KEY_SIZE); // 2
bin_pack_bin(bp, chat->shared_state.group_name, chat->shared_state.group_name_len); // 3
bin_pack_bin(bp, chat->shared_state.password, chat->shared_state.password_length); // 4
bin_pack_bin(bp, chat->shared_state.mod_list_hash, MOD_MODERATION_HASH_SIZE); // 5
}
non_null()
static void save_pack_topic_info(const GC_Chat *chat, Bin_Pack *bp)
{
bin_pack_array(bp, 6);
bin_pack_u32(bp, chat->topic_info.version); // 1
bin_pack_u16(bp, chat->topic_info.length); // 2
bin_pack_u16(bp, chat->topic_info.checksum); // 3
bin_pack_bin(bp, chat->topic_info.topic, chat->topic_info.length); // 4
bin_pack_bin(bp, chat->topic_info.public_sig_key, SIG_PUBLIC_KEY_SIZE); // 5
bin_pack_bin(bp, chat->topic_sig, SIGNATURE_SIZE); // 6
}
non_null()
static void save_pack_mod_list(const GC_Chat *chat, Bin_Pack *bp)
{
bin_pack_array(bp, 2);
const uint16_t num_mods = min_u16(chat->moderation.num_mods, MOD_MAX_NUM_MODERATORS);
if (num_mods == 0) {
bin_pack_u16(bp, num_mods); // 1
bin_pack_nil(bp); // 2
return;
}
uint8_t *packed_mod_list = (uint8_t *)malloc(num_mods * MOD_LIST_ENTRY_SIZE);
// we can still recover without the mod list
if (packed_mod_list == nullptr) {
bin_pack_u16(bp, 0); // 1
bin_pack_nil(bp); // 2
LOGGER_ERROR(chat->log, "Failed to allocate memory for moderation list");
return;
}
bin_pack_u16(bp, num_mods); // 1
mod_list_pack(&chat->moderation, packed_mod_list);
const size_t packed_size = num_mods * MOD_LIST_ENTRY_SIZE;
bin_pack_bin(bp, packed_mod_list, packed_size); // 2
free(packed_mod_list);
}
non_null()
static void save_pack_keys(const GC_Chat *chat, Bin_Pack *bp)
{
bin_pack_array(bp, 4);
bin_pack_bin(bp, chat->chat_public_key, EXT_PUBLIC_KEY_SIZE); // 1
bin_pack_bin(bp, chat->chat_secret_key, EXT_SECRET_KEY_SIZE); // 2
bin_pack_bin(bp, chat->self_public_key, EXT_PUBLIC_KEY_SIZE); // 3
bin_pack_bin(bp, chat->self_secret_key, EXT_SECRET_KEY_SIZE); // 4
}
non_null()
static void save_pack_self_info(const GC_Chat *chat, Bin_Pack *bp)
{
bin_pack_array(bp, 4);
GC_Peer *self = &chat->group[0];
if (self->nick_length > MAX_GC_NICK_SIZE) {
LOGGER_ERROR(chat->log, "self_nick is too big (%u). Truncating to %d", self->nick_length, MAX_GC_NICK_SIZE);
self->nick_length = MAX_GC_NICK_SIZE;
}
bin_pack_u16(bp, self->nick_length); // 1
bin_pack_u08(bp, (uint8_t)self->role); // 2
bin_pack_u08(bp, (uint8_t)self->status); // 3
bin_pack_bin(bp, self->nick, self->nick_length); // 4
}
non_null()
static void save_pack_saved_peers(const GC_Chat *chat, Bin_Pack *bp)
{
bin_pack_array(bp, 2);
uint8_t *saved_peers = (uint8_t *)malloc(GC_MAX_SAVED_PEERS * GC_SAVED_PEER_SIZE);
// we can still recover without the saved peers list
if (saved_peers == nullptr) {
bin_pack_u16(bp, 0); // 1
bin_pack_nil(bp); // 2
LOGGER_ERROR(chat->log, "Failed to allocate memory for saved peers list");
return;
}
uint16_t packed_size = 0;
const int count = pack_gc_saved_peers(chat, saved_peers, GC_MAX_SAVED_PEERS * GC_SAVED_PEER_SIZE, &packed_size);
if (count < 0) {
LOGGER_ERROR(chat->log, "Failed to pack saved peers");
}
bin_pack_u16(bp, packed_size); // 1
if (packed_size == 0) {
bin_pack_nil(bp); // 2
free(saved_peers);
return;
}
bin_pack_bin(bp, saved_peers, packed_size); // 2
free(saved_peers);
}
void gc_save_pack_group(const GC_Chat *chat, Bin_Pack *bp)
{
if (chat->numpeers == 0) {
LOGGER_ERROR(chat->log, "Failed to pack group: numpeers is 0");
return;
}
bin_pack_array(bp, 7);
save_pack_state_values(chat, bp); // 1
save_pack_state_bin(chat, bp); // 2
save_pack_topic_info(chat, bp); // 3
save_pack_mod_list(chat, bp); // 4
save_pack_keys(chat, bp); // 5
save_pack_self_info(chat, bp); // 6
save_pack_saved_peers(chat, bp); // 7
}