mirror of
https://github.com/irungentoo/toxcore.git
synced 2024-03-22 13:30:51 +08:00
Merge branch 'avatars' of https://github.com/ittner/toxcore
This commit is contained in:
commit
b52da45aeb
611
docs/Avatars.md
Normal file
611
docs/Avatars.md
Normal 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.
|
@ -105,6 +105,21 @@ tox_shell_LDADD = $(LIBSODIUM_LDFLAGS) \
|
|||||||
$(NACL_LIBS) \
|
$(NACL_LIBS) \
|
||||||
-lutil
|
-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
|
endif
|
||||||
|
|
||||||
EXTRA_DIST += $(top_srcdir)/testing/misc_tools.c
|
EXTRA_DIST += $(top_srcdir)/testing/misc_tools.c
|
||||||
|
770
testing/test_avatars.c
Normal file
770
testing/test_avatars.c
Normal 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;
|
||||||
|
}
|
@ -42,6 +42,7 @@
|
|||||||
static void set_friend_status(Messenger *m, int32_t friendnumber, uint8_t status);
|
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,
|
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);
|
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
|
// 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)
|
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 = calloc(1, 1);
|
||||||
m->friendlist[i].statusmessage_length = 1;
|
m->friendlist[i].statusmessage_length = 1;
|
||||||
m->friendlist[i].userstatus = USERSTATUS_NONE;
|
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;
|
m->friendlist[i].is_typing = 0;
|
||||||
memcpy(m->friendlist[i].info, data, length);
|
memcpy(m->friendlist[i].info, data, length);
|
||||||
m->friendlist[i].info_size = 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);
|
onion_delfriend(m->onion_c, m->friendlist[friendnumber].onion_friendnum);
|
||||||
crypto_kill(m->net_crypto, m->friendlist[friendnumber].crypt_connection_id);
|
crypto_kill(m->net_crypto, m->friendlist[friendnumber].crypt_connection_id);
|
||||||
free(m->friendlist[friendnumber].statusmessage);
|
free(m->friendlist[friendnumber].statusmessage);
|
||||||
|
free(m->friendlist[friendnumber].avatar_recv_data);
|
||||||
remove_request_received(&(m->fr), m->friendlist[friendnumber].client_id);
|
remove_request_received(&(m->fr), m->friendlist[friendnumber].client_id);
|
||||||
memset(&(m->friendlist[friendnumber]), 0, sizeof(Friend));
|
memset(&(m->friendlist[friendnumber]), 0, sizeof(Friend));
|
||||||
uint32_t i;
|
uint32_t i;
|
||||||
@ -564,6 +570,134 @@ int m_set_userstatus(Messenger *m, uint8_t status)
|
|||||||
return 0;
|
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.
|
/* return the size of friendnumber's user status.
|
||||||
* Guaranteed to be at most MAX_STATUSMESSAGE_LENGTH.
|
* 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;
|
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 break_files(const Messenger *m, int32_t friendnumber);
|
||||||
static void check_friend_connectionstatus(Messenger *m, int32_t friendnumber, uint8_t status)
|
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->net = new_networking(ip, TOX_PORT_DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m->avatar_format = AVATARFORMAT_NONE;
|
||||||
|
m->avatar_data = NULL;
|
||||||
|
|
||||||
if (m->net == NULL) {
|
if (m->net == NULL) {
|
||||||
free(m);
|
free(m);
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -1934,6 +2085,7 @@ void kill_messenger(Messenger *m)
|
|||||||
free(m->friendlist[i].statusmessage);
|
free(m->friendlist[i].statusmessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(m->avatar_data);
|
||||||
free(m->friendlist);
|
free(m->friendlist);
|
||||||
free(m);
|
free(m);
|
||||||
}
|
}
|
||||||
@ -1973,11 +2125,287 @@ static int handle_status(void *object, int i, uint8_t status)
|
|||||||
if (m->friendlist[i].status == FRIEND_ONLINE) {
|
if (m->friendlist[i].status == FRIEND_ONLINE) {
|
||||||
set_friend_status(m, i, FRIEND_CONFIRMED);
|
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;
|
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)
|
static int handle_packet(void *object, int i, uint8_t *temp, uint16_t len)
|
||||||
{
|
{
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
@ -2115,6 +2543,42 @@ static int handle_packet(void *object, int i, uint8_t *temp, uint16_t len)
|
|||||||
break;
|
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: {
|
case PACKET_ID_RECEIPT: {
|
||||||
uint32_t msgid;
|
uint32_t msgid;
|
||||||
|
|
||||||
@ -2343,6 +2807,11 @@ void do_friends(Messenger *m)
|
|||||||
m->friendlist[i].userstatus_sent = 1;
|
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 (m->friendlist[i].user_istyping_sent == 0) {
|
||||||
if (send_user_istyping(m, i, m->friendlist[i].user_istyping))
|
if (send_user_istyping(m, i, m->friendlist[i].user_istyping))
|
||||||
m->friendlist[i].user_istyping_sent = 1;
|
m->friendlist[i].user_istyping_sent = 1;
|
||||||
|
@ -36,6 +36,9 @@
|
|||||||
#define MAX_NAME_LENGTH 128
|
#define MAX_NAME_LENGTH 128
|
||||||
/* TODO: this must depend on other variable. */
|
/* TODO: this must depend on other variable. */
|
||||||
#define MAX_STATUSMESSAGE_LENGTH 1007
|
#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))
|
#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_STATUSMESSAGE 49
|
||||||
#define PACKET_ID_USERSTATUS 50
|
#define PACKET_ID_USERSTATUS 50
|
||||||
#define PACKET_ID_TYPING 51
|
#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_RECEIPT 63
|
||||||
#define PACKET_ID_MESSAGE 64
|
#define PACKET_ID_MESSAGE 64
|
||||||
#define PACKET_ID_ACTION 65
|
#define PACKET_ID_ACTION 65
|
||||||
@ -109,6 +117,13 @@ enum {
|
|||||||
/* If no packets are received from friend in this time interval, kill the connection. */
|
/* If no packets are received from friend in this time interval, kill the connection. */
|
||||||
#define FRIEND_CONNECTION_TIMEOUT (FRIEND_PING_INTERVAL * 3)
|
#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 -
|
/* USERSTATUS -
|
||||||
* Represents userstatuses someone can have.
|
* Represents userstatuses someone can have.
|
||||||
@ -122,6 +137,42 @@ typedef enum {
|
|||||||
}
|
}
|
||||||
USERSTATUS;
|
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 {
|
struct File_Transfers {
|
||||||
uint64_t size;
|
uint64_t size;
|
||||||
uint64_t transferred;
|
uint64_t transferred;
|
||||||
@ -163,6 +214,7 @@ typedef struct {
|
|||||||
uint8_t statusmessage_sent;
|
uint8_t statusmessage_sent;
|
||||||
USERSTATUS userstatus;
|
USERSTATUS userstatus;
|
||||||
uint8_t userstatus_sent;
|
uint8_t userstatus_sent;
|
||||||
|
uint8_t avatar_info_sent;
|
||||||
uint8_t user_istyping;
|
uint8_t user_istyping;
|
||||||
uint8_t user_istyping_sent;
|
uint8_t user_istyping_sent;
|
||||||
uint8_t is_typing;
|
uint8_t is_typing;
|
||||||
@ -178,6 +230,9 @@ typedef struct {
|
|||||||
int invited_groups[MAX_INVITED_GROUPS];
|
int invited_groups[MAX_INVITED_GROUPS];
|
||||||
uint16_t invited_groups_num;
|
uint16_t invited_groups_num;
|
||||||
|
|
||||||
|
AVATARSENDDATA avatar_send_data;
|
||||||
|
AVATARRECEIVEDATA *avatar_recv_data; // We are receiving avatar data from this friend.
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
int (*function)(void *object, const uint8_t *data, uint32_t len);
|
int (*function)(void *object, const uint8_t *data, uint32_t len);
|
||||||
void *object;
|
void *object;
|
||||||
@ -209,6 +264,11 @@ typedef struct Messenger {
|
|||||||
|
|
||||||
USERSTATUS userstatus;
|
USERSTATUS userstatus;
|
||||||
|
|
||||||
|
AVATARFORMAT avatar_format;
|
||||||
|
uint8_t *avatar_data;
|
||||||
|
uint32_t avatar_data_length;
|
||||||
|
uint8_t avatar_hash[AVATAR_HASH_LENGTH];
|
||||||
|
|
||||||
Friend *friendlist;
|
Friend *friendlist;
|
||||||
uint32_t numfriends;
|
uint32_t numfriends;
|
||||||
|
|
||||||
@ -243,6 +303,10 @@ typedef struct Messenger {
|
|||||||
void *friend_connectionstatuschange_userdata;
|
void *friend_connectionstatuschange_userdata;
|
||||||
void (*friend_connectionstatuschange_internal)(struct Messenger *m, int32_t, uint8_t, void *);
|
void (*friend_connectionstatuschange_internal)(struct Messenger *m, int32_t, uint8_t, void *);
|
||||||
void *friend_connectionstatuschange_internal_userdata;
|
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)(struct Messenger *m, int32_t, const uint8_t *, void *);
|
||||||
void *group_invite_userdata;
|
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_userstatus(const Messenger *m, int32_t friendnumber);
|
||||||
uint8_t m_get_self_userstatus(const Messenger *m);
|
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 timestamp of last time friendnumber was seen online, or 0 if never seen.
|
||||||
* returns -1 on error.
|
* 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 m_callback_connectionstatus_internal_av(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, void *),
|
||||||
void *userdata);
|
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************/
|
/**********GROUP CHATS************/
|
||||||
|
|
||||||
/* Set the callback for group invites.
|
/* Set the callback for group invites.
|
||||||
|
@ -275,6 +275,42 @@ uint8_t tox_get_self_user_status(const Tox *tox)
|
|||||||
return m_get_self_userstatus(m);
|
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 timestamp of last time friendnumber was seen online, or 0 if never seen.
|
||||||
* returns -1 on error.
|
* 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);
|
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.) ************/
|
/**********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.
|
/* Functions to get/set the nospam part of the id.
|
||||||
|
145
toxcore/tox.h
145
toxcore/tox.h
@ -37,6 +37,8 @@ extern "C" {
|
|||||||
#define TOX_MAX_MESSAGE_LENGTH 1368
|
#define TOX_MAX_MESSAGE_LENGTH 1368
|
||||||
#define TOX_MAX_STATUSMESSAGE_LENGTH 1007
|
#define TOX_MAX_STATUSMESSAGE_LENGTH 1007
|
||||||
#define TOX_CLIENT_ID_SIZE 32
|
#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))
|
#define TOX_FRIEND_ADDRESS_SIZE (TOX_CLIENT_ID_SIZE + sizeof(uint32_t) + sizeof(uint16_t))
|
||||||
|
|
||||||
@ -70,6 +72,16 @@ typedef enum {
|
|||||||
}
|
}
|
||||||
TOX_USERSTATUS;
|
TOX_USERSTATUS;
|
||||||
|
|
||||||
|
|
||||||
|
/* AVATARFORMAT -
|
||||||
|
* Data formats for user avatar images
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
TOX_AVATARFORMAT_NONE,
|
||||||
|
TOX_AVATARFORMAT_PNG
|
||||||
|
}
|
||||||
|
TOX_AVATARFORMAT;
|
||||||
|
|
||||||
#ifndef __TOX_DEFINED__
|
#ifndef __TOX_DEFINED__
|
||||||
#define __TOX_DEFINED__
|
#define __TOX_DEFINED__
|
||||||
typedef struct Tox Tox;
|
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);
|
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 timestamp of last time friendnumber was seen online, or 0 if never seen.
|
||||||
* returns -1 on error.
|
* 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);
|
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.) ************/
|
/**********ADVANCED FUNCTIONS (If you don't know what they do you can safely ignore them.) ************/
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user