/*
This file is part of GNUnet.
Copyright (C) 2021--2024 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
GNUnet is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
SPDX-License-Identifier: AGPL3.0-or-later
*/
/*
* @author Tobias Frisch
* @file gnunet_chat_util.c
*/
#include "gnunet_chat_util.h"
#include
#include
static const char label_prefix_of_contact [] = "contact";
static const char label_prefix_of_group [] = "group";
static const char identity_prefix_of_lobby [] = "_gnunet_chat_lobby";
void
util_shorthash_from_member (const struct GNUNET_MESSENGER_Contact *member,
struct GNUNET_ShortHashCode *shorthash)
{
GNUNET_assert(shorthash);
const size_t id = GNUNET_MESSENGER_contact_get_id(member);
memset(shorthash, 0, sizeof(*shorthash));
GNUNET_memcpy(
shorthash,
&id,
sizeof(id) < sizeof(*shorthash) ? sizeof(id) : sizeof(*shorthash)
);
}
void
util_set_name_field (const char *name,
char **field)
{
GNUNET_assert(field);
if (*field)
GNUNET_free(*field);
if (name)
*field = GNUNET_strdup(name);
else
*field = NULL;
}
enum GNUNET_GenericReturnValue
util_hash_file (const char *filename,
struct GNUNET_HashCode *hash)
{
GNUNET_assert((filename) && (hash));
uint64_t size;
if (GNUNET_OK != GNUNET_DISK_file_size(filename, &size, GNUNET_NO, GNUNET_YES))
return GNUNET_SYSERR;
struct GNUNET_DISK_FileHandle *file = GNUNET_DISK_file_open(
filename, GNUNET_DISK_OPEN_READ, GNUNET_DISK_PERM_USER_READ
);
if (!file)
return GNUNET_SYSERR;
struct GNUNET_DISK_MapHandle *mapping;
const void* data;
if (size > 0)
{
data = GNUNET_DISK_file_map(
file, &mapping, GNUNET_DISK_MAP_TYPE_READ, size
);
if ((!data) || (!mapping))
{
GNUNET_DISK_file_close(file);
return GNUNET_SYSERR;
}
}
else
{
mapping = NULL;
data = NULL;
}
GNUNET_CRYPTO_hash(data, size, hash);
if (mapping)
GNUNET_DISK_file_unmap(mapping);
GNUNET_DISK_file_close(file);
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
util_encrypt_file (const char *filename,
const struct GNUNET_HashCode *hash,
const struct GNUNET_CRYPTO_SymmetricSessionKey *key)
{
GNUNET_assert((filename) && (hash));
uint64_t size;
if (GNUNET_OK != GNUNET_DISK_file_size(filename, &size, GNUNET_NO, GNUNET_YES))
return GNUNET_SYSERR;
struct GNUNET_DISK_FileHandle *file = GNUNET_DISK_file_open(
filename, GNUNET_DISK_OPEN_READWRITE,
GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE
);
if (!file)
return GNUNET_SYSERR;
if (!size)
return GNUNET_DISK_file_close(file);
struct GNUNET_DISK_MapHandle *mapping;
const void* data = GNUNET_DISK_file_map(
file, &mapping, GNUNET_DISK_MAP_TYPE_READWRITE, size
);
if ((!data) || (!mapping))
{
GNUNET_DISK_file_close(file);
return GNUNET_SYSERR;
}
struct GNUNET_CRYPTO_SymmetricInitializationVector iv;
const uint64_t block_size = 1024*1024;
ssize_t result = 0;
const uint64_t blocks = ((size + block_size - 1) / block_size);
if (!key)
goto skip_encryption;
for (uint64_t i = 0; i < blocks; i++)
{
const uint64_t index = (blocks - i - 1);
const uint64_t offset = block_size * index;
const uint64_t remaining = (size - offset);
void* location = ((uint8_t*) data) + offset;
if (index > 0)
memcpy(&iv, ((uint8_t*) data) + (block_size * (index - 1)), sizeof(iv));
else
memcpy(&iv, hash, sizeof(iv));
result = GNUNET_CRYPTO_symmetric_encrypt(
location,
remaining >= block_size? block_size : remaining,
key,
&iv,
location
);
if (result < 0)
break;
}
skip_encryption:
if (GNUNET_OK != GNUNET_DISK_file_unmap(mapping))
result = -1;
if (GNUNET_OK != GNUNET_DISK_file_sync(file))
result = -1;
if (GNUNET_OK != GNUNET_DISK_file_close(file))
result = -1;
if (result < 0)
return GNUNET_SYSERR;
return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
util_decrypt_file (const char *filename,
const struct GNUNET_HashCode *hash,
const struct GNUNET_CRYPTO_SymmetricSessionKey *key)
{
GNUNET_assert((filename) && (hash));
uint64_t size;
if (GNUNET_OK != GNUNET_DISK_file_size(filename, &size, GNUNET_NO, GNUNET_YES))
return GNUNET_SYSERR;
struct GNUNET_DISK_FileHandle *file = GNUNET_DISK_file_open(
filename, GNUNET_DISK_OPEN_READWRITE,
GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE
);
if (!file)
return GNUNET_SYSERR;
struct GNUNET_DISK_MapHandle *mapping = NULL;
void* data = GNUNET_DISK_file_map(
file, &mapping, GNUNET_DISK_MAP_TYPE_READWRITE, size
);
if ((!data) || (!mapping))
{
GNUNET_DISK_file_close(file);
return GNUNET_SYSERR;
}
struct GNUNET_CRYPTO_SymmetricInitializationVector iv;
const uint64_t block_size = 1024*1024;
struct GNUNET_HashCode check;
ssize_t result = 0;
const uint64_t blocks = ((size + block_size - 1) / block_size);
if (!key)
goto skip_decryption;
for (uint64_t index = 0; index < blocks; index++)
{
const uint64_t offset = block_size * index;
const uint64_t remaining = (size - offset);
void* location = ((uint8_t*) data) + offset;
if (index > 0)
memcpy(&iv, ((uint8_t*) data) + (block_size * (index - 1)), sizeof(iv));
else
memcpy(&iv, hash, sizeof(iv));
result = GNUNET_CRYPTO_symmetric_decrypt(
location,
remaining >= block_size? block_size : remaining,
key,
&iv,
location
);
if (result < 0)
break;
}
skip_decryption:
GNUNET_CRYPTO_hash(data, size, &check);
if (0 != GNUNET_CRYPTO_hash_cmp(hash, &check))
result = -1;
if (GNUNET_OK != GNUNET_DISK_file_unmap(mapping))
result = -1;
if (GNUNET_OK != GNUNET_DISK_file_sync(file))
result = -1;
if (GNUNET_OK != GNUNET_DISK_file_close(file))
result = -1;
if (result < 0)
return GNUNET_SYSERR;
return GNUNET_OK;
}
int
util_get_dirname (const char *directory,
const char *subdir,
char **filename)
{
GNUNET_assert(
(filename) &&
(directory) &&
(subdir)
);
return GNUNET_asprintf (
filename,
"%s/%s",
directory,
subdir
);
}
int
util_get_filename (const char *directory,
const char *subdir,
const struct GNUNET_HashCode *hash,
char **filename)
{
GNUNET_assert(
(filename) &&
(directory) &&
(subdir) &&
(hash)
);
char* dirname;
util_get_dirname(directory, subdir, &dirname);
int result = GNUNET_asprintf (
filename,
"%s/%s",
dirname,
GNUNET_h2s_full(hash)
);
GNUNET_free(dirname);
return result;
}
char*
util_get_lower(const char *name)
{
GNUNET_assert(name);
char *lower = GNUNET_malloc(strlen(name) + 1);
if (GNUNET_OK == GNUNET_STRINGS_utf8_tolower(name, lower))
return lower;
GNUNET_free(lower);
return GNUNET_strdup(name);
}
int
util_get_context_label (enum GNUNET_CHAT_ContextType type,
const struct GNUNET_HashCode *hash,
char **label)
{
GNUNET_assert((hash) && (label));
const char *type_string = "chat";
switch (type)
{
case GNUNET_CHAT_CONTEXT_TYPE_CONTACT:
type_string = "contact";
break;
case GNUNET_CHAT_CONTEXT_TYPE_GROUP:
type_string = "group";
break;
default:
break;
}
char *low = util_get_lower(GNUNET_h2s(hash));
int result = GNUNET_asprintf (
label,
"%s_%s",
type_string,
low
);
GNUNET_free(low);
return result;
}
enum GNUNET_CHAT_ContextType
util_get_context_label_type (const char *label,
const struct GNUNET_HashCode *hash)
{
GNUNET_assert((hash) && (label));
enum GNUNET_CHAT_ContextType type = GNUNET_CHAT_CONTEXT_TYPE_UNKNOWN;
char *low = util_get_lower(GNUNET_h2s(hash));
const char *sub = strstr(label, low);
if ((!sub) || (sub == label) || (sub[-1] != '_'))
goto cleanup;
const size_t len = (size_t) (sub - label - 1);
if (0 == strncmp(label, label_prefix_of_group, len))
type = GNUNET_CHAT_CONTEXT_TYPE_GROUP;
else if (0 == strncmp(label, label_prefix_of_contact, len))
type = GNUNET_CHAT_CONTEXT_TYPE_CONTACT;
cleanup:
GNUNET_free(low);
return type;
}
int
util_lobby_name (const struct GNUNET_HashCode *hash,
char **name)
{
GNUNET_assert((hash) && (name));
char *low = util_get_lower(GNUNET_h2s(hash));
int result = GNUNET_asprintf (
name,
"%s_%s",
identity_prefix_of_lobby,
low
);
GNUNET_free(low);
return result;
}
enum GNUNET_GenericReturnValue
util_is_lobby_name(const char *name)
{
GNUNET_assert(name);
const char *sub = strstr(name, identity_prefix_of_lobby);
if ((!sub) || (sub != name))
return GNUNET_NO;
const size_t len = strlen(identity_prefix_of_lobby);
if (name[len] != '_')
return GNUNET_NO;
else
return GNUNET_YES;
}
enum GNUNET_CHAT_MessageKind
util_message_kind_from_kind (enum GNUNET_MESSENGER_MessageKind kind)
{
switch (kind)
{
case GNUNET_MESSENGER_KIND_JOIN:
return GNUNET_CHAT_KIND_JOIN;
case GNUNET_MESSENGER_KIND_LEAVE:
return GNUNET_CHAT_KIND_LEAVE;
case GNUNET_MESSENGER_KIND_NAME:
case GNUNET_MESSENGER_KIND_KEY:
case GNUNET_MESSENGER_KIND_ID:
return GNUNET_CHAT_KIND_CONTACT;
case GNUNET_MESSENGER_KIND_INVITE:
return GNUNET_CHAT_KIND_INVITATION;
case GNUNET_MESSENGER_KIND_TEXT:
return GNUNET_CHAT_KIND_TEXT;
case GNUNET_MESSENGER_KIND_FILE:
return GNUNET_CHAT_KIND_FILE;
case GNUNET_MESSENGER_KIND_DELETE:
return GNUNET_CHAT_KIND_DELETION;
case GNUNET_MESSENGER_KIND_TICKET:
return GNUNET_CHAT_KIND_SHARED_ATTRIBUTES;
case GNUNET_MESSENGER_KIND_TAG:
return GNUNET_CHAT_KIND_TAG;
default:
return GNUNET_CHAT_KIND_UNKNOWN;
}
}