pull/1057/merge
irungentoo 2014-09-22 15:07:14 -04:00
commit b52da45aeb
No known key found for this signature in database
GPG Key ID: 10349DC9BED89E98
7 changed files with 2259 additions and 0 deletions

611
docs/Avatars.md Normal file
View File

@ -0,0 +1,611 @@
# User avatars in Tox
## Introduction and rationale
User avatars are small icons or images used to identify users in the friend
list; they exists in virtually all VoIP and IM protocols and provide an easy
way to an user identify another in the friend list.
This document describes the implementation of avatars in the Tox protocol,
according to the following design considerations:
- Avatars are handled as private information, ie., only exchanged over
Tox encrypted channels among previously authenticated friends;
- The library treats all images as blobs and does not interpret or
understands image formats, only ensures that the avatar data sent by
an user is correctly received by the other. The client application is
responsible for validating, decoding, resizing, and presenting the
image to the user.
- There is a strict limit of 16 KiB to the avatar raw data size -- this
seems suitable for practical use as, for example, the raw data of an
uncompressed 64 x 64 pixels 24 bpp RGB bitmap is 12288 bytes long; the
data limit provides enough space for larger bitmaps if the usual
compressed formats are used.
**Notice:** As designed, this limit can be changed in the future without
breaking the protocol compatibility, but clients using the original
limit will reject larger avatars;
- The protocol MUST provide means to allow caching and avoid unnecessary
data transfers;
- Avatars are transfered between clients in a background operation;
- Avatars are served in a "best effort" basis, without breaking clients
who do not support them;
- The protocol MUST resist to malicious users;
- The protocol MUST work with both UDP and TCP networks.
The Single Tox Standard Draft v.0.1.0 recommends implementing avatars as
a purely client-side feature through a procedure that can be summarized as
sending a specially named file as a file transfer request and accepting
it silently. This procedure can be improved to provide the previously stated
design considerations, but this requires a higher integration with the core
protocol. Moving this feature to the core protocol also:
- Provides a simpler and cleaner interfaces for client applications;
- Hides protocol complexities from the client;
- Avoids code duplication and ad-hoc protocols in the clients;
- Avoids incompatibility between client implementations;
- Allows important optimizations such as lightweight notification of
removed and updated avatars;
- Plays well with cache schemes;
- Makes avatar transfer an essentially background operation.
## High level description
The avatar exchange is implemented with the following new elements in the
Tox protocol. This is a very high level description and the usage patterns
expected from client applications are described in Section "Using Avatars
in Client Applications" and a low level protocol description is available
in Section "Internal Protocol Description".
- **Avatar Information Notifications** are events which may be sent by
an user to another anytime, but are usually sent after one of them
connects to the network, changes his avatar, or in reply to an **avatar
information request**. They are delivered by a very lightweight message
but with information enough to allow an user to validate or discard an
avatar from the local cache and decide if is interesting to request the
avatar data from the peer.
This event contain two data fields: (1) the image format and (2) the
cryptographic hash of the actual image data. Image format may be NONE
(for users who have no avatar or removed their avatars) or PNG. The
cryptographic hash is intended to be compared with the hash o the
currently cached avatar (if any) and check if it stills up to date.
- **Avatar Information Requests** are very lightweight messages sent by an
user asking for an **avatar information notification**. They may be sent
as part of the login process or when the client thinks the currently
cached avatar is outdated. The receiver may or may not answer to this
request. This message contains no data fields;
- An **Avatar Data Request** is sent by an user asking another for his
complete avatar data. It is sent only when the requesting user decides
the avatar do not exists in the local cache or is outdated. The receiver
may or may not answer to this request. This message contains no data
fields.
- An **Avatar Data Notification** is an event signaling the client that
the complete avatar image data of another user is available. The actual
data transfer is implemented using several data and control messages,
but the details are hidden from the client applications. This event can
only arrive in reply to an **avatar data request**.
This event contains three data fields: (1) the image format, (2) the
cryptographic hash of the image data, and (3) the raw image data. If the
image format is NONE (i.e. no avatar) the hash is zeroed and the image
data is empty. The raw image data is locally validated and ensured to
match the hash (the event is **not** triggered otherwise).
## API
To implement this feature, the following public symbols were added. The
complete API documentation is available in `tox.h`.
```
#define TOX_MAX_AVATAR_DATA_LENGTH 16384
#define TOX_AVATAR_HASH_LENGTH 32
/* Data formats for user avatar images */
typedef enum {
TOX_AVATARFORMAT_NONE,
TOX_AVATARFORMAT_PNG
}
TOX_AVATARFORMAT;
/* Set the user avatar image data. */
int tox_set_avatar(Tox *tox, uint8_t format, const uint8_t *data, uint32_t length);
/* Get avatar data from the current user. */
int tox_get_self_avatar(const Tox *tox, uint8_t *format, uint8_t *buf, uint32_t *length, uint32_t maxlen, uint8_t *hash);
/* Generates a cryptographic hash of the given avatar data. */
int tox_avatar_hash(const Tox *tox, uint8_t *hash, const uint8_t *data, const uint32_t datalen);
/* Request avatar information from a friend. */
int tox_request_avatar_info(const Tox *tox, const int32_t friendnumber);
/* Send an unrequested avatar information to a friend. */
int tox_send_avatar_info(Tox *tox, const int32_t friendnumber);
/* Request the avatar data from a friend. */
int tox_request_avatar_data(const Tox *tox, const int32_t friendnumber);
/* Set the callback function for avatar data. */
void tox_callback_avatar_info(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t*, void *), void *userdata);
/* Set the callback function for avatar data. */
void tox_callback_avatar_data(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t*, uint8_t*, uint32_t, void *), void *userdata);
```
## Using Avatars in Client Applications
### General recommendations
- Clients MUST NOT imply the availability of avatars in other users.
Avatars are an optional feature and not all users and clients may
support them;
- Clients MUST NOT block waiting for avatar information and avatar data
packets;
- Clients MUST treat avatar data as insecure and potentially malicious;
For example, users may accidentally use corrupted images as avatars,
a malicious user may send a specially crafted image to exploit a know
vulnerability in an image decoding library, etc. It is recommended to
handle the avatar image data in the same way as an image downloaded
from an unknown Internet source;
- The peers MUST NOT assume any coupling between the operations of
receiving an avatar information packet, sending unrequested avatar
information packets, requesting avatar data, or receiving avatar data.
For example, the following situations are valid:
* A text-mode client may send avatars to other users, but never
request them;
* A client may not understand a particular image format and ignore
avatars using it, but request and handle other formats;
- Clients SHOULD implement a local cache of avatars and do not request
avatar data from other peers unless necessary;
- When an avatar information is received, the client should delete the
avatar if the new avatar format is NONE or compare the hash received
from the peer with the hash of the currently cached avatar. If they
differ, send an avatar data request;
- If the cached avatar is older than a given threshold, the client may
also send an avatar info request to that friend once he is online and
mark the avatar as updated *before* any avatar information is received
(to not spam the peer with such requests);
- When an avatar data notification is received, the client must update
the cached avatar with the new one;
- Clients should resize or crop the image to the way it better adapts
to the client user interface;
- If the user already have an avatar defined in the client configuration,
it must be set before connecting to the network to avoid spurious avatar
change notifications and unnecessary data transfers.
- If no avatar data is available for a given friend, the client should
show a placeholder image.
### Interoperability and sharing avatars among different clients
**This section is a tentative recommendation of how clients should store
avatars to ensure local interoperability and should be revised if this
code is accepted into Tox core.**
It is desirable that the user avatar and the cached friends avatars could be
shared among different Tox clients in the same system, in the spirit of the
proposed Single Tox Standard. This not only makes switching from one client
to another easier, but also minimizes the need of data transfers, as avatars
already downloaded by other clients can be reused.
Given the Tox data directory described in STS Draft v0.1.0:
- The user avatar is stored in a file named "avatar.png". As more formats
may be used in the future, another extensions are reserved and clients
should keep just one file named "avatar.*", with the data of the last
avatar set by the user. If the user have no avatar, no such files should
be kept in the data directory;
- Friends avatars are stored in a directory called "avatars" and named
as "xxxxx.png", where "xxxxx" is the complete client id encoded as an
uppercase hexadecimal string and "png" is the extension for the PNG
avatar. As new image formats may be used in the future, clients should
ensure no other file "xxxxx.*" exists. No file should be kept for an user
who have no avatar.
**To be discussed:** User keys are usually presented in Tox clients as
upper case strings, but lower case file names are more usual.
Example for Linux and other Unix systems, assuming an user called "gildor":
Tox data directory: /home/gildor/.config/tox/
Tox data file: /home/gildor/.config/tox/data
Gildor's avatar: /home/gildor/.config/tox/avatar.png
Avatar data dir: /home/gildor/.config/tox/avatars/
Elrond's avatar: /home/gildor/.config/tox/avatars/43656C65627269616E20646F6E277420546F782E426164206D656D6F72696573.png
Elladan's avatar: /home/gildor/.config/tox/avatars/49486174655768656E48756D616E735468696E6B49416D4D7942726F74686572.png
Elrohir's avatar /home/gildor/.config/tox/avatars/726568746F7242794D6D41496B6E696854736E616D75486E6568576574614849.png
Arwen's avatar: /home/gildor/.config/tox/avatars/53686520746F6F6B20476C6F7266696E64656C277320706C6163652068657265.png
Lindir's avatar: /home/gildor/.config/tox/avatars/417070735772697474656E42794D6F7274616C734C6F6F6B54686553616D652E.png
This recommendation is partially implemented by "testing/test_avatars.c".
### Common operations
These are minimal examples of how perform common operations with avatar
functions. For a complete, working, example, see `testing/test_avatars.c`.
#### Setting an avatar for the current user
In this example `load_data_file` is just an hypothetical function that loads
data from a file into the buffer and sets the length accordingly.
uint8_t buf[TOX_MAX_AVATAR_DATA_LENGTH];
uint32_t len;
if (load_data_file("avatar.png", buf, &len) == 0)
if (tox_set_avatar(tox, TOX_AVATARFORMAT_PNG, buf, len) != 0)
fprintf(stderr, "Failed to set avatar.\n");
If the user is connected, this function will also notify all connected
friends about the avatar change.
If the user already have an avatar defined in the client configuration, it
must be set before connecting to the network to avoid spurious avatar change
notifications and unnecessary data transfers.
#### Removing the avatar from the current user
To remove an avatar, an application must set it to `TOX_AVATARFORMAT_NONE`.
tox_set_avatar(tox, TOX_AVATARFORMAT_NONE, NULL, 0);
If the user is connected, this function will also notify all connected
friends about the avatar change.
#### Receiving avatar information from friends
All avatar information is passed to a callback function with the prototype:
void function(Tox *tox, int32_t friendnumber, uint8_t format,
uint8_t *hash, uint8_t *data, uint32_t datalen, void *userdata)
As in this example:
static void avatar_info_cb(Tox *tox, int32_t friendnumber, uint8_t format,
uint8_t *hash, void *userdata)
{
printf("Receiving avatar information from friend %d. Format = %d\n",
friendnumber, format);
printf("Data hash: ");
hex_printf(hash, TOX_AVATAR_HASH_LENGTH); /* Hypothetical function */
printf("\n");
}
And, somewhere in the Tox initialization calls, set if as the callback to be
triggered when an avatar information event arrives:
tox_callback_avatar_info(tox, avatar_info_cb, NULL);
A typical client will test the currently cached avatar against the hash given
in the avatar information event and, if needed, request the avatar data.
#### Receiving avatar data from friends
Avatar data events are only delivered in reply of avatar data requests which
**should** only be sent after getting the user avatar information (format
and hash) from an avatar information event and checking it against a local
cache.
For this, an application must define an avatar information callback which
checks the local avatar cache and emits an avatar data request if necessary:
static void avatar_info_cb(Tox *tox, int32_t friendnumber, uint8_t format,
uint8_t *hash, void *userdata)
{
printf("Receiving avatar information from friend %d. Format = %d\n",
friendnumber, format);
if (format = TOX_AVATARFORMAT_NONE) {
/* User have no avatar or removed the avatar */
delete_avatar_from_cache(tox, friendnumber);
} else {
/* Use the received hash to check if the cached avatar is
still updated. */
if (!is_user_cached_avatar_updated(tox, friendnumber, hash)) {
/* User avatar is outdated, send data request */
tox_request_avatar_data(tox, friendnumber);
}
}
}
Then define an avatar data callback to store the received data in the local
cache:
static void avatar_data_cb(Tox *tox, int32_t friendnumber, uint8_t format,
uint8_t *hash, uint8_t *data, uint32_t datalen, void *userdata)
{
if (format = TOX_AVATARFORMAT_NONE) {
/* User have no avatar or removed the avatar */
delete_avatar_from_cache(tox, friendnumber);
} else {
save_avatar_data_to_cache(tox, friendnumber, format, hash,
data, datalen);
}
}
And, finally, register both callbacks somewhere in the Tox initialization
calls:
tox_callback_avatar_info(tox, avatar_info_cb, NULL);
tox_callback_avatar_data(tox, avatar_data_cb, NULL);
In the previous examples, implementation of the functions to check, store
and retrieve data from the cache were omitted for brevity. These functions
will also need to get the friend client ID (public key) from they friend
number and, usually, convert it from a byte string to a hexadecimal
string. A complete, yet more complex, example is available in the file
`testing/test_avatars.c`.
## Internal Protocol Description
### New packet types
The avatar transfer protocol adds the following new packet types and ids:
PACKET_ID_AVATAR_INFO_REQ = 52
PACKET_ID_AVATAR_INFO = 53
PACKET_ID_AVATAR_DATA_CONTROL = 54
PACKET_ID_AVATAR_DATA_START = 55
PACKET_ID_AVATAR_DATA_PUSH = 56
### Requesting avatar information
To request avatar information, an user must send a packet of type
`PACKET_ID_AVATAR_INFO_REQ`. This packet have no data fields. Upon
receiving this packet, a client which supports avatars should answer with
a `PACKET_ID_AVATAR_INFO`. The sender must accept that the friend may
not answer at all.
### Receiving avatar information
Avatar information arrives in a packet of type `PACKET_ID_AVATAR_INFO` with
the following structure:
PACKET_ID_AVATAR_INFO (53)
Packet data size: 33 bytes
[1: uint8_t format][32: uint8_t hash]
Where 'format' is the image data format, one of the following:
0 = AVATARFORMAT_NONE (no avatar set)
1 = AVATARFORMAT_PNG
and 'hash' is the SHA-256 message digest of the avatar data.
This packet may be sent at any time and no previous request is required.
Clients should send this packet upon connection or when a friend
connects, in the same way Tox sends name, status and action information.
### Requesting avatar data
Transmission of avatar data is a multi-step procedure using three new packet
types.
- Packet `PACKET_ID_AVATAR_DATA_CONTROL` have the format:
PACKET_ID_AVATAR_DATA_CONTROL (54)
Packet data size: 1 byte
[1: uint8_t op]
where 'op' is a code signaling both an operation request or a status
return, which semantics are explained bellow. The following values are
defined:
0 = AVATARDATACONTROL_REQ
1 = AVATARDATACONTROL_ERROR
- Packet `PACKET_ID_AVATAR_DATA_START` have the following format:
PACKET_ID_AVATAR_DATA_START (55)
Packet data size: 37 bytes
[1: uint8_t format][32: uint8_t hash][1: uint32_t data_length]
where 'format' is the image format, with the same values accepted for
the field 'format' in packet type `PACKET_ID_AVATAR_INFO`, 'hash' is
the SHA-256 cryptographic hash of the avatar raw data and 'data_length'
is the total number of bytes the raw avatar data.
- Packet `PACKET_ID_AVATAR_DATA_PUSH` have no format structure, just up
to `AVATAR_DATA_MAX_CHUNK_SIZE` bytes of raw avatar image data; this
value is defined according to the maximum amount of data a Tox crypted
packet can hold.
The following procedure assumes that a client "A" is requesting avatar data
from a client "B":
- "A" must initialize its control structures and mark its data transfer
as not yet started. Then it requests avatar data from "B" by sending a
packet `PACKET_ID_AVATAR_DATA_CONTROL` with 'op' set to
`AVATARDATACONTROL_REQ`.
- If "B" accepts this transfer, it answers by sending an
`PACKET_ID_AVATAR_DATA_START` with the fields 'format', 'hash' and
'data_length' set to the respective values from the current avatar.
If "B" have no avatar set, 'format' must be `AVATARFORMAT_NONE`, 'hash'
must be zeroed and 'data_length' must be zero.
If "B" does not accept sending the avatar, it may send a packet
`PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to
`AVATARDATACONTROL_ERROR` or simply ignore this request. "A" must cope
with this.
If "B" have an avatar, it sends a variable number of
`PACKET_ID_AVATAR_DATA_PUSH` packets with the avatar data in a single
shot.
- Upon receiving a `PACKET_ID_AVATAR_DATA_START`, "A" checks if it
has sent a data request to "B". If not, just ignores the packet.
If "A" really requested avatar data and the format is `AVATARFORMAT_NONE`,
it triggers the avatar data callback, and clears all the temporary data,
finishing the process. For other formats, "A" just waits for packets
of type `PACKET_ID_AVATAR_DATA_PUSH`.
- Upon receiving a `PACKET_ID_AVATAR_DATA_PUSH`, "A" checks if it really
sent an avatar data request and if the `PACKET_ID_AVATAR_DATA_START` was
already received. If this conditions are valid, it checks if the total
length of the data already stored in the receiving buffer plus the data
present in the push packet is still less or equal than
`TOX_MAX_AVATAR_DATA_LENGTH`. If invalid, it replies with a
`PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to
`AVATARDATACONTROL_ERROR`.
If valid, "A" updates the 'bytes_received' counter and concatenates the
newly arrived data to the buffer.
The "A" checks if all the data was already received by comparing the
counter 'bytes_received' with the field 'total_length'. If they are
equal, "A" takes a SHA-256 hash of the data and compares it with the
hash stored in the field 'hash' received from the first
`PACKET_ID_AVATAR_DATA_START`.
If the hashes match, the avatar data was correctly received and "A"
triggers the avatar data callback, and clears all the temporary data,
finishing the process.
If not all data was received, "A" simply waits for more data.
Client "A" is always responsible for controlling the transfer and
validating the data received. "B" don't need to keep any state for the
protocol, have full control over the data sent and should implement
some transfer limit for the data it sends.
- Any peer receiving a `PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op'
set to `AVATARDATACONTROL_ERROR` clears any existing control state and
finishes sending or receiving data.
## Security considerations
The major security implication of background data transfers of large objects,
like avatars, is the possibility of exhausting the network resources from a
client. This problem is exacerbated when there is the possibility of an
amplification attack as happens, for example, when sending a very small
avatar request message will force the user to reply with a larger avatar
data message.
The present proposal mitigates this situation by:
- Only transferring data between previously authenticated friends;
- Enforcing strict limits on the avatar data size;
- Providing an alternate, smaller, message to cooperative users refresh
avatar information when nothing has changed (`PACKET_ID_AVATAR_INFO`);
- Having per-friend data transfer limit. As the current protocol still
allows an user to request an infinite data stream by asking the the
same offset of the avatar again and again, the implementation limits
the amount of data a single user can request for some time. For now,
the library will not allow an user to request more than
`10*TOX_MAX_AVATAR_DATA_LENGTH` in less than 20 minutes;
- Making the requester responsible for storing partial data and state
information;
Another problem present in the avatars is the possibility of a friend send
a maliciously crafted image intended to exploit vulnerabilities in image
decoders. Without an intermediate server to recompress and validate and
convert the images to neutral formats, the client applications must handle
this situation by themselves using stable and secure image libraries and
imposing limits on the maximum amount of system resources the decoding
process can take. Images coming from Tox friends must be treated in the same
way as images coming from random Internet sources.

View File

@ -105,6 +105,21 @@ tox_shell_LDADD = $(LIBSODIUM_LDFLAGS) \
$(NACL_LIBS) \
-lutil
noinst_PROGRAMS += test_avatars
test_avatars_SOURCES = ../testing/test_avatars.c
test_avatars_CFLAGS = $(LIBSODIUM_CFLAGS) \
$(NACL_CFLAGS)
test_avatars_LDADD = $(LIBSODIUM_LDFLAGS) \
$(NACL_LDFLAGS) \
libtoxcore.la \
$(LIBSODIUM_LIBS) \
$(NACL_OBJECTS) \
$(NACL_LIBS)
endif
EXTRA_DIST += $(top_srcdir)/testing/misc_tools.c

770
testing/test_avatars.c Normal file
View File

@ -0,0 +1,770 @@
/*
* A bot to test Tox avatars
*
* Usage: ./test_avatars <data dir>
*
* Connects to the Tox network, publishes our avatar, requests our friends
* avatars and, if available, saves them to a local cache.
* This bot automatically accepts any friend request.
*
*
* Data dir MUST have:
*
* - A file named "data" (named accordingly to STS Draft v0.1.0) with
* user id, friends, bootstrap data, etc. from a previously configured
* Tox session; use a client (eg. toxic) to configure it, add friends,
* etc.
*
* Data dir MAY have:
*
* - A file named avatar.png. If given, the bot will publish it. Otherwise,
* no avatar will be set.
*
* - A directory named "avatars" with the currently cached avatars.
*
*
* The bot will answer to these commands:
*
* !debug-on - Enable extended debug messages
* !debug-off - Disenable extended debug messages
* !set-avatar - Set our avatar to the contents of the file avatar.*
* !remove-avatar - Remove our avatar
*
*/
#define DATA_FILE_NAME "data"
#define AVATAR_DIR_NAME "avatars"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "../toxcore/tox.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <stdbool.h>
#include <linux/limits.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <stdarg.h>
/* Basic debug utils */
#define DEBUG(format, ...) debug_printf("DEBUG: %s:%d %s: " format "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__)
static bool print_debug_msgs = true;
static void debug_printf(const char *fmt, ...)
{
if (print_debug_msgs == true) {
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
}
/* ------------ Avatar cache managenment functions ------------ */
typedef struct {
uint8_t format;
char *suffix;
char *file_name;
} def_avatar_name_t;
static const def_avatar_name_t def_avatar_names[] = {
/* In order of preference */
{ TOX_AVATARFORMAT_PNG, "png", "avatar.png" },
{ TOX_AVATARFORMAT_NONE, NULL, NULL }, /* Must be the last one */
};
static void set_avatar(Tox *tox, const char *base_dir);
static char *get_avatar_suffix_from_format(uint8_t format)
{
int i;
for (i = 0; def_avatar_names[i].format != TOX_AVATARFORMAT_NONE; i++)
if (def_avatar_names[i].format == format)
return def_avatar_names[i].suffix;
return NULL;
}
/* Load avatar data from a file into a memory buffer 'buf'.
* buf must have at least TOX_MAX_AVATAR_DATA_LENGTH bytes
* Returns the length of the data sucess or < 0 on error
*/
static int load_avatar_data(char *fname, uint8_t *buf)
{
FILE *fp = fopen(fname, "rb");
if (fp == NULL)
return -1; /* Error */
size_t n = fread(buf, 1, TOX_MAX_AVATAR_DATA_LENGTH, fp);
int ret;
if (ferror(fp) != 0 || n == 0)
ret = -1; /* Error */
else
ret = n;
fclose(fp);
return ret;
}
/* Save avatar data to a file */
static int save_avatar_data(char *fname, uint8_t *data, uint32_t len)
{
FILE *fp = fopen(fname, "wb");
if (fp == NULL)
return -1; /* Error */
int ret = 0; /* Ok */
if (fwrite(data, 1, len, fp) != len)
ret = -1; /* Error */
if (fclose(fp) != 0)
ret = -1; /* Error */
return ret;
}
static void byte_to_hex_str(const uint8_t *buf, const size_t buflen, char *dst)
{
const char *hex_chars = "0123456789ABCDEF";
size_t i = 0;
size_t j = 0;
while (i < buflen) {
dst[j++] = hex_chars[(buf[i] >> 4) & 0xf];
dst[j++] = hex_chars[buf[i] & 0xf];
i++;
}
dst[j++] = '\0';
}
/* Make the cache file name for a avatar of the given format for the given
* client id.
*/
static int make_avatar_file_name(char *dst, size_t dst_len,
char *base_dir, uint8_t format, uint8_t *client_id)
{
char client_id_str[2 * TOX_CLIENT_ID_SIZE + 1];
byte_to_hex_str(client_id, TOX_CLIENT_ID_SIZE, client_id_str);
const char *suffix = get_avatar_suffix_from_format(format);
if (suffix == NULL)
return -1; /* Error */
int n = snprintf(dst, dst_len, "%s/%s/%s.%s", base_dir, AVATAR_DIR_NAME,
client_id_str, suffix);
dst[dst_len - 1] = '\0';
if (n >= dst_len)
return -1; /* Error: Output truncated */
return 0; /* Ok */
}
/* Load a cached avatar into the buffer 'data' (which must be at least
* TOX_MAX_AVATAR_DATA_LENGTH bytes long). Gets the file name from client
* id and the given data format.
* Returns 0 on success, or -1 on error.
*/
static int load_user_avatar(Tox *tox, char *base_dir, int friendnum,
uint8_t format, uint8_t *hash, uint8_t *data, uint32_t *datalen)
{
uint8_t addr[TOX_CLIENT_ID_SIZE];
if (tox_get_client_id(tox, friendnum, addr) != 0) {
DEBUG("Bad client id, friendnumber=%d", friendnum);
return -1;
}
char path[PATH_MAX];
int ret = make_avatar_file_name(path, sizeof(path), base_dir, format, addr);
if (ret != 0) {
DEBUG("Can't create an file name for this user/avatar.");
return -1;
}
ret = load_avatar_data(path, data);
if (ret < 0) {
DEBUG("Failed to load avatar data.");
return -1;
}
*datalen = ret;
tox_avatar_hash(tox, hash, data, *datalen);
return 0;
}
/* Save a user avatar into the cache. Gets the file name from client id and
* the given data format.
* Returns 0 on success, or -1 on error.
*/
static int save_user_avatar(Tox *tox, char *base_dir, int friendnum,
uint8_t format, uint8_t *data, uint32_t datalen)
{
uint8_t addr[TOX_CLIENT_ID_SIZE];
if (tox_get_client_id(tox, friendnum, addr) != 0) {
DEBUG("Bad client id, friendnumber=%d", friendnum);
return -1;
}
char path[PATH_MAX];
int ret = make_avatar_file_name(path, sizeof(path), base_dir, format, addr);
if (ret != 0) {
DEBUG("Can't create a file name for this user/avatar");
return -1;
}
return save_avatar_data(path, data, datalen);
}
/* Delete all cached avatars for a given user */
static int delete_user_avatar(Tox *tox, char *base_dir, int friendnum)
{
uint8_t addr[TOX_CLIENT_ID_SIZE];
if (tox_get_client_id(tox, friendnum, addr) != 0) {
DEBUG("Bad client id, friendnumber=%d", friendnum);
return -1;
}
char path[PATH_MAX];
/* This iteration is dumb and inefficient */
int i;
for (i = 0; def_avatar_names[i].format != TOX_AVATARFORMAT_NONE; i++) {
int ret = make_avatar_file_name(path, sizeof(path), base_dir,
def_avatar_names[i].format, addr);
if (ret != 0) {
DEBUG("Failed to create avatar path for friend #%d, format %d\n",
friendnum, def_avatar_names[i].format);
continue;
}
if (unlink(path) == 0)
printf("Avatar file %s deleted.\n", path);
}
return 0;
}
/* ------------ Protocol callbacks ------------ */
static void friend_status_cb(Tox *tox, int n, uint8_t status, void *ud)
{
uint8_t addr[TOX_CLIENT_ID_SIZE];
char addr_str[2 * TOX_CLIENT_ID_SIZE + 1];
if (tox_get_client_id(tox, n, addr) == 0) {
byte_to_hex_str(addr, TOX_CLIENT_ID_SIZE, addr_str);
printf("Receiving status from %s: %u\n", addr_str, status);
}
}
static void friend_avatar_info_cb(Tox *tox, int32_t n, uint8_t format, uint8_t *hash, void *ud)
{
char *base_dir = (char *) ud;
uint8_t addr[TOX_CLIENT_ID_SIZE];
char addr_str[2 * TOX_CLIENT_ID_SIZE + 1];
char hash_str[2 * TOX_AVATAR_HASH_LENGTH + 1];
if (tox_get_client_id(tox, n, addr) == 0) {
byte_to_hex_str(addr, TOX_CLIENT_ID_SIZE, addr_str);
printf("Receiving avatar information from %s.\n", addr_str);
} else {
DEBUG("tox_get_client_id failed");
printf("Receiving avatar information from friend number %u.\n", n);
}
byte_to_hex_str(hash, TOX_AVATAR_HASH_LENGTH, hash_str);
DEBUG("format=%u, hash=%s", format, hash_str);
if (format == TOX_AVATARFORMAT_NONE) {
printf(" -> User do not have an avatar.\n");
/* User have no avatar anymore, delete it from our cache */
delete_user_avatar(tox, base_dir, n);
} else {
/* Check the hash of the currently cached user avatar
* WARNING: THIS IS ONLY AN EXAMPLE!
*
* Real clients should keep the hashes in memory (eg. in the object
* used to represent a friend in the friend list) and do not access
* the file system or do anything resource intensive in reply of
* these events.
*/
uint32_t cur_av_len;
uint8_t cur_av_data[TOX_MAX_AVATAR_DATA_LENGTH];
uint8_t cur_av_hash[TOX_AVATAR_HASH_LENGTH];
int ret;
ret = load_user_avatar(tox, base_dir, n, format, cur_av_hash, cur_av_data, &cur_av_len);
if (ret != 0
&& memcpy(cur_av_hash, hash, TOX_AVATAR_HASH_LENGTH) != 0) {
printf(" -> Cached avatar is outdated. Requesting avatar data.\n");
tox_request_avatar_data(tox, n);
} else {
printf(" -> Cached avatar is still updated.\n");
}
}
}
static void friend_avatar_data_cb(Tox *tox, int32_t n, uint8_t format,
uint8_t *hash, uint8_t *data, uint32_t datalen, void *ud)
{
char *base_dir = (char *) ud;
uint8_t addr[TOX_CLIENT_ID_SIZE];
char addr_str[2 * TOX_CLIENT_ID_SIZE + 1];
char hash_str[2 * TOX_AVATAR_HASH_LENGTH + 1];
if (tox_get_client_id(tox, n, addr) == 0) {
byte_to_hex_str(addr, TOX_CLIENT_ID_SIZE, addr_str);
printf("Receiving avatar data from %s.\n", addr_str);
} else {
DEBUG("tox_get_client_id failed");
printf("Receiving avatar data from friend number %u.\n", n);
}
byte_to_hex_str(hash, TOX_AVATAR_HASH_LENGTH, hash_str);
DEBUG("format=%u, datalen=%d, hash=%s\n", format, datalen, hash_str);
delete_user_avatar(tox, base_dir, n);
if (format != TOX_AVATARFORMAT_NONE) {
int ret = save_user_avatar(tox, base_dir, n, format, data, datalen);
if (ret == 0)
printf(" -> Avatar updated in the cache.\n");
else
printf(" -> Failed to save user avatar.\n");
}
}
static void friend_msg_cb(Tox *tox, int n, const uint8_t *msg, uint16_t len, void *ud)
{
const char *base_dir = (char *) ud;
const char *msg_str = (char *) msg;
uint8_t addr[TOX_CLIENT_ID_SIZE];
char addr_str[2 * TOX_CLIENT_ID_SIZE + 1];
if (tox_get_client_id(tox, n, addr) == 0) {
byte_to_hex_str(addr, TOX_FRIEND_ADDRESS_SIZE, addr_str);
printf("Receiving message from %s:\n %s\n", addr_str, msg);
}
/* Handle bot commands for the tests */
char *reply_ptr = NULL;
if (strstr(msg_str, "!debug-on") != NULL) {
print_debug_msgs = true;
reply_ptr = "Debug enabled.";
} else if (strstr(msg_str, "!debug-off") != NULL) {
print_debug_msgs = false;
reply_ptr = "Debug disabled.";
} else if (strstr(msg_str, "!set-avatar") != NULL) {
set_avatar(tox, base_dir);
reply_ptr = "Setting image avatar";
} else if (strstr(msg_str, "!remove-avatar") != NULL) {
int r = tox_set_avatar(tox, TOX_AVATARFORMAT_NONE, NULL, 0);
DEBUG("tox_set_avatar returned %d", r);
reply_ptr = "Removing avatar";
}
/* Add more useful commands here: add friend, etc. */
char reply[TOX_MAX_MESSAGE_LENGTH];
int reply_len;
if (reply_ptr)
reply_len = snprintf(reply, sizeof(reply), "%s", reply_ptr);
else
reply_len = snprintf(reply, sizeof(reply),
"No command found in message: %s", msg);
reply[sizeof(reply) - 1] = '\0';
printf(" -> Reply: %s\n", reply);
tox_send_message(tox, n, (uint8_t *) reply, reply_len);
}
static void friend_request_cb(Tox *tox, const uint8_t *public_key,
const uint8_t *data, uint16_t length, void *ud)
{
char addr_str[2 * TOX_CLIENT_ID_SIZE + 1];
byte_to_hex_str(public_key, TOX_CLIENT_ID_SIZE, addr_str);
printf("Accepting friend request from %s.\n %s\n", addr_str, data);
tox_add_friend_norequest(tox, public_key);
}
static int try_avatar_file(Tox *tox, const char *base_dir, const def_avatar_name_t *an)
{
char path[PATH_MAX];
int n = snprintf(path, sizeof(path), "%s/%s", base_dir, an->file_name);
path[sizeof(path) - 1] = '\0';
if (n >= sizeof(path)) {
DEBUG("error: path %s too big\n", path);
return -1;
}
DEBUG("trying file %s: ", path);
FILE *fp = fopen(path, "rb");
if (fp != NULL) {
uint8_t buf[2 * TOX_MAX_AVATAR_DATA_LENGTH];
int len = fread(buf, 1, sizeof(buf), fp);
if (len >= 0 && len <= TOX_MAX_AVATAR_DATA_LENGTH) {
int r = tox_set_avatar(tox, an->format, buf, len);
DEBUG("%d bytes, tox_set_avatar returned=%d", len, r);
if (r == 0)
printf("Setting avatar file %s\n", path);
else
printf("Error setting avatar file %s\n", path);
} else if (len < 0) {
DEBUG("read error %d", len);
} else {
printf("Avatar file %s if too big (more than %d bytes)",
path, TOX_MAX_AVATAR_DATA_LENGTH);
}
fclose(fp);
return 0;
} else {
DEBUG("File %s not found", path);
}
return -1;
}
static void set_avatar(Tox *tox, const char *base_dir)
{
int i;
for (i = 0; i < 4; i++) {
if (def_avatar_names[i].format == TOX_AVATARFORMAT_NONE) {
tox_set_avatar(tox, TOX_AVATARFORMAT_NONE, NULL, 0);
printf("No avatar file found, setting to NONE.\n");
return;
} else {
if (try_avatar_file(tox, base_dir, &def_avatar_names[i]) == 0)
return;
}
}
/* Should be unreachable */
printf("UNEXPECTED CODE PATH\n");
}
static void print_avatar_info(Tox *tox)
{
uint8_t format;
uint8_t data[TOX_MAX_AVATAR_DATA_LENGTH];
uint8_t hash[TOX_AVATAR_HASH_LENGTH];
uint32_t data_length;
char hash_str[2 * TOX_AVATAR_HASH_LENGTH + 1];
int ret = tox_get_self_avatar(tox, &format, data, &data_length, sizeof(data), hash);
DEBUG("tox_get_self_avatar returned %d", ret);
DEBUG("format: %d, data_length: %d", format, data_length);
byte_to_hex_str(hash, TOX_AVATAR_HASH_LENGTH, hash_str);
DEBUG("hash: %s", hash_str);
}
/* ------------ Initialization functions ------------ */
/* Create directory to store tha avatars. Returns 0 if it was sucessfuly
* created or already existed. Returns -1 on error.
*/
static int create_avatar_diretory(const char *base_dir)
{
char path[PATH_MAX];
int n = snprintf(path, sizeof(path), "%s/%s", base_dir, AVATAR_DIR_NAME);
path[sizeof(path) - 1] = '\0';
if (n >= sizeof(path))
return -1;
if (mkdir(path, 0755) == 0) {
return 0; /* Done */
} else if (errno == EEXIST) {
/* Check if the existing path is a directory */
struct stat st;
if (stat(path, &st) != 0) {
perror("stat()ing avatar directory");
return -1;
}
if (S_ISDIR(st.st_mode))
return 0;
}
return -1; /* Error */
}
static void *load_bootstrap_data(const char *base_dir, uint32_t *len)
{
char path[PATH_MAX];
int n = snprintf(path, sizeof(path), "%s/%s", base_dir, DATA_FILE_NAME);
path[sizeof(path) - 1] = '\0';
if (n >= sizeof(path)) {
printf("Load error: path %s too long\n", path);
return NULL;
}
/* We should be using POSIX functions here, but let's try to be
* compatible with Windows.
*/
FILE *fp = fopen(path, "rb");
if (fp == NULL) {
printf("fatal error: file %s not found.\n", path);
return NULL;
}
if (fseek(fp, 0, SEEK_END) != 0) {
printf("seek fail\n");
fclose(fp);
return NULL;
}
int32_t flen = ftell(fp);
if (flen < 8 || flen > 2e6) {
printf("Fatal error: file %s have %u bytes. Out of acceptable range.\n", path, flen);
fclose(fp);
return NULL;
}
if (fseek(fp, 0, SEEK_SET) != 0) {
printf("seek fail\n");
fclose(fp);
return NULL;
}
void *buf = malloc(flen);
if (buf == NULL) {
printf("malloc failed, %u bytes", flen);
fclose(fp);
return NULL;
}
*len = fread(buf, 1, flen, fp);
fclose(fp);
if (*len != flen) {
printf("fatal: %s have %u bytes, read only %u\n", path, flen, *len);
free(buf);
return NULL;
}
printf("bootstrap data loaded from %s (%u bytes)\n", path, flen);
return buf;
}
static int save_bootstrap_data(Tox *tox, const char *base_dir)
{
char path[PATH_MAX];
int n = snprintf(path, sizeof(path), "%s/%s", base_dir, DATA_FILE_NAME);
path[sizeof(path) - 1] = '\0';
if (n >= sizeof(path)) {
printf("Save error: path %s too long\n", path);
return -1;
}
char path_tmp[PATH_MAX];
n = snprintf(path_tmp, sizeof(path_tmp), "%s.tmp", path);
path_tmp[sizeof(path_tmp) - 1] = '\0';
if (n >= sizeof(path_tmp)) {
printf("error: path %s too long\n", path);
return -1;
}
uint32_t len = tox_size(tox);
if (len < 8 || len > 2e6) {
printf("save data length == %u, out of acceptable range\n", len);
return -1;
}
void *buf = malloc(len);
if (buf == NULL) {
printf("save data: malloc failed\n");
return -1;
}
tox_save(tox, buf);
FILE *fp = fopen(path_tmp, "wb");
if (fp == NULL) {
printf("Error saving data: can't open %s\n", path_tmp);
free(buf);
return -1;
}
if (fwrite(buf, 1, len, fp) != len) {
printf("Error writing data to %s\n", path_tmp);
free(buf);
fclose(fp);
return -1;
}
free(buf);
if (fclose(fp) != 0) {
printf("Error writing data to %s\n", path_tmp);
return -1;
}
if (rename(path_tmp, path) != 0) {
printf("Error renaming %s to %s\n", path_tmp, path);
return -1;
}
printf("Bootstrap data saved to %s\n", path);
return 0; /* Done */
}
int main(int argc, char *argv[])
{
int ret;
if (argc != 2) {
printf("usage: %s <data dir>\n", argv[0]);
return 1;
}
char *base_dir = argv[1];
if (create_avatar_diretory(base_dir) != 0)
printf("Error creating avatar directory.\n");
Tox *tox = tox_new(NULL);
uint32_t len;
void *data = load_bootstrap_data(base_dir, &len);
if (data == NULL)
return 1;
ret = tox_load(tox, data, len);
free(data);
if (ret == 0) {
printf("Tox initialized\n");
} else {
printf("Fatal: tox_load returned %d\n", ret);
return 1;
}
tox_callback_connection_status(tox, friend_status_cb, NULL);
tox_callback_friend_message(tox, friend_msg_cb, base_dir);
tox_callback_friend_request(tox, friend_request_cb, NULL);
tox_callback_avatar_info(tox, friend_avatar_info_cb, base_dir);
tox_callback_avatar_data(tox, friend_avatar_data_cb, base_dir);
uint8_t addr[TOX_FRIEND_ADDRESS_SIZE];
char addr_str[2 * TOX_FRIEND_ADDRESS_SIZE + 1];
tox_get_address(tox, addr);
byte_to_hex_str(addr, TOX_FRIEND_ADDRESS_SIZE, addr_str);
printf("Using local tox address: %s\n", addr_str);
#ifdef TEST_SET_RESET_AVATAR
printf("Printing default avatar information:\n");
print_avatar_info(tox);
printf("Setting a new avatar:\n");
set_avatar(tox, base_dir);
print_avatar_info(tox);
printf("Removing the avatar we just set:\n");
tox_avatar(tox, TOX_AVATARFORMAT_NONE, NULL, 0);
print_avatar_info(tox);
printf("Setting that avatar again:\n");
#endif /* TEST_SET_RESET_AVATAR */
set_avatar(tox, base_dir);
print_avatar_info(tox);
bool waiting = true;
time_t last_save = time(0);
while (1) {
if (tox_isconnected(tox) && waiting) {
printf("DHT connected.\n");
waiting = false;
}
tox_do(tox);
time_t now = time(0);
if (now - last_save > 120) {
save_bootstrap_data(tox, base_dir);
last_save = now;
}
usleep(500000);
}
return 0;
}

View File

@ -42,6 +42,7 @@
static void set_friend_status(Messenger *m, int32_t friendnumber, uint8_t status);
static int write_cryptpacket_id(const Messenger *m, int32_t friendnumber, uint8_t packet_id, const uint8_t *data,
uint32_t length, uint8_t congestion_control);
static int send_avatar_data_control(const Messenger *m, const uint32_t friendnumber, uint8_t op);
// friend_not_valid determines if the friendnumber passed is valid in the Messenger object
static uint8_t friend_not_valid(const Messenger *m, int32_t friendnumber)
@ -247,6 +248,10 @@ int32_t m_addfriend(Messenger *m, const uint8_t *address, const uint8_t *data, u
m->friendlist[i].statusmessage = calloc(1, 1);
m->friendlist[i].statusmessage_length = 1;
m->friendlist[i].userstatus = USERSTATUS_NONE;
m->friendlist[i].avatar_info_sent = 0;
m->friendlist[i].avatar_recv_data = NULL;
m->friendlist[i].avatar_send_data.bytes_sent = 0;
m->friendlist[i].avatar_send_data.last_reset = 0;
m->friendlist[i].is_typing = 0;
memcpy(m->friendlist[i].info, data, length);
m->friendlist[i].info_size = length;
@ -330,6 +335,7 @@ int m_delfriend(Messenger *m, int32_t friendnumber)
onion_delfriend(m->onion_c, m->friendlist[friendnumber].onion_friendnum);
crypto_kill(m->net_crypto, m->friendlist[friendnumber].crypt_connection_id);
free(m->friendlist[friendnumber].statusmessage);
free(m->friendlist[friendnumber].avatar_recv_data);
remove_request_received(&(m->fr), m->friendlist[friendnumber].client_id);
memset(&(m->friendlist[friendnumber]), 0, sizeof(Friend));
uint32_t i;
@ -564,6 +570,134 @@ int m_set_userstatus(Messenger *m, uint8_t status)
return 0;
}
int m_set_avatar(Messenger *m, uint8_t format, const uint8_t *data, uint32_t length)
{
if (length > MAX_AVATAR_DATA_LENGTH)
return -1;
if (format == AVATARFORMAT_NONE) {
free(m->avatar_data);
m->avatar_data = NULL;
m->avatar_data_length = 0;
m->avatar_format = format;
memset(m->avatar_hash, 0, AVATAR_HASH_LENGTH);
} else {
if (length == 0 || data == NULL)
return -1;
uint8_t *tmp = realloc(m->avatar_data, length);
if (tmp == NULL)
return -1;
m->avatar_format = format;
m->avatar_data = tmp;
m->avatar_data_length = length;
memcpy(m->avatar_data, data, length);
m_avatar_hash(m->avatar_hash, m->avatar_data, m->avatar_data_length);
}
uint32_t i;
for (i = 0; i < m->numfriends; ++i)
m->friendlist[i].avatar_info_sent = 0;
return 0;
}
int m_get_self_avatar(const Messenger *m, uint8_t *format, uint8_t *buf, uint32_t *length, uint32_t maxlen,
uint8_t *hash)
{
if (format)
*format = m->avatar_format;
if (length)
*length = m->avatar_data_length;
if (hash)
memcpy(hash, m->avatar_hash, AVATAR_HASH_LENGTH);
if (buf != NULL && maxlen > 0) {
if (m->avatar_data_length <= maxlen)
memcpy(buf, m->avatar_data, m->avatar_data_length);
else
return -1;
}
return 0;
}
int m_avatar_hash(uint8_t *hash, const uint8_t *data, const uint32_t datalen)
{
if (hash == NULL)
return -1;
crypto_hash_sha256(hash, data, datalen);
return 0;
}
int m_request_avatar_info(const Messenger *m, const int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return -1;
if (write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_INFO_REQ, 0, 0, 0) >= 0)
return 0;
else
return -1;
}
int m_send_avatar_info(const Messenger *m, const int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return -1;
uint8_t data[sizeof(uint8_t) + AVATAR_HASH_LENGTH];
data[0] = m->avatar_format;
memcpy(data + 1, m->avatar_hash, AVATAR_HASH_LENGTH);
int ret = write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_INFO, data, sizeof(data), 0);
if (ret >= 0)
return 0;
else
return -1;
}
int m_request_avatar_data(const Messenger *m, const int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return -1;
AVATARRECEIVEDATA *avrd = m->friendlist[friendnumber].avatar_recv_data;
if (avrd == NULL) {
avrd = malloc(sizeof(AVATARRECEIVEDATA));
if (avrd == NULL)
return -1;
memset(avrd, 0, sizeof(AVATARRECEIVEDATA));
avrd->started = 0;
m->friendlist[friendnumber].avatar_recv_data = avrd;
}
if (avrd->started) {
LOGGER_DEBUG("Resetting already started data request. "
"friendnumber == %u", friendnumber);
}
avrd->started = 0;
avrd->bytes_received = 0;
avrd->total_length = 0;
avrd->format = AVATARFORMAT_NONE;
return send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_REQ);
}
/* return the size of friendnumber's user status.
* Guaranteed to be at most MAX_STATUSMESSAGE_LENGTH.
*/
@ -806,6 +940,20 @@ void m_callback_connectionstatus_internal_av(Messenger *m, void (*function)(Mess
m->friend_connectionstatuschange_internal_userdata = userdata;
}
void m_callback_avatar_info(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint8_t *, void *),
void *userdata)
{
m->avatar_info_recv = function;
m->avatar_info_recv_userdata = userdata;
}
void m_callback_avatar_data(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint8_t *, uint8_t *,
uint32_t, void *), void *userdata)
{
m->avatar_data_recv = function;
m->avatar_data_recv_userdata = userdata;
}
static void break_files(const Messenger *m, int32_t friendnumber);
static void check_friend_connectionstatus(Messenger *m, int32_t friendnumber, uint8_t status)
{
@ -1857,6 +2005,9 @@ Messenger *new_messenger(Messenger_Options *options)
m->net = new_networking(ip, TOX_PORT_DEFAULT);
}
m->avatar_format = AVATARFORMAT_NONE;
m->avatar_data = NULL;
if (m->net == NULL) {
free(m);
return NULL;
@ -1934,6 +2085,7 @@ void kill_messenger(Messenger *m)
free(m->friendlist[i].statusmessage);
}
free(m->avatar_data);
free(m->friendlist);
free(m);
}
@ -1973,11 +2125,287 @@ static int handle_status(void *object, int i, uint8_t status)
if (m->friendlist[i].status == FRIEND_ONLINE) {
set_friend_status(m, i, FRIEND_CONFIRMED);
}
/* Clear avatar transfer state */
if (m->friendlist[i].avatar_recv_data) {
free(m->friendlist[i].avatar_recv_data);
m->friendlist[i].avatar_recv_data = NULL;
}
}
return 0;
}
/* Sends an avatar data control packet to the peer. Usually to return status
* values or request data.
*/
static int send_avatar_data_control(const Messenger *m, const uint32_t friendnumber,
uint8_t op)
{
int ret = write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_DATA_CONTROL,
&op, sizeof(op), 0);
LOGGER_DEBUG("friendnumber = %u, op = %u, ret = %d",
friendnumber, op, ret);
return (ret >= 0) ? 0 : -1;
}
static int handle_avatar_data_control(Messenger *m, uint32_t friendnumber,
uint8_t *data, uint32_t data_length)
{
if (data_length != 1) {
LOGGER_DEBUG("Error: PACKET_ID_AVATAR_DATA_CONTROL with bad "
"data_length = %u, friendnumber = %u",
data_length, friendnumber);
send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR);
return -1; /* Error */
}
LOGGER_DEBUG("friendnumber = %u, op = %u", friendnumber, data[0]);
switch (data[0]) {
case AVATARDATACONTROL_REQ: {
/* Check data transfer limits for this friend */
AVATARSENDDATA *const avsd = &(m->friendlist[friendnumber].avatar_send_data);
if (avsd->bytes_sent >= AVATAR_DATA_TRANSFER_LIMIT) {
/* User reached data limit. Check timeout */
uint64_t now = unix_time();
if (avsd->last_reset > 0
&& (avsd->last_reset + AVATAR_DATA_TRANSFER_TIMEOUT < now)) {
avsd->bytes_sent = 0;
avsd->last_reset = now;
} else {
/* Friend still rate-limitted. Send an error and stops. */
LOGGER_DEBUG("Avatar data transfer limit reached. "
"friendnumber = %u", friendnumber);
send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR);
return 0;
}
}
/* Start the transmission with a DATA_START message. Format:
* uint8_t format
* uint8_t hash[AVATAR_HASH_LENGTH]
* uint32_t total_length
*/
LOGGER_DEBUG("Sending start msg to friend number %u. "
"m->avatar_format = %u, m->avatar_data_length = %u",
friendnumber, m->avatar_format, m->avatar_data_length);
uint8_t start_data[1 + AVATAR_HASH_LENGTH + sizeof(uint32_t)];
uint32_t avatar_len = htonl(m->avatar_data_length);
start_data[0] = m->avatar_format;
memcpy(start_data + 1, m->avatar_hash, AVATAR_HASH_LENGTH);
memcpy(start_data + 1 + AVATAR_HASH_LENGTH, &avatar_len, sizeof(uint32_t));
avsd->bytes_sent += sizeof(start_data); /* For rate limit */
int ret = write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_DATA_START,
start_data, sizeof(start_data), 0);
if (ret < 0) {
/* Something went wrong, try to signal the error so the friend
* can clear up the state. */
send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR);
return 0;
}
/* User have no avatar data, nothing more to do. */
if (m->avatar_format == AVATARFORMAT_NONE)
return 0;
/* Send the actual avatar data. */
uint32_t offset = 0;
while (offset < m->avatar_data_length) {
uint32_t chunk_len = m->avatar_data_length - offset;
if (chunk_len > AVATAR_DATA_MAX_CHUNK_SIZE)
chunk_len = AVATAR_DATA_MAX_CHUNK_SIZE;
uint8_t chunk[AVATAR_DATA_MAX_CHUNK_SIZE];
memcpy(chunk, m->avatar_data + offset, chunk_len);
offset += chunk_len;
avsd->bytes_sent += chunk_len; /* For rate limit */
int ret = write_cryptpacket_id(m, friendnumber,
PACKET_ID_AVATAR_DATA_PUSH,
chunk, chunk_len, 0);
if (ret < 0) {
LOGGER_DEBUG("write_cryptpacket_id failed. ret = %d, "
"friendnumber = %u, offset = %u",
ret, friendnumber, offset);
send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR);
return -1;
}
}
return 0;
}
case AVATARDATACONTROL_ERROR: {
if (m->friendlist[friendnumber].avatar_recv_data) {
/* We were receiving the data, sender detected an error
(eg. changing avatar) and asked us to stop. */
free(m->friendlist[friendnumber].avatar_recv_data);
m->friendlist[friendnumber].avatar_recv_data = NULL;
}
return 0;
}
}
return -1;
}
static int handle_avatar_data_start(Messenger *m, uint32_t friendnumber,
uint8_t *data, uint32_t data_length)
{
LOGGER_DEBUG("data_length = %u, friendnumber = %u", data_length, friendnumber);
if (data_length != 1 + AVATAR_HASH_LENGTH + sizeof(uint32_t)) {
LOGGER_DEBUG("Invalid msg length = %u, friendnumber = %u",
data_length, friendnumber);
return -1;
}
AVATARRECEIVEDATA *avrd = m->friendlist[friendnumber].avatar_recv_data;
if (avrd == NULL) {
LOGGER_DEBUG("Received an unrequested DATA_START, friendnumber = %u",
friendnumber);
return -1;
}
if (avrd->started) {
/* Already receiving data from this friend. Must be an error
* or an malicious request, because we zeroed the started bit
* when we requested the data. */
LOGGER_DEBUG("Received an unrequested duplicated DATA_START, "
"friendnumber = %u", friendnumber);
return -1;
}
/* Copy data from message to our control structure */
avrd->started = 1;
avrd->format = data[0];
memcpy(avrd->hash, data + 1, AVATAR_HASH_LENGTH);
uint32_t tmp_len;
memcpy(&tmp_len, data + 1 + AVATAR_HASH_LENGTH, sizeof(uint32_t));
avrd->total_length = ntohl(tmp_len);
avrd->bytes_received = 0;
LOGGER_DEBUG("friendnumber = %u, avrd->format = %u, "
"avrd->total_length = %u, avrd->bytes_received = %u",
friendnumber, avrd->format, avrd->total_length,
avrd->bytes_received);
if (avrd->total_length > MAX_AVATAR_DATA_LENGTH) {
/* Invalid data length. Stops. */
LOGGER_DEBUG("Error: total_length > MAX_AVATAR_DATA_LENGTH, "
"friendnumber = %u", friendnumber);
free(avrd);
avrd = NULL;
m->friendlist[friendnumber].avatar_recv_data = NULL;
return 0;
}
if (avrd->format == AVATARFORMAT_NONE || avrd->total_length == 0) {
/* No real data to receive. Run callback function and finish. */
LOGGER_DEBUG("format == NONE, friendnumber = %u", friendnumber);
if (m->avatar_data_recv) {
memset(avrd->hash, 0, AVATAR_HASH_LENGTH);
(m->avatar_data_recv)(m, friendnumber, avrd->format, avrd->hash,
NULL, 0, m->avatar_data_recv_userdata);
}
free(avrd);
avrd = NULL;
m->friendlist[friendnumber].avatar_recv_data = NULL;
return 0;
}
/* Waits for more data to be received */
return 0;
}
static int handle_avatar_data_push(Messenger *m, uint32_t friendnumber,
uint8_t *data, uint32_t data_length)
{
LOGGER_DEBUG("friendnumber = %u, data_length = %u", friendnumber, data_length);
AVATARRECEIVEDATA *avrd = m->friendlist[friendnumber].avatar_recv_data;
if (avrd == NULL) {
/* No active transfer. It must be an error or a malicious request,
* because we set the avatar_recv_data on the first DATA_START. */
LOGGER_DEBUG("Error: avrd == NULL, friendnumber = %u", friendnumber);
return -1; /* Error */
}
if (avrd->started == 0) {
/* Receiving data for a non-started request. Must be an error
* or an malicious request. */
LOGGER_DEBUG("Received an data push for a yet non started data "
"request. friendnumber = %u", friendnumber);
return -1; /* Error */
}
uint32_t new_length = avrd->bytes_received + data_length;
if (new_length > avrd->total_length
|| new_length >= MAX_AVATAR_DATA_LENGTH) {
/* Invalid data length due to error or malice. Stops. */
LOGGER_DEBUG("Invalid data length. friendnumber = %u, "
"new_length = %u, avrd->total_length = %u",
friendnumber, new_length, avrd->total_length);
free(avrd);
m->friendlist[friendnumber].avatar_recv_data = NULL;
return 0;
}
memcpy(avrd->data + avrd->bytes_received, data, data_length);
avrd->bytes_received += data_length;
if (avrd->bytes_received == avrd->total_length) {
LOGGER_DEBUG("All data received. friendnumber = %u", friendnumber);
/* All data was received. Check if the hashes match. It the
* requester's responsability to do this. The sender may have done
* anything with its avatar data between the DATA_START and now.
*/
uint8_t cur_hash[AVATAR_HASH_LENGTH];
m_avatar_hash(cur_hash, avrd->data, avrd->bytes_received);
if (memcmp(cur_hash, avrd->hash, AVATAR_HASH_LENGTH) == 0) {
/* Avatar successfuly received! */
if (m->avatar_data_recv) {
(m->avatar_data_recv)(m, friendnumber, avrd->format, cur_hash,
avrd->data, avrd->bytes_received, m->avatar_data_recv_userdata);
}
} else {
LOGGER_DEBUG("Avatar hash error. friendnumber = %u", friendnumber);
}
free(avrd);
m->friendlist[friendnumber].avatar_recv_data = NULL;
return 0;
}
/* Waits for more data to be received */
return 0;
}
static int handle_packet(void *object, int i, uint8_t *temp, uint16_t len)
{
if (len == 0)
@ -2115,6 +2543,42 @@ static int handle_packet(void *object, int i, uint8_t *temp, uint16_t len)
break;
}
case PACKET_ID_AVATAR_INFO_REQ: {
/* Send our avatar information */
m_send_avatar_info(m, i);
break;
}
case PACKET_ID_AVATAR_INFO: {
if (m->avatar_info_recv) {
/*
* A malicious user may send an incomplete avatar info message.
* Check if it have the correct size for the format:
* [1 uint8_t: avatar format] [32 uint8_t: hash]
*/
if (data_length == AVATAR_HASH_LENGTH + 1) {
(m->avatar_info_recv)(m, i, data[0], data + 1, m->avatar_info_recv_userdata);
}
}
break;
}
case PACKET_ID_AVATAR_DATA_CONTROL: {
handle_avatar_data_control(m, i, data, data_length);
break;
}
case PACKET_ID_AVATAR_DATA_START: {
handle_avatar_data_start(m, i, data, data_length);
break;
}
case PACKET_ID_AVATAR_DATA_PUSH: {
handle_avatar_data_push(m, i, data, data_length);
break;
}
case PACKET_ID_RECEIPT: {
uint32_t msgid;
@ -2343,6 +2807,11 @@ void do_friends(Messenger *m)
m->friendlist[i].userstatus_sent = 1;
}
if (m->friendlist[i].avatar_info_sent == 0) {
if (m_send_avatar_info(m, i) == 0)
m->friendlist[i].avatar_info_sent = 1;
}
if (m->friendlist[i].user_istyping_sent == 0) {
if (send_user_istyping(m, i, m->friendlist[i].user_istyping))
m->friendlist[i].user_istyping_sent = 1;

View File

@ -36,6 +36,9 @@
#define MAX_NAME_LENGTH 128
/* TODO: this must depend on other variable. */
#define MAX_STATUSMESSAGE_LENGTH 1007
#define MAX_AVATAR_DATA_LENGTH 16384
#define AVATAR_HASH_LENGTH 32
#define FRIEND_ADDRESS_SIZE (crypto_box_PUBLICKEYBYTES + sizeof(uint32_t) + sizeof(uint16_t))
@ -46,6 +49,11 @@
#define PACKET_ID_STATUSMESSAGE 49
#define PACKET_ID_USERSTATUS 50
#define PACKET_ID_TYPING 51
#define PACKET_ID_AVATAR_INFO_REQ 52
#define PACKET_ID_AVATAR_INFO 53
#define PACKET_ID_AVATAR_DATA_CONTROL 54
#define PACKET_ID_AVATAR_DATA_START 55
#define PACKET_ID_AVATAR_DATA_PUSH 56
#define PACKET_ID_RECEIPT 63
#define PACKET_ID_MESSAGE 64
#define PACKET_ID_ACTION 65
@ -109,6 +117,13 @@ enum {
/* If no packets are received from friend in this time interval, kill the connection. */
#define FRIEND_CONNECTION_TIMEOUT (FRIEND_PING_INTERVAL * 3)
/* Must be < MAX_CRYPTO_DATA_SIZE */
#define AVATAR_DATA_MAX_CHUNK_SIZE (MAX_CRYPTO_DATA_SIZE-1)
/* Per-friend data limit for avatar data requests */
#define AVATAR_DATA_TRANSFER_LIMIT (10*MAX_AVATAR_DATA_LENGTH)
#define AVATAR_DATA_TRANSFER_TIMEOUT (20*60)
/* USERSTATUS -
* Represents userstatuses someone can have.
@ -122,6 +137,42 @@ typedef enum {
}
USERSTATUS;
/* AVATARFORMAT -
* Data formats for user avatar images
*/
typedef enum {
AVATARFORMAT_NONE,
AVATARFORMAT_PNG
}
AVATARFORMAT;
/* AVATARDATACONTROL
* To control avatar data requests (PACKET_ID_AVATAR_DATA_CONTROL)
*/
typedef enum {
AVATARDATACONTROL_REQ,
AVATARDATACONTROL_ERROR
}
AVATARDATACONTROL;
typedef struct {
uint8_t started;
AVATARFORMAT format;
uint8_t hash[AVATAR_HASH_LENGTH];
uint32_t total_length;
uint32_t bytes_received;
uint8_t data[MAX_AVATAR_DATA_LENGTH];
}
AVATARRECEIVEDATA;
typedef struct {
/* Fields only used to limit the network usage from a given friend */
uint32_t bytes_sent; /* Total bytes send to this user */
uint64_t last_reset; /* Time the data counter was last reset */
}
AVATARSENDDATA;
struct File_Transfers {
uint64_t size;
uint64_t transferred;
@ -163,6 +214,7 @@ typedef struct {
uint8_t statusmessage_sent;
USERSTATUS userstatus;
uint8_t userstatus_sent;
uint8_t avatar_info_sent;
uint8_t user_istyping;
uint8_t user_istyping_sent;
uint8_t is_typing;
@ -178,6 +230,9 @@ typedef struct {
int invited_groups[MAX_INVITED_GROUPS];
uint16_t invited_groups_num;
AVATARSENDDATA avatar_send_data;
AVATARRECEIVEDATA *avatar_recv_data; // We are receiving avatar data from this friend.
struct {
int (*function)(void *object, const uint8_t *data, uint32_t len);
void *object;
@ -209,6 +264,11 @@ typedef struct Messenger {
USERSTATUS userstatus;
AVATARFORMAT avatar_format;
uint8_t *avatar_data;
uint32_t avatar_data_length;
uint8_t avatar_hash[AVATAR_HASH_LENGTH];
Friend *friendlist;
uint32_t numfriends;
@ -243,6 +303,10 @@ typedef struct Messenger {
void *friend_connectionstatuschange_userdata;
void (*friend_connectionstatuschange_internal)(struct Messenger *m, int32_t, uint8_t, void *);
void *friend_connectionstatuschange_internal_userdata;
void *avatar_info_recv_userdata;
void (*avatar_info_recv)(struct Messenger *m, int32_t, uint8_t, uint8_t *, void *);
void *avatar_data_recv_userdata;
void (*avatar_data_recv)(struct Messenger *m, int32_t, uint8_t, uint8_t *, uint8_t *, uint32_t, void *);
void (*group_invite)(struct Messenger *m, int32_t, const uint8_t *, void *);
void *group_invite_userdata;
@ -437,6 +501,94 @@ int m_copy_self_statusmessage(const Messenger *m, uint8_t *buf, uint32_t maxlen)
uint8_t m_get_userstatus(const Messenger *m, int32_t friendnumber);
uint8_t m_get_self_userstatus(const Messenger *m);
/* Set the user avatar image data.
* This should be made before connecting, so we will not announce that the user have no avatar
* before setting and announcing a new one, forcing the peers to re-download it.
*
* Notice that the library treats the image as raw data and does not interpret it by any way.
*
* Arguments:
* format - Avatar image format or NONE for user with no avatar (see AVATARFORMAT);
* data - pointer to the avatar data (may be NULL it the format is NONE);
* length - length of image data. Must be <= MAX_AVATAR_DATA_LENGTH.
*
* returns 0 on success
* returns -1 on failure.
*/
int m_set_avatar(Messenger *m, uint8_t format, const uint8_t *data, uint32_t length);
/* Get avatar data from the current user.
* Copies the current user avatar data to the destination buffer and sets the image format
* accordingly.
*
* If the avatar format is NONE, the buffer 'buf' isleft uninitialized, 'hash' is zeroed, and
* 'length' is set to zero.
*
* If any of the pointers format, buf, length, and hash are NULL, that particular field will be ignored.
*
* Arguments:
* format - destination pointer to the avatar image format (see AVATARFORMAT);
* buf - destination buffer to the image data. Must have at least 'maxlen' bytes;
* length - destination pointer to the image data length;
* maxlen - length of the destination buffer 'buf';
* hash - destination pointer to the avatar hash (it must be exactly AVATAR_HASH_LENGTH bytes long).
*
* returns 0 on success;
* returns -1 on failure.
*
*/
int m_get_self_avatar(const Messenger *m, uint8_t *format, uint8_t *buf, uint32_t *length, uint32_t maxlen,
uint8_t *hash);
/* Generates a cryptographic hash of the given avatar data.
* This function is a wrapper to internal message-digest functions and specifically provided
* to generate hashes from user avatars that may be memcmp()ed with the values returned by the
* other avatar functions. It is specially important to validate cached avatars.
*
* Arguments:
* hash - destination buffer for the hash data, it must be exactly AVATAR_HASH_LENGTH bytes long.
* data - avatar image data;
* datalen - length of the avatar image data; it must be <= MAX_AVATAR_DATA_LENGTH.
*
* returns 0 on success
* returns -1 on failure.
*/
int m_avatar_hash(uint8_t *hash, const uint8_t *data, const uint32_t datalen);
/* Request avatar information from a friend.
* Asks a friend to provide their avatar information (image format and hash). The friend may
* or may not answer this request and, if answered, the information will be provided through
* the callback 'avatar_info'.
*
* returns 0 on success
* returns -1 on failure.
*/
int m_request_avatar_info(const Messenger *m, const int32_t friendnumber);
/* Send an unrequested avatar information to a friend.
* Sends our avatar format and hash to a friend; he/she can use this information to validate
* an avatar from the cache and may (or not) reply with an avatar data request.
*
* Notice: it is NOT necessary to send these notification after changing the avatar or
* connecting. The library already does this.
*
* returns 0 on success
* returns -1 on failure.
*/
int m_send_avatar_info(const Messenger *m, const int32_t friendnumber);
/* Request the avatar data from a friend.
* Ask a friend to send their avatar data. The friend may or may not answer this request and,
* if answered, the information will be provided in callback 'avatar_data'.
*
* returns 0 on sucess
* returns -1 on failure.
*/
int m_request_avatar_data(const Messenger *m, const int32_t friendnumber);
/* returns timestamp of last time friendnumber was seen online, or 0 if never seen.
* returns -1 on error.
*/
@ -533,6 +685,49 @@ void m_callback_connectionstatus(Messenger *m, void (*function)(Messenger *m, in
void m_callback_connectionstatus_internal_av(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, void *),
void *userdata);
/* Set the callback function for avatar information.
* This callback will be called when avatar information are received from friends. These events
* can arrive at anytime, but are usually received uppon connection and in reply of avatar
* information requests.
*
* Function format is:
* function(Tox *tox, int32_t friendnumber, uint8_t format, uint8_t *hash, void *userdata)
*
* where 'format' is the avatar image format (see AVATARFORMAT) and 'hash' is the hash of
* the avatar data for caching purposes and it is exactly AVATAR_HASH_LENGTH long. If the
* image format is NONE, the hash is zeroed.
*
*/
void m_callback_avatar_info(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint8_t *, void *),
void *userdata);
/* Set the callback function for avatar data.
* This callback will be called when the complete avatar data was correctly received from a
* friend. This only happens in reply of a avatar data request (see tox_request_avatar_data);
*
* Function format is:
* function(Tox *tox, int32_t friendnumber, uint8_t format, uint8_t *hash, uint8_t *data, uint32_t datalen, void *userdata)
*
* where 'format' is the avatar image format (see AVATARFORMAT); 'hash' is the
* locally-calculated cryptographic hash of the avatar data and it is exactly
* AVATAR_HASH_LENGTH long; 'data' is the avatar image data and 'datalen' is the length
* of such data.
*
* If format is NONE, 'data' is NULL, 'datalen' is zero, and the hash is zeroed. The hash is
* always validated locally with the function tox_avatar_hash and ensured to match the image
* data, so this value can be safely used to compare with cached avatars.
*
* WARNING: users MUST treat all avatar image data received from another peer as untrusted and
* potentially malicious. The library only ensures that the data which arrived is the same the
* other user sent, and does not interpret or validate any image data.
*/
void m_callback_avatar_data(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint8_t *, uint8_t *,
uint32_t, void *), void *userdata);
/**********GROUP CHATS************/
/* Set the callback for group invites.

View File

@ -275,6 +275,42 @@ uint8_t tox_get_self_user_status(const Tox *tox)
return m_get_self_userstatus(m);
}
int tox_set_avatar(Tox *tox, uint8_t format, const uint8_t *data, uint32_t length)
{
Messenger *m = tox;
return m_set_avatar(m, format, data, length);
}
int tox_get_self_avatar(const Tox *tox, uint8_t *format, uint8_t *buf, uint32_t *length, uint32_t maxlen, uint8_t *hash)
{
const Messenger *m = tox;
return m_get_self_avatar(m, format, buf, length, maxlen, hash);
}
int tox_avatar_hash(const Tox *tox, uint8_t *hash, const uint8_t *data, const uint32_t datalen)
{
return m_avatar_hash(hash, data, datalen);
}
int tox_request_avatar_info(const Tox *tox, const int32_t friendnumber)
{
const Messenger *m = tox;
return m_request_avatar_info(m, friendnumber);
}
int tox_send_avatar_info(Tox *tox, const int32_t friendnumber)
{
const Messenger *m = tox;
return m_send_avatar_info(m, friendnumber);
}
int tox_request_avatar_data(const Tox *tox, const int32_t friendnumber)
{
const Messenger *m = tox;
return m_request_avatar_data(m, friendnumber);
}
/* returns timestamp of last time friendnumber was seen online, or 0 if never seen.
* returns -1 on error.
*/
@ -439,6 +475,24 @@ void tox_callback_connection_status(Tox *tox, void (*function)(Messenger *tox, i
m_callback_connectionstatus(m, function, userdata);
}
void tox_callback_avatar_info(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t *, void *), void *userdata)
{
Messenger *m = tox;
m_callback_avatar_info(m, function, userdata);
}
void tox_callback_avatar_data(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t *, uint8_t *, uint32_t,
void *), void *userdata)
{
Messenger *m = tox;
m_callback_avatar_data(m, function, userdata);
}
/**********ADVANCED FUNCTIONS (If you don't know what they do you can safely ignore them.) ************/
/* Functions to get/set the nospam part of the id.

View File

@ -37,6 +37,8 @@ extern "C" {
#define TOX_MAX_MESSAGE_LENGTH 1368
#define TOX_MAX_STATUSMESSAGE_LENGTH 1007
#define TOX_CLIENT_ID_SIZE 32
#define TOX_MAX_AVATAR_DATA_LENGTH 16384
#define TOX_AVATAR_HASH_LENGTH 32
#define TOX_FRIEND_ADDRESS_SIZE (TOX_CLIENT_ID_SIZE + sizeof(uint32_t) + sizeof(uint16_t))
@ -70,6 +72,16 @@ typedef enum {
}
TOX_USERSTATUS;
/* AVATARFORMAT -
* Data formats for user avatar images
*/
typedef enum {
TOX_AVATARFORMAT_NONE,
TOX_AVATARFORMAT_PNG
}
TOX_AVATARFORMAT;
#ifndef __TOX_DEFINED__
#define __TOX_DEFINED__
typedef struct Tox Tox;
@ -243,6 +255,97 @@ uint8_t tox_get_user_status(const Tox *tox, int32_t friendnumber);
uint8_t tox_get_self_user_status(const Tox *tox);
/* Set the user avatar image data.
* This should be made before connecting, so we will not announce that the user have no avatar
* before setting and announcing a new one, forcing the peers to re-download it.
*
* Notice that the library treats the image as raw data and does not interpret it by any way.
*
* Arguments:
* format - Avatar image format or NONE for user with no avatar (see TOX_AVATARFORMAT);
* data - pointer to the avatar data (may be NULL it the format is NONE);
* length - length of image data. Must be <= TOX_MAX_AVATAR_DATA_LENGTH.
*
* returns 0 on success
* returns -1 on failure.
*/
int tox_set_avatar(Tox *tox, uint8_t format, const uint8_t *data, uint32_t length);
/* Get avatar data from the current user.
* Copies the current user avatar data to the destination buffer and sets the image format
* accordingly.
*
* If the avatar format is NONE, the buffer 'buf' isleft uninitialized, 'hash' is zeroed, and
* 'length' is set to zero.
*
* If any of the pointers format, buf, length, and hash are NULL, that particular field will be ignored.
*
* Arguments:
* format - destination pointer to the avatar image format (see TOX_AVATARFORMAT);
* buf - destination buffer to the image data. Must have at least 'maxlen' bytes;
* length - destination pointer to the image data length;
* maxlen - length of the destination buffer 'buf';
* hash - destination pointer to the avatar hash (it must be exactly TOX_AVATAR_HASH_LENGTH bytes long).
*
* returns 0 on success;
* returns -1 on failure.
*
*/
int tox_get_self_avatar(const Tox *tox, uint8_t *format, uint8_t *buf, uint32_t *length, uint32_t maxlen,
uint8_t *hash);
/* Generates a cryptographic hash of the given avatar data.
* This function is a wrapper to internal message-digest functions and specifically provided
* to generate hashes from user avatars that may be memcmp()ed with the values returned by the
* other avatar functions. It is specially important to validate cached avatars.
*
* Arguments:
* hash - destination buffer for the hash data, it must be exactly TOX_AVATAR_HASH_LENGTH bytes long.
* data - avatar image data;
* datalen - length of the avatar image data; it must be <= TOX_MAX_AVATAR_DATA_LENGTH.
*
* returns 0 on success
* returns -1 on failure.
*/
int tox_avatar_hash(const Tox *tox, uint8_t *hash, const uint8_t *data, const uint32_t datalen);
/* Request avatar information from a friend.
* Asks a friend to provide their avatar information (image format and hash). The friend may
* or may not answer this request and, if answered, the information will be provided through
* the callback 'avatar_info'.
*
* returns 0 on success
* returns -1 on failure.
*/
int tox_request_avatar_info(const Tox *tox, const int32_t friendnumber);
/* Send an unrequested avatar information to a friend.
* Sends our avatar format and hash to a friend; he/she can use this information to validate
* an avatar from the cache and may (or not) reply with an avatar data request.
*
* Notice: it is NOT necessary to send these notification after changing the avatar or
* connecting. The library already does this.
*
* returns 0 on success
* returns -1 on failure.
*/
int tox_send_avatar_info(Tox *tox, const int32_t friendnumber);
/* Request the avatar data from a friend.
* Ask a friend to send their avatar data. The friend may or may not answer this request and,
* if answered, the information will be provided in callback 'avatar_data'.
*
* returns 0 on sucess
* returns -1 on failure.
*/
int tox_request_avatar_data(const Tox *tox, const int32_t friendnumber);
/* returns timestamp of last time friendnumber was seen online, or 0 if never seen.
* returns -1 on error.
*/
@ -344,6 +447,48 @@ void tox_callback_read_receipt(Tox *tox, void (*function)(Tox *tox, int32_t, uin
*/
void tox_callback_connection_status(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, void *), void *userdata);
/* Set the callback function for avatar information.
* This callback will be called when avatar information are received from friends. These events
* can arrive at anytime, but are usually received uppon connection and in reply of avatar
* information requests.
*
* Function format is:
* function(Tox *tox, int32_t friendnumber, uint8_t format, uint8_t *hash, void *userdata)
*
* where 'format' is the avatar image format (see TOX_AVATARFORMAT) and 'hash' is the hash of
* the avatar data for caching purposes and it is exactly TOX_AVATAR_HASH_LENGTH long. If the
* image format is NONE, the hash is zeroed.
*
*/
void tox_callback_avatar_info(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t *, void *),
void *userdata);
/* Set the callback function for avatar data.
* This callback will be called when the complete avatar data was correctly received from a
* friend. This only happens in reply of a avatar data request (see tox_request_avatar_data);
*
* Function format is:
* function(Tox *tox, int32_t friendnumber, uint8_t format, uint8_t *hash, uint8_t *data, uint32_t datalen, void *userdata)
*
* where 'format' is the avatar image format (see TOX_AVATARFORMAT); 'hash' is the
* locally-calculated cryptographic hash of the avatar data and it is exactly
* TOX_AVATAR_HASH_LENGTH long; 'data' is the avatar image data and 'datalen' is the length
* of such data.
*
* If format is NONE, 'data' is NULL, 'datalen' is zero, and the hash is zeroed. The hash is
* always validated locally with the function tox_avatar_hash and ensured to match the image
* data, so this value can be safely used to compare with cached avatars.
*
* WARNING: users MUST treat all avatar image data received from another peer as untrusted and
* potentially malicious. The library only ensures that the data which arrived is the same the
* other user sent, and does not interpret or validate any image data.
*/
void tox_callback_avatar_data(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t *, uint8_t *, uint32_t,
void *), void *userdata);
/**********ADVANCED FUNCTIONS (If you don't know what they do you can safely ignore them.) ************/