mirror of
https://github.com/irungentoo/toxcore.git
synced 2024-03-22 13:30:51 +08:00
Add support for user avatars in the core protocol
Add a protocol and the APIs to straightforwardly support user avatars in client applications. The protocol is designed to transfer avatars in background, between friends only, and minimize network load by providing a lightweight avatar notification for local cache validation. Strict safeguards are imposed to avoid damage from non-cooperative or malicious users and to limit network usage. The complete documentation is available in docs/Avatars.md and sample code is available in testing/test_avatars.c. Code and documentation are released under the GNU GPLv3 or later, as described in the file COPYING.
This commit is contained in:
parent
fa007a3b04
commit
e4f66475d8
658
docs/Avatars.md
Normal file
658
docs/Avatars.md
Normal file
|
@ -0,0 +1,658 @@
|
|||
# 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), JPEG, PNG, or
|
||||
GIF. 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_JPEG,
|
||||
TOX_AVATARFORMAT_PNG,
|
||||
TOX_AVATARFORMAT_GIF
|
||||
}
|
||||
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.ext", where "ext" is
|
||||
"jpg", "png", or "gif", according to the image format. Clients should
|
||||
keep just one of these files, 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.ext", where "xxxxx" is the complete client id encoded as an
|
||||
uppercase hexadecimal string and "ext" is "jpg", "png", or "gif",
|
||||
according to the image format. Clients should keep just one of these
|
||||
files per friend, with the data received from the last avatar data
|
||||
notification. 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.jpg
|
||||
Avatar data dir: /home/gildor/.config/tox/avatars/
|
||||
Elrond's avatar: /home/gildor/.config/tox/avatars/43656C65627269616E20646F6E277420546F782E426164206D656D6F72696573.jpg
|
||||
Elladan's avatar: /home/gildor/.config/tox/avatars/49486174655768656E48756D616E735468696E6B49416D4D7942726F74686572.gif
|
||||
Elrohir's avatar /home/gildor/.config/tox/avatars/726568746F7242794D6D41496B6E696854736E616D75486E6568576574614849.jpg
|
||||
Arwen's avatar: /home/gildor/.config/tox/avatars/53686520746F6F6B20476C6F7266696E64656C277320706C6163652068657265.png
|
||||
Lindir's avatar: /home/gildor/.config/tox/avatars/417070735772697474656E42794D6F7274616C734C6F6F6B54686553616D652E.gif
|
||||
|
||||
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_JPEG
|
||||
2 = AVATARFORMAT_PNG
|
||||
3 = AVATARFORMAT_GIF
|
||||
|
||||
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: 5 bytes
|
||||
[1: uint8_t op][1: uint32_t bytes_received]
|
||||
|
||||
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_MORE
|
||||
2 = AVATARDATACONTROL_ERROR
|
||||
|
||||
and 'bytes_received' is the number of bytes already received by the
|
||||
client when the operation is `MORE` or zero otherwise.
|
||||
|
||||
|
||||
- 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` (56) bytes of raw avatar image data.
|
||||
|
||||
|
||||
|
||||
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`. The field 'bytes_received' must be present, but
|
||||
should be set to zero and is ignored in this step.
|
||||
|
||||
- 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.
|
||||
|
||||
- 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 sent a request to "B", checks if the request was already
|
||||
started. If true, it is an error and it just ignores the request.
|
||||
|
||||
Otherwise, "A" decodes the message data and checks if the avatar data
|
||||
length stated in the field 'data_length' is acceptable (ie. less or
|
||||
equal than `TOX_MAX_AVATAR_DATA_LENGTH`). If not, it replies with an
|
||||
`PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to
|
||||
`AVATARDATACONTROL_ERROR` (or just ignores this, "A" holds no state).
|
||||
|
||||
If the size is acceptable, "A" marks the request as stated, stores the
|
||||
format, hash, and data length in the local state for user "B", sets a
|
||||
counter for the number of bytes received from the peer and replies with
|
||||
a `PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to
|
||||
`AVATARDATACONTROL_MORE` and 'bytes_received' set to zero (as no data
|
||||
was received yet).
|
||||
|
||||
- Upon receiving a `PACKET_ID_AVATAR_DATA_CONTROL` with op
|
||||
`AVATARDATACONTROL_MORE`, "B" sends an `PACKET_ID_AVATAR_DATA_PUSH`
|
||||
with up to `AVATAR_DATA_MAX_CHUNK_SIZE` bytes of data from the avatar,
|
||||
starting from the offset stated in the field 'bytes_received'.
|
||||
|
||||
If the requested offset is invalid, "B" replies with a
|
||||
`PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to
|
||||
`AVATARDATACONTROL_ERROR`.
|
||||
|
||||
"B" must have full control of the amount of data it sends to "A" and
|
||||
may, at any time, abort the transfer by sending a
|
||||
`PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to
|
||||
`AVATARDATACONTROL_ERROR`. This may happens, for example, if some limit
|
||||
was hit or a network data usage throttle enabled. A rationale for this
|
||||
procedures is available in section "Security considerations".
|
||||
|
||||
- Upon receiving a `PACKET_ID_AVATAR_DATA_PUSH`, "A" 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" requests more data by sending a
|
||||
`PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to
|
||||
`AVATARDATACONTROL_MORE` and 'bytes_received' set to the new offset.
|
||||
|
||||
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.
|
||||
|
||||
This "chatty" protocol mitigates a potential amplification attack,
|
||||
i.e., a malicious friend sending a very small data packet that causes
|
||||
another user to send a larger amount of data. The hash validation
|
||||
ensures that the avatar data is correct even if "B" changed its avatar
|
||||
data in the middle of the transfer. A rationale for this procedures is
|
||||
available in section "Security considerations".
|
||||
|
||||
- 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`);
|
||||
|
||||
- Making the avatar data transfer chatty: The user requesting avatar data
|
||||
can not force a peer to send large amounts of data in a single shot and
|
||||
must request new chunks as needed. The sender will never send more that
|
||||
1 kB of data in a single push and have ultimate control over the amount
|
||||
of data sent in a chunk;
|
||||
|
||||
- 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;
|
||||
|
||||
- (currently not implemented) Treating avatar data transfers as a low
|
||||
priority operation, handled only if no other packets are being
|
||||
processed.
|
||||
|
||||
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) \
|
||||
-lutil
|
||||
|
||||
|
||||
noinst_PROGRAMS += test_avatars
|
||||
|
||||
test_avatars_SOURCES = ../testing/test_avatars.c
|
||||
|
||||
test_avatars_CFLAGS = $(LIBSODIUM_CFLAGS) \
|
||||
$(NACL_CFLAGS)
|
||||
|
||||
test_avatars_LDADD = $(LIBSODIUM_LDFLAGS) \
|
||||
$(NACL_LDFLAGS) \
|
||||
libtoxcore.la \
|
||||
$(LIBSODIUM_LIBS) \
|
||||
$(NACL_OBJECTS) \
|
||||
$(NACL_LIBS)
|
||||
|
||||
endif
|
||||
|
||||
EXTRA_DIST += $(top_srcdir)/testing/misc_tools.c
|
||||
|
|
717
testing/test_avatars.c
Normal file
717
testing/test_avatars.c
Normal file
|
@ -0,0 +1,717 @@
|
|||
/*
|
||||
* 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, avatar.jpg, or avatar.gif. If given, the
|
||||
* bot will publish it. Otherwhise, 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_JPEG, "jpg", "avatar.jpg" },
|
||||
{ TOX_AVATARFORMAT_GIF, "gif", "avatar.gif" },
|
||||
{ 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 int write_cryptpacket_id(const Messenger *m, int32_t friendnumber, uint8_t packet_id, const uint8_t *data,
|
||||
uint32_t length, uint8_t congestion_control);
|
||||
static int send_avatar_data_control(const Messenger *m, const uint32_t friendnumber, uint8_t op, uint32_t bytes_received);
|
||||
|
||||
// friend_not_valid determines if the friendnumber passed is valid in the Messenger object
|
||||
static uint8_t friend_not_valid(const Messenger *m, int32_t friendnumber)
|
||||
|
@ -247,6 +248,10 @@ int32_t m_addfriend(Messenger *m, const uint8_t *address, const uint8_t *data, u
|
|||
m->friendlist[i].statusmessage = calloc(1, 1);
|
||||
m->friendlist[i].statusmessage_length = 1;
|
||||
m->friendlist[i].userstatus = USERSTATUS_NONE;
|
||||
m->friendlist[i].avatar_info_sent = 0;
|
||||
m->friendlist[i].avatar_recv_data = NULL;
|
||||
m->friendlist[i].avatar_send_data.bytes_sent = 0;
|
||||
m->friendlist[i].avatar_send_data.last_reset = 0;
|
||||
m->friendlist[i].is_typing = 0;
|
||||
memcpy(m->friendlist[i].info, data, length);
|
||||
m->friendlist[i].info_size = length;
|
||||
|
@ -330,6 +335,7 @@ int m_delfriend(Messenger *m, int32_t friendnumber)
|
|||
onion_delfriend(m->onion_c, m->friendlist[friendnumber].onion_friendnum);
|
||||
crypto_kill(m->net_crypto, m->friendlist[friendnumber].crypt_connection_id);
|
||||
free(m->friendlist[friendnumber].statusmessage);
|
||||
free(m->friendlist[friendnumber].avatar_recv_data);
|
||||
remove_request_received(&(m->fr), m->friendlist[friendnumber].client_id);
|
||||
memset(&(m->friendlist[friendnumber]), 0, sizeof(Friend));
|
||||
uint32_t i;
|
||||
|
@ -564,6 +570,126 @@ int m_set_userstatus(Messenger *m, uint8_t status)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int m_set_avatar(Messenger *m, uint8_t format, const uint8_t *data, uint32_t length)
|
||||
{
|
||||
if (length > MAX_AVATAR_DATA_LENGTH)
|
||||
return -1;
|
||||
|
||||
if (format == AVATARFORMAT_NONE) {
|
||||
free(m->avatar_data);
|
||||
m->avatar_data = NULL;
|
||||
m->avatar_data_length = 0;
|
||||
m->avatar_format = format;
|
||||
memset(m->avatar_hash, 0, AVATAR_HASH_LENGTH);
|
||||
}
|
||||
else {
|
||||
if (length == 0 || data == NULL)
|
||||
return -1;
|
||||
|
||||
uint8_t *tmp = realloc(m->avatar_data, length);
|
||||
if (tmp == NULL)
|
||||
return -1;
|
||||
|
||||
m->avatar_format = format;
|
||||
m->avatar_data = tmp;
|
||||
m->avatar_data_length = length;
|
||||
memcpy(m->avatar_data, data, length);
|
||||
|
||||
m_avatar_hash(m->avatar_hash, m->avatar_data, m->avatar_data_length);
|
||||
}
|
||||
|
||||
uint32_t i;
|
||||
for (i = 0; i < m->numfriends; ++i)
|
||||
m->friendlist[i].avatar_info_sent = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int m_get_self_avatar(const Messenger *m, uint8_t *format, uint8_t *buf, uint32_t *length, uint32_t maxlen, uint8_t *hash)
|
||||
{
|
||||
if (format)
|
||||
*format = m->avatar_format;
|
||||
|
||||
if (length)
|
||||
*length = m->avatar_data_length;
|
||||
|
||||
if (hash)
|
||||
memcpy(hash, m->avatar_hash, AVATAR_HASH_LENGTH);
|
||||
|
||||
if (buf != NULL && maxlen > 0) {
|
||||
if (m->avatar_data_length <= maxlen)
|
||||
memcpy(buf, m->avatar_data, m->avatar_data_length);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int m_avatar_hash(uint8_t *hash, const uint8_t *data, const uint32_t datalen)
|
||||
{
|
||||
if (hash == NULL)
|
||||
return -1;
|
||||
crypto_hash_sha256(hash, data, datalen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int m_request_avatar_info(const Messenger *m, const int32_t friendnumber)
|
||||
{
|
||||
if (friend_not_valid(m, friendnumber))
|
||||
return -1;
|
||||
if (write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_INFO_REQ, 0, 0, 0) >= 0)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
int m_send_avatar_info(const Messenger *m, const int32_t friendnumber)
|
||||
{
|
||||
if (friend_not_valid(m, friendnumber))
|
||||
return -1;
|
||||
|
||||
uint8_t data[sizeof(uint8_t) + AVATAR_HASH_LENGTH];
|
||||
data[0] = m->avatar_format;
|
||||
memcpy(data+1, m->avatar_hash, AVATAR_HASH_LENGTH);
|
||||
|
||||
int ret = write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_INFO, data, sizeof(data), 0);
|
||||
|
||||
if (ret >= 0)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
int m_request_avatar_data(const Messenger *m, const int32_t friendnumber)
|
||||
{
|
||||
if (friend_not_valid(m, friendnumber))
|
||||
return -1;
|
||||
|
||||
AVATARRECEIVEDATA *avrd = m->friendlist[friendnumber].avatar_recv_data;
|
||||
if (avrd == NULL) {
|
||||
avrd = malloc(sizeof(AVATARRECEIVEDATA));
|
||||
if (avrd == NULL)
|
||||
return -1;
|
||||
m->friendlist[friendnumber].avatar_recv_data = avrd;
|
||||
}
|
||||
|
||||
if (avrd->started) {
|
||||
LOGGER_DEBUG("Resetting already started data request. "
|
||||
"friendnumber == %u", friendnumber);
|
||||
}
|
||||
|
||||
memset(avrd, 0, sizeof(AVATARRECEIVEDATA));
|
||||
avrd->started = 0;
|
||||
avrd->bytes_received = 0;
|
||||
avrd->total_length = 0;
|
||||
avrd->format = AVATARFORMAT_NONE;
|
||||
|
||||
return send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_REQ, 0);
|
||||
}
|
||||
|
||||
|
||||
/* return the size of friendnumber's user status.
|
||||
* Guaranteed to be at most MAX_STATUSMESSAGE_LENGTH.
|
||||
*/
|
||||
|
@ -806,6 +932,18 @@ void m_callback_connectionstatus_internal_av(Messenger *m, void (*function)(Mess
|
|||
m->friend_connectionstatuschange_internal_userdata = userdata;
|
||||
}
|
||||
|
||||
void m_callback_avatar_info(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint8_t*, void *), void *userdata)
|
||||
{
|
||||
m->avatar_info_recv = function;
|
||||
m->avatar_info_recv_userdata = userdata;
|
||||
}
|
||||
|
||||
void m_callback_avatar_data(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint8_t*, uint8_t*, uint32_t, void *), void *userdata)
|
||||
{
|
||||
m->avatar_data_recv = function;
|
||||
m->avatar_data_recv_userdata = userdata;
|
||||
}
|
||||
|
||||
static void break_files(const Messenger *m, int32_t friendnumber);
|
||||
static void check_friend_connectionstatus(Messenger *m, int32_t friendnumber, uint8_t status)
|
||||
{
|
||||
|
@ -1844,6 +1982,9 @@ Messenger *new_messenger(Messenger_Options *options)
|
|||
m->net = new_networking(ip, TOX_PORT_DEFAULT);
|
||||
}
|
||||
|
||||
m->avatar_format = AVATARFORMAT_NONE;
|
||||
m->avatar_data = NULL;
|
||||
|
||||
if (m->net == NULL) {
|
||||
free(m);
|
||||
return NULL;
|
||||
|
@ -1921,6 +2062,7 @@ void kill_messenger(Messenger *m)
|
|||
free(m->friendlist[i].statusmessage);
|
||||
}
|
||||
|
||||
free(m->avatar_data);
|
||||
free(m->friendlist);
|
||||
free(m);
|
||||
}
|
||||
|
@ -1960,11 +2102,282 @@ static int handle_status(void *object, int i, uint8_t status)
|
|||
if (m->friendlist[i].status == FRIEND_ONLINE) {
|
||||
set_friend_status(m, i, FRIEND_CONFIRMED);
|
||||
}
|
||||
|
||||
/* Clear avatar transfer state */
|
||||
if (m->friendlist[i].avatar_recv_data) {
|
||||
free(m->friendlist[i].avatar_recv_data);
|
||||
m->friendlist[i].avatar_recv_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Sends an avatar data control packet to the peer. Usually to return status
|
||||
* values or request data.
|
||||
*/
|
||||
static int send_avatar_data_control(const Messenger *m, const uint32_t friendnumber,
|
||||
uint8_t op, uint32_t bytes_received)
|
||||
{
|
||||
uint8_t data[1 + sizeof(uint32_t)];
|
||||
data[0] = op;
|
||||
uint32_t tmp_recv = htonl(bytes_received);
|
||||
memcpy(data+1, &tmp_recv, sizeof(uint32_t));
|
||||
|
||||
int ret = write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_DATA_CONTROL,
|
||||
data, sizeof(data), 0);
|
||||
|
||||
LOGGER_DEBUG("friendnumber = %u, op = %u, bytes_received = %u, ret = %d",
|
||||
friendnumber, op, bytes_received, 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 + sizeof(uint32_t)) {
|
||||
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, 0);
|
||||
return -1; /* Error */
|
||||
}
|
||||
|
||||
LOGGER_DEBUG("friendnumber = %u, op = %u", friendnumber, data[0]);
|
||||
|
||||
switch (data[0]) {
|
||||
case AVATARDATACONTROL_REQ: {
|
||||
/* 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));
|
||||
|
||||
int ret = write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_DATA_START,
|
||||
start_data, sizeof(start_data), 0);
|
||||
if (ret >= 0)
|
||||
return 0; /* Ok */
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
case AVATARDATACONTROL_MORE: {
|
||||
/* Format: [uint8_t op][uint32_t position]
|
||||
* Friend received a chunk of data and asked for more.
|
||||
*/
|
||||
uint32_t bytes_received;
|
||||
memcpy(&bytes_received, data+1, sizeof(uint32_t));
|
||||
bytes_received = ntohl(bytes_received);
|
||||
|
||||
if (m->avatar_format == AVATARFORMAT_NONE
|
||||
|| m->avatar_data == NULL
|
||||
|| m->avatar_data_length == 0
|
||||
|| m->avatar_data_length < bytes_received) {
|
||||
/* Avatar changed, or position became invalid, or we
|
||||
* received a incorrect or malicious request. Abort. */
|
||||
LOGGER_DEBUG("Aborting. AVATARDATACONTROL_MORE with bad "
|
||||
"offset. bytes_received = %u, friendnumber = %u",
|
||||
bytes_received, friendnumber);
|
||||
send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 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 ad stops. */
|
||||
LOGGER_DEBUG("Avatar data transfer limit reached. "
|
||||
"friendnumber = %u", friendnumber);
|
||||
send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR, 0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Send another chunk of data */
|
||||
uint32_t chunk_len = m->avatar_data_length - bytes_received;
|
||||
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 + bytes_received, chunk_len);
|
||||
avsd->bytes_sent += chunk_len; /* For rate limit */
|
||||
|
||||
write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_DATA_PUSH,
|
||||
chunk, chunk_len, 0);
|
||||
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;
|
||||
}
|
||||
|
||||
LOGGER_DEBUG("requesting more data, friendnumber = %u", friendnumber);
|
||||
return send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_MORE, 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;
|
||||
}
|
||||
|
||||
/* Request more data */
|
||||
return send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_MORE,
|
||||
avrd->bytes_received);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int handle_packet(void *object, int i, uint8_t *temp, uint16_t len)
|
||||
{
|
||||
if (len == 0)
|
||||
|
@ -2102,6 +2515,41 @@ static int handle_packet(void *object, int i, uint8_t *temp, uint16_t len)
|
|||
break;
|
||||
}
|
||||
|
||||
case PACKET_ID_AVATAR_INFO_REQ: {
|
||||
/* Send our avatar information */
|
||||
m_send_avatar_info(m, i);
|
||||
break;
|
||||
}
|
||||
|
||||
case PACKET_ID_AVATAR_INFO: {
|
||||
if (m->avatar_info_recv) {
|
||||
/*
|
||||
* A malicious user may send an incomplete avatar info message.
|
||||
* Check if it have the correct size for the format:
|
||||
* [1 uint8_t: avatar format] [32 uint8_t: hash]
|
||||
*/
|
||||
if (data_length == AVATAR_HASH_LENGTH + 1) {
|
||||
(m->avatar_info_recv)(m, i, data[0], data+1, m->avatar_info_recv_userdata);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PACKET_ID_AVATAR_DATA_CONTROL: {
|
||||
handle_avatar_data_control(m, i, data, data_length);
|
||||
break;
|
||||
}
|
||||
|
||||
case PACKET_ID_AVATAR_DATA_START: {
|
||||
handle_avatar_data_start(m, i, data, data_length);
|
||||
break;
|
||||
}
|
||||
|
||||
case PACKET_ID_AVATAR_DATA_PUSH: {
|
||||
handle_avatar_data_push(m, i, data, data_length);
|
||||
break;
|
||||
}
|
||||
|
||||
case PACKET_ID_RECEIPT: {
|
||||
uint32_t msgid;
|
||||
|
||||
|
@ -2330,6 +2778,11 @@ void do_friends(Messenger *m)
|
|||
m->friendlist[i].userstatus_sent = 1;
|
||||
}
|
||||
|
||||
if (m->friendlist[i].avatar_info_sent == 0) {
|
||||
if (m_send_avatar_info(m, i) == 0)
|
||||
m->friendlist[i].avatar_info_sent = 1;
|
||||
}
|
||||
|
||||
if (m->friendlist[i].user_istyping_sent == 0) {
|
||||
if (send_user_istyping(m, i, m->friendlist[i].user_istyping))
|
||||
m->friendlist[i].user_istyping_sent = 1;
|
||||
|
|
|
@ -36,6 +36,9 @@
|
|||
#define MAX_NAME_LENGTH 128
|
||||
/* TODO: this must depend on other variable. */
|
||||
#define MAX_STATUSMESSAGE_LENGTH 1007
|
||||
#define MAX_AVATAR_DATA_LENGTH 16384
|
||||
#define AVATAR_HASH_LENGTH 32
|
||||
|
||||
|
||||
#define FRIEND_ADDRESS_SIZE (crypto_box_PUBLICKEYBYTES + sizeof(uint32_t) + sizeof(uint16_t))
|
||||
|
||||
|
@ -46,6 +49,11 @@
|
|||
#define PACKET_ID_STATUSMESSAGE 49
|
||||
#define PACKET_ID_USERSTATUS 50
|
||||
#define PACKET_ID_TYPING 51
|
||||
#define PACKET_ID_AVATAR_INFO_REQ 52
|
||||
#define PACKET_ID_AVATAR_INFO 53
|
||||
#define PACKET_ID_AVATAR_DATA_CONTROL 54
|
||||
#define PACKET_ID_AVATAR_DATA_START 55
|
||||
#define PACKET_ID_AVATAR_DATA_PUSH 56
|
||||
#define PACKET_ID_RECEIPT 63
|
||||
#define PACKET_ID_MESSAGE 64
|
||||
#define PACKET_ID_ACTION 65
|
||||
|
@ -109,6 +117,13 @@ enum {
|
|||
/* If no packets are received from friend in this time interval, kill the connection. */
|
||||
#define FRIEND_CONNECTION_TIMEOUT (FRIEND_PING_INTERVAL * 3)
|
||||
|
||||
/* Must be <= MAX_CRYPTO_DATA_SIZE */
|
||||
#define AVATAR_DATA_MAX_CHUNK_SIZE 1024
|
||||
|
||||
/* Per-friend data limit for avatar data requests */
|
||||
#define AVATAR_DATA_TRANSFER_LIMIT (10*MAX_AVATAR_DATA_LENGTH)
|
||||
#define AVATAR_DATA_TRANSFER_TIMEOUT (20*60)
|
||||
|
||||
|
||||
/* USERSTATUS -
|
||||
* Represents userstatuses someone can have.
|
||||
|
@ -122,6 +137,45 @@ typedef enum {
|
|||
}
|
||||
USERSTATUS;
|
||||
|
||||
/* AVATARFORMAT -
|
||||
* Data formats for user avatar images
|
||||
*/
|
||||
typedef enum {
|
||||
AVATARFORMAT_NONE,
|
||||
AVATARFORMAT_JPEG,
|
||||
AVATARFORMAT_PNG,
|
||||
AVATARFORMAT_GIF
|
||||
}
|
||||
AVATARFORMAT;
|
||||
|
||||
/* AVATARDATACONTROL
|
||||
* To control avatar data requests (PACKET_ID_AVATAR_DATA_CONTROL)
|
||||
*/
|
||||
typedef enum {
|
||||
AVATARDATACONTROL_REQ,
|
||||
AVATARDATACONTROL_MORE,
|
||||
AVATARDATACONTROL_ERROR
|
||||
}
|
||||
AVATARDATACONTROL;
|
||||
|
||||
typedef struct {
|
||||
uint8_t started;
|
||||
AVATARFORMAT format;
|
||||
uint8_t hash[AVATAR_HASH_LENGTH];
|
||||
uint32_t total_length;
|
||||
uint32_t bytes_received;
|
||||
uint8_t data[MAX_AVATAR_DATA_LENGTH];
|
||||
}
|
||||
AVATARRECEIVEDATA;
|
||||
|
||||
typedef struct {
|
||||
/* Fields only used to limit the network usage from a given friend */
|
||||
uint32_t bytes_sent; /* Total bytes send to this user */
|
||||
uint64_t last_reset; /* Time the data counter was last reset */
|
||||
}
|
||||
AVATARSENDDATA;
|
||||
|
||||
|
||||
struct File_Transfers {
|
||||
uint64_t size;
|
||||
uint64_t transferred;
|
||||
|
@ -163,6 +217,7 @@ typedef struct {
|
|||
uint8_t statusmessage_sent;
|
||||
USERSTATUS userstatus;
|
||||
uint8_t userstatus_sent;
|
||||
uint8_t avatar_info_sent;
|
||||
uint8_t user_istyping;
|
||||
uint8_t user_istyping_sent;
|
||||
uint8_t is_typing;
|
||||
|
@ -178,6 +233,9 @@ typedef struct {
|
|||
int invited_groups[MAX_INVITED_GROUPS];
|
||||
uint16_t invited_groups_num;
|
||||
|
||||
AVATARSENDDATA avatar_send_data;
|
||||
AVATARRECEIVEDATA *avatar_recv_data; // We are receiving avatar data from this friend.
|
||||
|
||||
struct {
|
||||
int (*function)(void *object, const uint8_t *data, uint32_t len);
|
||||
void *object;
|
||||
|
@ -209,6 +267,11 @@ typedef struct Messenger {
|
|||
|
||||
USERSTATUS userstatus;
|
||||
|
||||
AVATARFORMAT avatar_format;
|
||||
uint8_t *avatar_data;
|
||||
uint32_t avatar_data_length;
|
||||
uint8_t avatar_hash[AVATAR_HASH_LENGTH];
|
||||
|
||||
Friend *friendlist;
|
||||
uint32_t numfriends;
|
||||
|
||||
|
@ -239,6 +302,10 @@ typedef struct Messenger {
|
|||
void *friend_connectionstatuschange_userdata;
|
||||
void (*friend_connectionstatuschange_internal)(struct Messenger *m, int32_t, uint8_t, void *);
|
||||
void *friend_connectionstatuschange_internal_userdata;
|
||||
void *avatar_info_recv_userdata;
|
||||
void (*avatar_info_recv)(struct Messenger *m, int32_t, uint8_t, uint8_t*, void *);
|
||||
void *avatar_data_recv_userdata;
|
||||
void (*avatar_data_recv)(struct Messenger *m, int32_t, uint8_t, uint8_t*, uint8_t*, uint32_t, void *);
|
||||
|
||||
void (*group_invite)(struct Messenger *m, int32_t, const uint8_t *, void *);
|
||||
void *group_invite_userdata;
|
||||
|
@ -433,6 +500,93 @@ int m_copy_self_statusmessage(const Messenger *m, uint8_t *buf, uint32_t maxlen)
|
|||
uint8_t m_get_userstatus(const Messenger *m, int32_t friendnumber);
|
||||
uint8_t m_get_self_userstatus(const Messenger *m);
|
||||
|
||||
|
||||
/* Set the user avatar image data.
|
||||
* This should be made before connecting, so we will not announce that the user have no avatar
|
||||
* before setting and announcing a new one, forcing the peers to re-download it.
|
||||
*
|
||||
* Notice that the library treats the image as raw data and does not interpret it by any way.
|
||||
*
|
||||
* Arguments:
|
||||
* format - Avatar image format or NONE for user with no avatar (see AVATARFORMAT);
|
||||
* data - pointer to the avatar data (may be NULL it the format is NONE);
|
||||
* length - length of image data. Must be <= MAX_AVATAR_DATA_LENGTH.
|
||||
*
|
||||
* returns 0 on success
|
||||
* returns -1 on failure.
|
||||
*/
|
||||
int m_set_avatar(Messenger *m, uint8_t format, const uint8_t *data, uint32_t length);
|
||||
|
||||
/* Get avatar data from the current user.
|
||||
* Copies the current user avatar data to the destination buffer and sets the image format
|
||||
* accordingly.
|
||||
*
|
||||
* If the avatar format is NONE, the buffer 'buf' isleft uninitialized, 'hash' is zeroed, and
|
||||
* 'length' is set to zero.
|
||||
*
|
||||
* If any of the pointers format, buf, length, and hash are NULL, that particular field will be ignored.
|
||||
*
|
||||
* Arguments:
|
||||
* format - destination pointer to the avatar image format (see AVATARFORMAT);
|
||||
* buf - destination buffer to the image data. Must have at least 'maxlen' bytes;
|
||||
* length - destination pointer to the image data length;
|
||||
* maxlen - length of the destination buffer 'buf';
|
||||
* hash - destination pointer to the avatar hash (it must be exactly AVATAR_HASH_LENGTH bytes long).
|
||||
*
|
||||
* returns 0 on success;
|
||||
* returns -1 on failure.
|
||||
*
|
||||
*/
|
||||
int m_get_self_avatar(const Messenger *m, uint8_t *format, uint8_t *buf, uint32_t *length, uint32_t maxlen, uint8_t *hash);
|
||||
|
||||
/* Generates a cryptographic hash of the given avatar data.
|
||||
* This function is a wrapper to internal message-digest functions and specifically provided
|
||||
* to generate hashes from user avatars that may be memcmp()ed with the values returned by the
|
||||
* other avatar functions. It is specially important to validate cached avatars.
|
||||
*
|
||||
* Arguments:
|
||||
* hash - destination buffer for the hash data, it must be exactly AVATAR_HASH_LENGTH bytes long.
|
||||
* data - avatar image data;
|
||||
* datalen - length of the avatar image data; it must be <= MAX_AVATAR_DATA_LENGTH.
|
||||
*
|
||||
* returns 0 on success
|
||||
* returns -1 on failure.
|
||||
*/
|
||||
int m_avatar_hash(uint8_t *hash, const uint8_t *data, const uint32_t datalen);
|
||||
|
||||
/* Request avatar information from a friend.
|
||||
* Asks a friend to provide their avatar information (image format and hash). The friend may
|
||||
* or may not answer this request and, if answered, the information will be provided through
|
||||
* the callback 'avatar_info'.
|
||||
*
|
||||
* returns 0 on success
|
||||
* returns -1 on failure.
|
||||
*/
|
||||
int m_request_avatar_info(const Messenger *m, const int32_t friendnumber);
|
||||
|
||||
/* Send an unrequested avatar information to a friend.
|
||||
* Sends our avatar format and hash to a friend; he/she can use this information to validate
|
||||
* an avatar from the cache and may (or not) reply with an avatar data request.
|
||||
*
|
||||
* Notice: it is NOT necessary to send these notification after changing the avatar or
|
||||
* connecting. The library already does this.
|
||||
*
|
||||
* returns 0 on success
|
||||
* returns -1 on failure.
|
||||
*/
|
||||
int m_send_avatar_info(const Messenger *m, const int32_t friendnumber);
|
||||
|
||||
|
||||
/* Request the avatar data from a friend.
|
||||
* Ask a friend to send their avatar data. The friend may or may not answer this request and,
|
||||
* if answered, the information will be provided in callback 'avatar_data'.
|
||||
*
|
||||
* returns 0 on sucess
|
||||
* returns -1 on failure.
|
||||
*/
|
||||
int m_request_avatar_data(const Messenger *m, const int32_t friendnumber);
|
||||
|
||||
|
||||
/* returns timestamp of last time friendnumber was seen online, or 0 if never seen.
|
||||
* returns -1 on error.
|
||||
*/
|
||||
|
@ -529,6 +683,47 @@ void m_callback_connectionstatus(Messenger *m, void (*function)(Messenger *m, in
|
|||
void m_callback_connectionstatus_internal_av(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, void *),
|
||||
void *userdata);
|
||||
|
||||
|
||||
/* Set the callback function for avatar information.
|
||||
* This callback will be called when avatar information are received from friends. These events
|
||||
* can arrive at anytime, but are usually received uppon connection and in reply of avatar
|
||||
* information requests.
|
||||
*
|
||||
* Function format is:
|
||||
* function(Tox *tox, int32_t friendnumber, uint8_t format, uint8_t *hash, void *userdata)
|
||||
*
|
||||
* where 'format' is the avatar image format (see AVATARFORMAT) and 'hash' is the hash of
|
||||
* the avatar data for caching purposes and it is exactly AVATAR_HASH_LENGTH long. If the
|
||||
* image format is NONE, the hash is zeroed.
|
||||
*
|
||||
*/
|
||||
void m_callback_avatar_info(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint8_t*, void *), void *userdata);
|
||||
|
||||
|
||||
/* Set the callback function for avatar data.
|
||||
* This callback will be called when the complete avatar data was correctly received from a
|
||||
* friend. This only happens in reply of a avatar data request (see tox_request_avatar_data);
|
||||
*
|
||||
* Function format is:
|
||||
* function(Tox *tox, int32_t friendnumber, uint8_t format, uint8_t *hash, uint8_t *data, uint32_t datalen, void *userdata)
|
||||
*
|
||||
* where 'format' is the avatar image format (see AVATARFORMAT); 'hash' is the
|
||||
* locally-calculated cryptographic hash of the avatar data and it is exactly
|
||||
* AVATAR_HASH_LENGTH long; 'data' is the avatar image data and 'datalen' is the length
|
||||
* of such data.
|
||||
*
|
||||
* If format is NONE, 'data' is NULL, 'datalen' is zero, and the hash is zeroed. The hash is
|
||||
* always validated locally with the function tox_avatar_hash and ensured to match the image
|
||||
* data, so this value can be safely used to compare with cached avatars.
|
||||
*
|
||||
* WARNING: users MUST treat all avatar image data received from another peer as untrusted and
|
||||
* potentially malicious. The library only ensures that the data which arrived is the same the
|
||||
* other user sent, and does not interpret or validate any image data.
|
||||
*/
|
||||
void m_callback_avatar_data(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint8_t*, uint8_t*, uint32_t, void *), void *userdata);
|
||||
|
||||
|
||||
|
||||
/**********GROUP CHATS************/
|
||||
|
||||
/* Set the callback for group invites.
|
||||
|
|
|
@ -275,6 +275,42 @@ uint8_t tox_get_self_user_status(const Tox *tox)
|
|||
return m_get_self_userstatus(m);
|
||||
}
|
||||
|
||||
int tox_set_avatar(Tox *tox, uint8_t format, const uint8_t *data, uint32_t length)
|
||||
{
|
||||
Messenger *m = tox;
|
||||
return m_set_avatar(m, format, data, length);
|
||||
}
|
||||
|
||||
int tox_get_self_avatar(const Tox *tox, uint8_t *format, uint8_t *buf, uint32_t *length, uint32_t maxlen, uint8_t *hash)
|
||||
{
|
||||
const Messenger *m = tox;
|
||||
return m_get_self_avatar(m, format, buf, length, maxlen, hash);
|
||||
}
|
||||
|
||||
int tox_avatar_hash(const Tox *tox, uint8_t *hash, const uint8_t *data, const uint32_t datalen)
|
||||
{
|
||||
return m_avatar_hash(hash, data, datalen);
|
||||
}
|
||||
|
||||
int tox_request_avatar_info(const Tox *tox, const int32_t friendnumber)
|
||||
{
|
||||
const Messenger *m = tox;
|
||||
return m_request_avatar_info(m, friendnumber);
|
||||
}
|
||||
|
||||
int tox_send_avatar_info(Tox *tox, const int32_t friendnumber)
|
||||
{
|
||||
const Messenger *m = tox;
|
||||
return m_send_avatar_info(m, friendnumber);
|
||||
}
|
||||
|
||||
int tox_request_avatar_data(const Tox *tox, const int32_t friendnumber)
|
||||
{
|
||||
const Messenger *m = tox;
|
||||
return m_request_avatar_data(m, friendnumber);
|
||||
}
|
||||
|
||||
|
||||
/* returns timestamp of last time friendnumber was seen online, or 0 if never seen.
|
||||
* returns -1 on error.
|
||||
*/
|
||||
|
@ -439,6 +475,23 @@ void tox_callback_connection_status(Tox *tox, void (*function)(Messenger *tox, i
|
|||
m_callback_connectionstatus(m, function, userdata);
|
||||
}
|
||||
|
||||
void tox_callback_avatar_info(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t*, void *), void *userdata)
|
||||
{
|
||||
Messenger *m = tox;
|
||||
m_callback_avatar_info(m, function, userdata);
|
||||
}
|
||||
|
||||
|
||||
void tox_callback_avatar_data(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t*, uint8_t*, uint32_t, void *), void *userdata)
|
||||
{
|
||||
Messenger *m = tox;
|
||||
m_callback_avatar_data(m, function, userdata);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**********ADVANCED FUNCTIONS (If you don't know what they do you can safely ignore them.) ************/
|
||||
|
||||
/* Functions to get/set the nospam part of the id.
|
||||
|
|
144
toxcore/tox.h
144
toxcore/tox.h
|
@ -37,6 +37,8 @@ extern "C" {
|
|||
#define TOX_MAX_MESSAGE_LENGTH 1368
|
||||
#define TOX_MAX_STATUSMESSAGE_LENGTH 1007
|
||||
#define TOX_CLIENT_ID_SIZE 32
|
||||
#define TOX_MAX_AVATAR_DATA_LENGTH 16384
|
||||
#define TOX_AVATAR_HASH_LENGTH 32
|
||||
|
||||
#define TOX_FRIEND_ADDRESS_SIZE (TOX_CLIENT_ID_SIZE + sizeof(uint32_t) + sizeof(uint16_t))
|
||||
|
||||
|
@ -67,6 +69,18 @@ typedef enum {
|
|||
}
|
||||
TOX_USERSTATUS;
|
||||
|
||||
|
||||
/* AVATARFORMAT -
|
||||
* Data formats for user avatar images
|
||||
*/
|
||||
typedef enum {
|
||||
TOX_AVATARFORMAT_NONE,
|
||||
TOX_AVATARFORMAT_JPEG,
|
||||
TOX_AVATARFORMAT_PNG,
|
||||
TOX_AVATARFORMAT_GIF
|
||||
}
|
||||
TOX_AVATARFORMAT;
|
||||
|
||||
#ifndef __TOX_DEFINED__
|
||||
#define __TOX_DEFINED__
|
||||
typedef struct Tox Tox;
|
||||
|
@ -240,6 +254,96 @@ uint8_t tox_get_user_status(const Tox *tox, int32_t friendnumber);
|
|||
uint8_t tox_get_self_user_status(const Tox *tox);
|
||||
|
||||
|
||||
/* Set the user avatar image data.
|
||||
* This should be made before connecting, so we will not announce that the user have no avatar
|
||||
* before setting and announcing a new one, forcing the peers to re-download it.
|
||||
*
|
||||
* Notice that the library treats the image as raw data and does not interpret it by any way.
|
||||
*
|
||||
* Arguments:
|
||||
* format - Avatar image format or NONE for user with no avatar (see TOX_AVATARFORMAT);
|
||||
* data - pointer to the avatar data (may be NULL it the format is NONE);
|
||||
* length - length of image data. Must be <= TOX_MAX_AVATAR_DATA_LENGTH.
|
||||
*
|
||||
* returns 0 on success
|
||||
* returns -1 on failure.
|
||||
*/
|
||||
int tox_set_avatar(Tox *tox, uint8_t format, const uint8_t *data, uint32_t length);
|
||||
|
||||
|
||||
/* Get avatar data from the current user.
|
||||
* Copies the current user avatar data to the destination buffer and sets the image format
|
||||
* accordingly.
|
||||
*
|
||||
* If the avatar format is NONE, the buffer 'buf' isleft uninitialized, 'hash' is zeroed, and
|
||||
* 'length' is set to zero.
|
||||
*
|
||||
* If any of the pointers format, buf, length, and hash are NULL, that particular field will be ignored.
|
||||
*
|
||||
* Arguments:
|
||||
* format - destination pointer to the avatar image format (see TOX_AVATARFORMAT);
|
||||
* buf - destination buffer to the image data. Must have at least 'maxlen' bytes;
|
||||
* length - destination pointer to the image data length;
|
||||
* maxlen - length of the destination buffer 'buf';
|
||||
* hash - destination pointer to the avatar hash (it must be exactly TOX_AVATAR_HASH_LENGTH bytes long).
|
||||
*
|
||||
* returns 0 on success;
|
||||
* returns -1 on failure.
|
||||
*
|
||||
*/
|
||||
int tox_get_self_avatar(const Tox *tox, uint8_t *format, uint8_t *buf, uint32_t *length, uint32_t maxlen, uint8_t *hash);
|
||||
|
||||
|
||||
/* Generates a cryptographic hash of the given avatar data.
|
||||
* This function is a wrapper to internal message-digest functions and specifically provided
|
||||
* to generate hashes from user avatars that may be memcmp()ed with the values returned by the
|
||||
* other avatar functions. It is specially important to validate cached avatars.
|
||||
*
|
||||
* Arguments:
|
||||
* hash - destination buffer for the hash data, it must be exactly TOX_AVATAR_HASH_LENGTH bytes long.
|
||||
* data - avatar image data;
|
||||
* datalen - length of the avatar image data; it must be <= TOX_MAX_AVATAR_DATA_LENGTH.
|
||||
*
|
||||
* returns 0 on success
|
||||
* returns -1 on failure.
|
||||
*/
|
||||
int tox_avatar_hash(const Tox *tox, uint8_t *hash, const uint8_t *data, const uint32_t datalen);
|
||||
|
||||
|
||||
/* Request avatar information from a friend.
|
||||
* Asks a friend to provide their avatar information (image format and hash). The friend may
|
||||
* or may not answer this request and, if answered, the information will be provided through
|
||||
* the callback 'avatar_info'.
|
||||
*
|
||||
* returns 0 on success
|
||||
* returns -1 on failure.
|
||||
*/
|
||||
int tox_request_avatar_info(const Tox *tox, const int32_t friendnumber);
|
||||
|
||||
|
||||
/* Send an unrequested avatar information to a friend.
|
||||
* Sends our avatar format and hash to a friend; he/she can use this information to validate
|
||||
* an avatar from the cache and may (or not) reply with an avatar data request.
|
||||
*
|
||||
* Notice: it is NOT necessary to send these notification after changing the avatar or
|
||||
* connecting. The library already does this.
|
||||
*
|
||||
* returns 0 on success
|
||||
* returns -1 on failure.
|
||||
*/
|
||||
int tox_send_avatar_info(Tox *tox, const int32_t friendnumber);
|
||||
|
||||
|
||||
/* Request the avatar data from a friend.
|
||||
* Ask a friend to send their avatar data. The friend may or may not answer this request and,
|
||||
* if answered, the information will be provided in callback 'avatar_data'.
|
||||
*
|
||||
* returns 0 on sucess
|
||||
* returns -1 on failure.
|
||||
*/
|
||||
int tox_request_avatar_data(const Tox *tox, const int32_t friendnumber);
|
||||
|
||||
|
||||
/* returns timestamp of last time friendnumber was seen online, or 0 if never seen.
|
||||
* returns -1 on error.
|
||||
*/
|
||||
|
@ -341,6 +445,46 @@ void tox_callback_read_receipt(Tox *tox, void (*function)(Tox *tox, int32_t, uin
|
|||
*/
|
||||
void tox_callback_connection_status(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, void *), void *userdata);
|
||||
|
||||
/* Set the callback function for avatar information.
|
||||
* This callback will be called when avatar information are received from friends. These events
|
||||
* can arrive at anytime, but are usually received uppon connection and in reply of avatar
|
||||
* information requests.
|
||||
*
|
||||
* Function format is:
|
||||
* function(Tox *tox, int32_t friendnumber, uint8_t format, uint8_t *hash, void *userdata)
|
||||
*
|
||||
* where 'format' is the avatar image format (see TOX_AVATARFORMAT) and 'hash' is the hash of
|
||||
* the avatar data for caching purposes and it is exactly TOX_AVATAR_HASH_LENGTH long. If the
|
||||
* image format is NONE, the hash is zeroed.
|
||||
*
|
||||
*/
|
||||
void tox_callback_avatar_info(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t*, void *), void *userdata);
|
||||
|
||||
|
||||
/* Set the callback function for avatar data.
|
||||
* This callback will be called when the complete avatar data was correctly received from a
|
||||
* friend. This only happens in reply of a avatar data request (see tox_request_avatar_data);
|
||||
*
|
||||
* Function format is:
|
||||
* function(Tox *tox, int32_t friendnumber, uint8_t format, uint8_t *hash, uint8_t *data, uint32_t datalen, void *userdata)
|
||||
*
|
||||
* where 'format' is the avatar image format (see TOX_AVATARFORMAT); 'hash' is the
|
||||
* locally-calculated cryptographic hash of the avatar data and it is exactly
|
||||
* TOX_AVATAR_HASH_LENGTH long; 'data' is the avatar image data and 'datalen' is the length
|
||||
* of such data.
|
||||
*
|
||||
* If format is NONE, 'data' is NULL, 'datalen' is zero, and the hash is zeroed. The hash is
|
||||
* always validated locally with the function tox_avatar_hash and ensured to match the image
|
||||
* data, so this value can be safely used to compare with cached avatars.
|
||||
*
|
||||
* WARNING: users MUST treat all avatar image data received from another peer as untrusted and
|
||||
* potentially malicious. The library only ensures that the data which arrived is the same the
|
||||
* other user sent, and does not interpret or validate any image data.
|
||||
*/
|
||||
void tox_callback_avatar_data(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t*, uint8_t*, uint32_t, void *), void *userdata);
|
||||
|
||||
|
||||
|
||||
|
||||
/**********ADVANCED FUNCTIONS (If you don't know what they do you can safely ignore them.) ************/
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user