diff options
-rw-r--r-- | docs/Avatars.md | 611 | ||||
-rw-r--r-- | testing/Makefile.inc | 15 | ||||
-rw-r--r-- | testing/test_avatars.c | 770 | ||||
-rw-r--r-- | toxcore/Messenger.c | 469 | ||||
-rw-r--r-- | toxcore/Messenger.h | 195 | ||||
-rw-r--r-- | toxcore/tox.c | 54 | ||||
-rw-r--r-- | toxcore/tox.h | 145 |
7 files changed, 2259 insertions, 0 deletions
diff --git a/docs/Avatars.md b/docs/Avatars.md new file mode 100644 index 00000000..97b65c10 --- /dev/null +++ b/docs/Avatars.md | |||
@@ -0,0 +1,611 @@ | |||
1 | # User avatars in Tox | ||
2 | |||
3 | |||
4 | |||
5 | ## Introduction and rationale | ||
6 | |||
7 | User avatars are small icons or images used to identify users in the friend | ||
8 | list; they exists in virtually all VoIP and IM protocols and provide an easy | ||
9 | way to an user identify another in the friend list. | ||
10 | |||
11 | This document describes the implementation of avatars in the Tox protocol, | ||
12 | according to the following design considerations: | ||
13 | |||
14 | - Avatars are handled as private information, ie., only exchanged over | ||
15 | Tox encrypted channels among previously authenticated friends; | ||
16 | |||
17 | - The library treats all images as blobs and does not interpret or | ||
18 | understands image formats, only ensures that the avatar data sent by | ||
19 | an user is correctly received by the other. The client application is | ||
20 | responsible for validating, decoding, resizing, and presenting the | ||
21 | image to the user. | ||
22 | |||
23 | - There is a strict limit of 16 KiB to the avatar raw data size -- this | ||
24 | seems suitable for practical use as, for example, the raw data of an | ||
25 | uncompressed 64 x 64 pixels 24 bpp RGB bitmap is 12288 bytes long; the | ||
26 | data limit provides enough space for larger bitmaps if the usual | ||
27 | compressed formats are used. | ||
28 | |||
29 | **Notice:** As designed, this limit can be changed in the future without | ||
30 | breaking the protocol compatibility, but clients using the original | ||
31 | limit will reject larger avatars; | ||
32 | |||
33 | - The protocol MUST provide means to allow caching and avoid unnecessary | ||
34 | data transfers; | ||
35 | |||
36 | - Avatars are transfered between clients in a background operation; | ||
37 | |||
38 | - Avatars are served in a "best effort" basis, without breaking clients | ||
39 | who do not support them; | ||
40 | |||
41 | - The protocol MUST resist to malicious users; | ||
42 | |||
43 | - The protocol MUST work with both UDP and TCP networks. | ||
44 | |||
45 | |||
46 | The Single Tox Standard Draft v.0.1.0 recommends implementing avatars as | ||
47 | a purely client-side feature through a procedure that can be summarized as | ||
48 | sending a specially named file as a file transfer request and accepting | ||
49 | it silently. This procedure can be improved to provide the previously stated | ||
50 | design considerations, but this requires a higher integration with the core | ||
51 | protocol. Moving this feature to the core protocol also: | ||
52 | |||
53 | - Provides a simpler and cleaner interfaces for client applications; | ||
54 | |||
55 | - Hides protocol complexities from the client; | ||
56 | |||
57 | - Avoids code duplication and ad-hoc protocols in the clients; | ||
58 | |||
59 | - Avoids incompatibility between client implementations; | ||
60 | |||
61 | - Allows important optimizations such as lightweight notification of | ||
62 | removed and updated avatars; | ||
63 | |||
64 | - Plays well with cache schemes; | ||
65 | |||
66 | - Makes avatar transfer an essentially background operation. | ||
67 | |||
68 | |||
69 | |||
70 | |||
71 | |||
72 | |||
73 | ## High level description | ||
74 | |||
75 | The avatar exchange is implemented with the following new elements in the | ||
76 | Tox protocol. This is a very high level description and the usage patterns | ||
77 | expected from client applications are described in Section "Using Avatars | ||
78 | in Client Applications" and a low level protocol description is available | ||
79 | in Section "Internal Protocol Description". | ||
80 | |||
81 | - **Avatar Information Notifications** are events which may be sent by | ||
82 | an user to another anytime, but are usually sent after one of them | ||
83 | connects to the network, changes his avatar, or in reply to an **avatar | ||
84 | information request**. They are delivered by a very lightweight message | ||
85 | but with information enough to allow an user to validate or discard an | ||
86 | avatar from the local cache and decide if is interesting to request the | ||
87 | avatar data from the peer. | ||
88 | |||
89 | This event contain two data fields: (1) the image format and (2) the | ||
90 | cryptographic hash of the actual image data. Image format may be NONE | ||
91 | (for users who have no avatar or removed their avatars) or PNG. The | ||
92 | cryptographic hash is intended to be compared with the hash o the | ||
93 | currently cached avatar (if any) and check if it stills up to date. | ||
94 | |||
95 | - **Avatar Information Requests** are very lightweight messages sent by an | ||
96 | user asking for an **avatar information notification**. They may be sent | ||
97 | as part of the login process or when the client thinks the currently | ||
98 | cached avatar is outdated. The receiver may or may not answer to this | ||
99 | request. This message contains no data fields; | ||
100 | |||
101 | - An **Avatar Data Request** is sent by an user asking another for his | ||
102 | complete avatar data. It is sent only when the requesting user decides | ||
103 | the avatar do not exists in the local cache or is outdated. The receiver | ||
104 | may or may not answer to this request. This message contains no data | ||
105 | fields. | ||
106 | |||
107 | - An **Avatar Data Notification** is an event signaling the client that | ||
108 | the complete avatar image data of another user is available. The actual | ||
109 | data transfer is implemented using several data and control messages, | ||
110 | but the details are hidden from the client applications. This event can | ||
111 | only arrive in reply to an **avatar data request**. | ||
112 | |||
113 | This event contains three data fields: (1) the image format, (2) the | ||
114 | cryptographic hash of the image data, and (3) the raw image data. If the | ||
115 | image format is NONE (i.e. no avatar) the hash is zeroed and the image | ||
116 | data is empty. The raw image data is locally validated and ensured to | ||
117 | match the hash (the event is **not** triggered otherwise). | ||
118 | |||
119 | |||
120 | |||
121 | |||
122 | |||
123 | ## API | ||
124 | |||
125 | To implement this feature, the following public symbols were added. The | ||
126 | complete API documentation is available in `tox.h`. | ||
127 | |||
128 | |||
129 | ``` | ||
130 | #define TOX_MAX_AVATAR_DATA_LENGTH 16384 | ||
131 | #define TOX_AVATAR_HASH_LENGTH 32 | ||
132 | |||
133 | |||
134 | /* Data formats for user avatar images */ | ||
135 | typedef enum { | ||
136 | TOX_AVATARFORMAT_NONE, | ||
137 | TOX_AVATARFORMAT_PNG | ||
138 | } | ||
139 | TOX_AVATARFORMAT; | ||
140 | |||
141 | |||
142 | |||
143 | /* Set the user avatar image data. */ | ||
144 | int tox_set_avatar(Tox *tox, uint8_t format, const uint8_t *data, uint32_t length); | ||
145 | |||
146 | /* Get avatar data from the current user. */ | ||
147 | int tox_get_self_avatar(const Tox *tox, uint8_t *format, uint8_t *buf, uint32_t *length, uint32_t maxlen, uint8_t *hash); | ||
148 | |||
149 | /* Generates a cryptographic hash of the given avatar data. */ | ||
150 | int tox_avatar_hash(const Tox *tox, uint8_t *hash, const uint8_t *data, const uint32_t datalen); | ||
151 | |||
152 | /* Request avatar information from a friend. */ | ||
153 | int tox_request_avatar_info(const Tox *tox, const int32_t friendnumber); | ||
154 | |||
155 | /* Send an unrequested avatar information to a friend. */ | ||
156 | int tox_send_avatar_info(Tox *tox, const int32_t friendnumber); | ||
157 | |||
158 | /* Request the avatar data from a friend. */ | ||
159 | int tox_request_avatar_data(const Tox *tox, const int32_t friendnumber); | ||
160 | |||
161 | /* Set the callback function for avatar data. */ | ||
162 | void tox_callback_avatar_info(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t*, void *), void *userdata); | ||
163 | |||
164 | /* Set the callback function for avatar data. */ | ||
165 | void tox_callback_avatar_data(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t*, uint8_t*, uint32_t, void *), void *userdata); | ||
166 | ``` | ||
167 | |||
168 | |||
169 | |||
170 | |||
171 | ## Using Avatars in Client Applications | ||
172 | |||
173 | |||
174 | ### General recommendations | ||
175 | |||
176 | - Clients MUST NOT imply the availability of avatars in other users. | ||
177 | Avatars are an optional feature and not all users and clients may | ||
178 | support them; | ||
179 | |||
180 | - Clients MUST NOT block waiting for avatar information and avatar data | ||
181 | packets; | ||
182 | |||
183 | - Clients MUST treat avatar data as insecure and potentially malicious; | ||
184 | For example, users may accidentally use corrupted images as avatars, | ||
185 | a malicious user may send a specially crafted image to exploit a know | ||
186 | vulnerability in an image decoding library, etc. It is recommended to | ||
187 | handle the avatar image data in the same way as an image downloaded | ||
188 | from an unknown Internet source; | ||
189 | |||
190 | - The peers MUST NOT assume any coupling between the operations of | ||
191 | receiving an avatar information packet, sending unrequested avatar | ||
192 | information packets, requesting avatar data, or receiving avatar data. | ||
193 | |||
194 | For example, the following situations are valid: | ||
195 | |||
196 | * A text-mode client may send avatars to other users, but never | ||
197 | request them; | ||
198 | |||
199 | * A client may not understand a particular image format and ignore | ||
200 | avatars using it, but request and handle other formats; | ||
201 | |||
202 | - Clients SHOULD implement a local cache of avatars and do not request | ||
203 | avatar data from other peers unless necessary; | ||
204 | |||
205 | - When an avatar information is received, the client should delete the | ||
206 | avatar if the new avatar format is NONE or compare the hash received | ||
207 | from the peer with the hash of the currently cached avatar. If they | ||
208 | differ, send an avatar data request; | ||
209 | |||
210 | - If the cached avatar is older than a given threshold, the client may | ||
211 | also send an avatar info request to that friend once he is online and | ||
212 | mark the avatar as updated *before* any avatar information is received | ||
213 | (to not spam the peer with such requests); | ||
214 | |||
215 | - When an avatar data notification is received, the client must update | ||
216 | the cached avatar with the new one; | ||
217 | |||
218 | - Clients should resize or crop the image to the way it better adapts | ||
219 | to the client user interface; | ||
220 | |||
221 | - If the user already have an avatar defined in the client configuration, | ||
222 | it must be set before connecting to the network to avoid spurious avatar | ||
223 | change notifications and unnecessary data transfers. | ||
224 | |||
225 | - If no avatar data is available for a given friend, the client should | ||
226 | show a placeholder image. | ||
227 | |||
228 | |||
229 | |||
230 | ### Interoperability and sharing avatars among different clients | ||
231 | |||
232 | **This section is a tentative recommendation of how clients should store | ||
233 | avatars to ensure local interoperability and should be revised if this | ||
234 | code is accepted into Tox core.** | ||
235 | |||
236 | It is desirable that the user avatar and the cached friends avatars could be | ||
237 | shared among different Tox clients in the same system, in the spirit of the | ||
238 | proposed Single Tox Standard. This not only makes switching from one client | ||
239 | to another easier, but also minimizes the need of data transfers, as avatars | ||
240 | already downloaded by other clients can be reused. | ||
241 | |||
242 | Given the Tox data directory described in STS Draft v0.1.0: | ||
243 | |||
244 | - The user avatar is stored in a file named "avatar.png". As more formats | ||
245 | may be used in the future, another extensions are reserved and clients | ||
246 | should keep just one file named "avatar.*", with the data of the last | ||
247 | avatar set by the user. If the user have no avatar, no such files should | ||
248 | be kept in the data directory; | ||
249 | |||
250 | - Friends avatars are stored in a directory called "avatars" and named | ||
251 | as "xxxxx.png", where "xxxxx" is the complete client id encoded as an | ||
252 | uppercase hexadecimal string and "png" is the extension for the PNG | ||
253 | avatar. As new image formats may be used in the future, clients should | ||
254 | ensure no other file "xxxxx.*" exists. No file should be kept for an user | ||
255 | who have no avatar. | ||
256 | |||
257 | **To be discussed:** User keys are usually presented in Tox clients as | ||
258 | upper case strings, but lower case file names are more usual. | ||
259 | |||
260 | |||
261 | Example for Linux and other Unix systems, assuming an user called "gildor": | ||
262 | |||
263 | Tox data directory: /home/gildor/.config/tox/ | ||
264 | Tox data file: /home/gildor/.config/tox/data | ||
265 | Gildor's avatar: /home/gildor/.config/tox/avatar.png | ||
266 | Avatar data dir: /home/gildor/.config/tox/avatars/ | ||
267 | Elrond's avatar: /home/gildor/.config/tox/avatars/43656C65627269616E20646F6E277420546F782E426164206D656D6F72696573.png | ||
268 | Elladan's avatar: /home/gildor/.config/tox/avatars/49486174655768656E48756D616E735468696E6B49416D4D7942726F74686572.png | ||
269 | Elrohir's avatar /home/gildor/.config/tox/avatars/726568746F7242794D6D41496B6E696854736E616D75486E6568576574614849.png | ||
270 | Arwen's avatar: /home/gildor/.config/tox/avatars/53686520746F6F6B20476C6F7266696E64656C277320706C6163652068657265.png | ||
271 | Lindir's avatar: /home/gildor/.config/tox/avatars/417070735772697474656E42794D6F7274616C734C6F6F6B54686553616D652E.png | ||
272 | |||
273 | This recommendation is partially implemented by "testing/test_avatars.c". | ||
274 | |||
275 | |||
276 | |||
277 | |||
278 | |||
279 | ### Common operations | ||
280 | |||
281 | These are minimal examples of how perform common operations with avatar | ||
282 | functions. For a complete, working, example, see `testing/test_avatars.c`. | ||
283 | |||
284 | |||
285 | #### Setting an avatar for the current user | ||
286 | |||
287 | In this example `load_data_file` is just an hypothetical function that loads | ||
288 | data from a file into the buffer and sets the length accordingly. | ||
289 | |||
290 | uint8_t buf[TOX_MAX_AVATAR_DATA_LENGTH]; | ||
291 | uint32_t len; | ||
292 | |||
293 | if (load_data_file("avatar.png", buf, &len) == 0) | ||
294 | if (tox_set_avatar(tox, TOX_AVATARFORMAT_PNG, buf, len) != 0) | ||
295 | fprintf(stderr, "Failed to set avatar.\n"); | ||
296 | |||
297 | If the user is connected, this function will also notify all connected | ||
298 | friends about the avatar change. | ||
299 | |||
300 | If the user already have an avatar defined in the client configuration, it | ||
301 | must be set before connecting to the network to avoid spurious avatar change | ||
302 | notifications and unnecessary data transfers. | ||
303 | |||
304 | |||
305 | |||
306 | |||
307 | #### Removing the avatar from the current user | ||
308 | |||
309 | To remove an avatar, an application must set it to `TOX_AVATARFORMAT_NONE`. | ||
310 | |||
311 | tox_set_avatar(tox, TOX_AVATARFORMAT_NONE, NULL, 0); | ||
312 | |||
313 | If the user is connected, this function will also notify all connected | ||
314 | friends about the avatar change. | ||
315 | |||
316 | |||
317 | |||
318 | |||
319 | |||
320 | #### Receiving avatar information from friends | ||
321 | |||
322 | All avatar information is passed to a callback function with the prototype: | ||
323 | |||
324 | void function(Tox *tox, int32_t friendnumber, uint8_t format, | ||
325 | uint8_t *hash, uint8_t *data, uint32_t datalen, void *userdata) | ||
326 | |||
327 | As in this example: | ||
328 | |||
329 | static void avatar_info_cb(Tox *tox, int32_t friendnumber, uint8_t format, | ||
330 | uint8_t *hash, void *userdata) | ||
331 | { | ||
332 | printf("Receiving avatar information from friend %d. Format = %d\n", | ||
333 | friendnumber, format); | ||
334 | printf("Data hash: "); | ||
335 | hex_printf(hash, TOX_AVATAR_HASH_LENGTH); /* Hypothetical function */ | ||
336 | printf("\n"); | ||
337 | } | ||
338 | |||
339 | And, somewhere in the Tox initialization calls, set if as the callback to be | ||
340 | triggered when an avatar information event arrives: | ||
341 | |||
342 | tox_callback_avatar_info(tox, avatar_info_cb, NULL); | ||
343 | |||
344 | |||
345 | A typical client will test the currently cached avatar against the hash given | ||
346 | in the avatar information event and, if needed, request the avatar data. | ||
347 | |||
348 | |||
349 | |||
350 | #### Receiving avatar data from friends | ||
351 | |||
352 | Avatar data events are only delivered in reply of avatar data requests which | ||
353 | **should** only be sent after getting the user avatar information (format | ||
354 | and hash) from an avatar information event and checking it against a local | ||
355 | cache. | ||
356 | |||
357 | For this, an application must define an avatar information callback which | ||
358 | checks the local avatar cache and emits an avatar data request if necessary: | ||
359 | |||
360 | static void avatar_info_cb(Tox *tox, int32_t friendnumber, uint8_t format, | ||
361 | uint8_t *hash, void *userdata) | ||
362 | { | ||
363 | printf("Receiving avatar information from friend %d. Format = %d\n", | ||
364 | friendnumber, format); | ||
365 | if (format = TOX_AVATARFORMAT_NONE) { | ||
366 | /* User have no avatar or removed the avatar */ | ||
367 | delete_avatar_from_cache(tox, friendnumber); | ||
368 | } else { | ||
369 | /* Use the received hash to check if the cached avatar is | ||
370 | still updated. */ | ||
371 | if (!is_user_cached_avatar_updated(tox, friendnumber, hash)) { | ||
372 | /* User avatar is outdated, send data request */ | ||
373 | tox_request_avatar_data(tox, friendnumber); | ||
374 | } | ||
375 | } | ||
376 | } | ||
377 | |||
378 | |||
379 | Then define an avatar data callback to store the received data in the local | ||
380 | cache: | ||
381 | |||
382 | static void avatar_data_cb(Tox *tox, int32_t friendnumber, uint8_t format, | ||
383 | uint8_t *hash, uint8_t *data, uint32_t datalen, void *userdata) | ||
384 | { | ||
385 | if (format = TOX_AVATARFORMAT_NONE) { | ||
386 | /* User have no avatar or removed the avatar */ | ||
387 | delete_avatar_from_cache(tox, friendnumber); | ||
388 | } else { | ||
389 | save_avatar_data_to_cache(tox, friendnumber, format, hash, | ||
390 | data, datalen); | ||
391 | } | ||
392 | } | ||
393 | |||
394 | |||
395 | And, finally, register both callbacks somewhere in the Tox initialization | ||
396 | calls: | ||
397 | |||
398 | tox_callback_avatar_info(tox, avatar_info_cb, NULL); | ||
399 | tox_callback_avatar_data(tox, avatar_data_cb, NULL); | ||
400 | |||
401 | |||
402 | In the previous examples, implementation of the functions to check, store | ||
403 | and retrieve data from the cache were omitted for brevity. These functions | ||
404 | will also need to get the friend client ID (public key) from they friend | ||
405 | number and, usually, convert it from a byte string to a hexadecimal | ||
406 | string. A complete, yet more complex, example is available in the file | ||
407 | `testing/test_avatars.c`. | ||
408 | |||
409 | |||
410 | |||
411 | |||
412 | |||
413 | |||
414 | |||
415 | |||
416 | |||
417 | |||
418 | |||
419 | ## Internal Protocol Description | ||
420 | |||
421 | ### New packet types | ||
422 | |||
423 | The avatar transfer protocol adds the following new packet types and ids: | ||
424 | |||
425 | PACKET_ID_AVATAR_INFO_REQ = 52 | ||
426 | PACKET_ID_AVATAR_INFO = 53 | ||
427 | PACKET_ID_AVATAR_DATA_CONTROL = 54 | ||
428 | PACKET_ID_AVATAR_DATA_START = 55 | ||
429 | PACKET_ID_AVATAR_DATA_PUSH = 56 | ||
430 | |||
431 | |||
432 | |||
433 | |||
434 | ### Requesting avatar information | ||
435 | |||
436 | To request avatar information, an user must send a packet of type | ||
437 | `PACKET_ID_AVATAR_INFO_REQ`. This packet have no data fields. Upon | ||
438 | receiving this packet, a client which supports avatars should answer with | ||
439 | a `PACKET_ID_AVATAR_INFO`. The sender must accept that the friend may | ||
440 | not answer at all. | ||
441 | |||
442 | |||
443 | |||
444 | |||
445 | ### Receiving avatar information | ||
446 | |||
447 | Avatar information arrives in a packet of type `PACKET_ID_AVATAR_INFO` with | ||
448 | the following structure: | ||
449 | |||
450 | PACKET_ID_AVATAR_INFO (53) | ||
451 | Packet data size: 33 bytes | ||
452 | [1: uint8_t format][32: uint8_t hash] | ||
453 | |||
454 | Where 'format' is the image data format, one of the following: | ||
455 | |||
456 | 0 = AVATARFORMAT_NONE (no avatar set) | ||
457 | 1 = AVATARFORMAT_PNG | ||
458 | |||
459 | and 'hash' is the SHA-256 message digest of the avatar data. | ||
460 | |||
461 | This packet may be sent at any time and no previous request is required. | ||
462 | Clients should send this packet upon connection or when a friend | ||
463 | connects, in the same way Tox sends name, status and action information. | ||
464 | |||
465 | |||
466 | |||
467 | |||
468 | |||
469 | ### Requesting avatar data | ||
470 | |||
471 | Transmission of avatar data is a multi-step procedure using three new packet | ||
472 | types. | ||
473 | |||
474 | - Packet `PACKET_ID_AVATAR_DATA_CONTROL` have the format: | ||
475 | |||
476 | PACKET_ID_AVATAR_DATA_CONTROL (54) | ||
477 | Packet data size: 1 byte | ||
478 | [1: uint8_t op] | ||
479 | |||
480 | where 'op' is a code signaling both an operation request or a status | ||
481 | return, which semantics are explained bellow. The following values are | ||
482 | defined: | ||
483 | |||
484 | 0 = AVATARDATACONTROL_REQ | ||
485 | 1 = AVATARDATACONTROL_ERROR | ||
486 | |||
487 | |||
488 | - Packet `PACKET_ID_AVATAR_DATA_START` have the following format: | ||
489 | |||
490 | PACKET_ID_AVATAR_DATA_START (55) | ||
491 | Packet data size: 37 bytes | ||
492 | [1: uint8_t format][32: uint8_t hash][1: uint32_t data_length] | ||
493 | |||
494 | |||
495 | where 'format' is the image format, with the same values accepted for | ||
496 | the field 'format' in packet type `PACKET_ID_AVATAR_INFO`, 'hash' is | ||
497 | the SHA-256 cryptographic hash of the avatar raw data and 'data_length' | ||
498 | is the total number of bytes the raw avatar data. | ||
499 | |||
500 | |||
501 | - Packet `PACKET_ID_AVATAR_DATA_PUSH` have no format structure, just up | ||
502 | to `AVATAR_DATA_MAX_CHUNK_SIZE` bytes of raw avatar image data; this | ||
503 | value is defined according to the maximum amount of data a Tox crypted | ||
504 | packet can hold. | ||
505 | |||
506 | |||
507 | |||
508 | The following procedure assumes that a client "A" is requesting avatar data | ||
509 | from a client "B": | ||
510 | |||
511 | - "A" must initialize its control structures and mark its data transfer | ||
512 | as not yet started. Then it requests avatar data from "B" by sending a | ||
513 | packet `PACKET_ID_AVATAR_DATA_CONTROL` with 'op' set to | ||
514 | `AVATARDATACONTROL_REQ`. | ||
515 | |||
516 | - If "B" accepts this transfer, it answers by sending an | ||
517 | `PACKET_ID_AVATAR_DATA_START` with the fields 'format', 'hash' and | ||
518 | 'data_length' set to the respective values from the current avatar. | ||
519 | If "B" have no avatar set, 'format' must be `AVATARFORMAT_NONE`, 'hash' | ||
520 | must be zeroed and 'data_length' must be zero. | ||
521 | |||
522 | If "B" does not accept sending the avatar, it may send a packet | ||
523 | `PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to | ||
524 | `AVATARDATACONTROL_ERROR` or simply ignore this request. "A" must cope | ||
525 | with this. | ||
526 | |||
527 | If "B" have an avatar, it sends a variable number of | ||
528 | `PACKET_ID_AVATAR_DATA_PUSH` packets with the avatar data in a single | ||
529 | shot. | ||
530 | |||
531 | - Upon receiving a `PACKET_ID_AVATAR_DATA_START`, "A" checks if it | ||
532 | has sent a data request to "B". If not, just ignores the packet. | ||
533 | |||
534 | If "A" really requested avatar data and the format is `AVATARFORMAT_NONE`, | ||
535 | it triggers the avatar data callback, and clears all the temporary data, | ||
536 | finishing the process. For other formats, "A" just waits for packets | ||
537 | of type `PACKET_ID_AVATAR_DATA_PUSH`. | ||
538 | |||
539 | - Upon receiving a `PACKET_ID_AVATAR_DATA_PUSH`, "A" checks if it really | ||
540 | sent an avatar data request and if the `PACKET_ID_AVATAR_DATA_START` was | ||
541 | already received. If this conditions are valid, it checks if the total | ||
542 | length of the data already stored in the receiving buffer plus the data | ||
543 | present in the push packet is still less or equal than | ||
544 | `TOX_MAX_AVATAR_DATA_LENGTH`. If invalid, it replies with a | ||
545 | `PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' set to | ||
546 | `AVATARDATACONTROL_ERROR`. | ||
547 | |||
548 | If valid, "A" updates the 'bytes_received' counter and concatenates the | ||
549 | newly arrived data to the buffer. | ||
550 | |||
551 | The "A" checks if all the data was already received by comparing the | ||
552 | counter 'bytes_received' with the field 'total_length'. If they are | ||
553 | equal, "A" takes a SHA-256 hash of the data and compares it with the | ||
554 | hash stored in the field 'hash' received from the first | ||
555 | `PACKET_ID_AVATAR_DATA_START`. | ||
556 | |||
557 | If the hashes match, the avatar data was correctly received and "A" | ||
558 | triggers the avatar data callback, and clears all the temporary data, | ||
559 | finishing the process. | ||
560 | |||
561 | If not all data was received, "A" simply waits for more data. | ||
562 | |||
563 | Client "A" is always responsible for controlling the transfer and | ||
564 | validating the data received. "B" don't need to keep any state for the | ||
565 | protocol, have full control over the data sent and should implement | ||
566 | some transfer limit for the data it sends. | ||
567 | |||
568 | - Any peer receiving a `PACKET_ID_AVATAR_DATA_CONTROL` with the field 'op' | ||
569 | set to `AVATARDATACONTROL_ERROR` clears any existing control state and | ||
570 | finishes sending or receiving data. | ||
571 | |||
572 | |||
573 | |||
574 | |||
575 | |||
576 | ## Security considerations | ||
577 | |||
578 | The major security implication of background data transfers of large objects, | ||
579 | like avatars, is the possibility of exhausting the network resources from a | ||
580 | client. This problem is exacerbated when there is the possibility of an | ||
581 | amplification attack as happens, for example, when sending a very small | ||
582 | avatar request message will force the user to reply with a larger avatar | ||
583 | data message. | ||
584 | |||
585 | The present proposal mitigates this situation by: | ||
586 | |||
587 | - Only transferring data between previously authenticated friends; | ||
588 | |||
589 | - Enforcing strict limits on the avatar data size; | ||
590 | |||
591 | - Providing an alternate, smaller, message to cooperative users refresh | ||
592 | avatar information when nothing has changed (`PACKET_ID_AVATAR_INFO`); | ||
593 | |||
594 | - Having per-friend data transfer limit. As the current protocol still | ||
595 | allows an user to request an infinite data stream by asking the the | ||
596 | same offset of the avatar again and again, the implementation limits | ||
597 | the amount of data a single user can request for some time. For now, | ||
598 | the library will not allow an user to request more than | ||
599 | `10*TOX_MAX_AVATAR_DATA_LENGTH` in less than 20 minutes; | ||
600 | |||
601 | - Making the requester responsible for storing partial data and state | ||
602 | information; | ||
603 | |||
604 | Another problem present in the avatars is the possibility of a friend send | ||
605 | a maliciously crafted image intended to exploit vulnerabilities in image | ||
606 | decoders. Without an intermediate server to recompress and validate and | ||
607 | convert the images to neutral formats, the client applications must handle | ||
608 | this situation by themselves using stable and secure image libraries and | ||
609 | imposing limits on the maximum amount of system resources the decoding | ||
610 | process can take. Images coming from Tox friends must be treated in the same | ||
611 | way as images coming from random Internet sources. | ||
diff --git a/testing/Makefile.inc b/testing/Makefile.inc index 42d89e05..6bb87913 100644 --- a/testing/Makefile.inc +++ b/testing/Makefile.inc | |||
@@ -105,6 +105,21 @@ tox_shell_LDADD = $(LIBSODIUM_LDFLAGS) \ | |||
105 | $(NACL_LIBS) \ | 105 | $(NACL_LIBS) \ |
106 | -lutil | 106 | -lutil |
107 | 107 | ||
108 | |||
109 | noinst_PROGRAMS += test_avatars | ||
110 | |||
111 | test_avatars_SOURCES = ../testing/test_avatars.c | ||
112 | |||
113 | test_avatars_CFLAGS = $(LIBSODIUM_CFLAGS) \ | ||
114 | $(NACL_CFLAGS) | ||
115 | |||
116 | test_avatars_LDADD = $(LIBSODIUM_LDFLAGS) \ | ||
117 | $(NACL_LDFLAGS) \ | ||
118 | libtoxcore.la \ | ||
119 | $(LIBSODIUM_LIBS) \ | ||
120 | $(NACL_OBJECTS) \ | ||
121 | $(NACL_LIBS) | ||
122 | |||
108 | endif | 123 | endif |
109 | 124 | ||
110 | EXTRA_DIST += $(top_srcdir)/testing/misc_tools.c | 125 | EXTRA_DIST += $(top_srcdir)/testing/misc_tools.c |
diff --git a/testing/test_avatars.c b/testing/test_avatars.c new file mode 100644 index 00000000..7d96bd52 --- /dev/null +++ b/testing/test_avatars.c | |||
@@ -0,0 +1,770 @@ | |||
1 | /* | ||
2 | * A bot to test Tox avatars | ||
3 | * | ||
4 | * Usage: ./test_avatars <data dir> | ||
5 | * | ||
6 | * Connects to the Tox network, publishes our avatar, requests our friends | ||
7 | * avatars and, if available, saves them to a local cache. | ||
8 | * This bot automatically accepts any friend request. | ||
9 | * | ||
10 | * | ||
11 | * Data dir MUST have: | ||
12 | * | ||
13 | * - A file named "data" (named accordingly to STS Draft v0.1.0) with | ||
14 | * user id, friends, bootstrap data, etc. from a previously configured | ||
15 | * Tox session; use a client (eg. toxic) to configure it, add friends, | ||
16 | * etc. | ||
17 | * | ||
18 | * Data dir MAY have: | ||
19 | * | ||
20 | * - A file named avatar.png. If given, the bot will publish it. Otherwise, | ||
21 | * no avatar will be set. | ||
22 | * | ||
23 | * - A directory named "avatars" with the currently cached avatars. | ||
24 | * | ||
25 | * | ||
26 | * The bot will answer to these commands: | ||
27 | * | ||
28 | * !debug-on - Enable extended debug messages | ||
29 | * !debug-off - Disenable extended debug messages | ||
30 | * !set-avatar - Set our avatar to the contents of the file avatar.* | ||
31 | * !remove-avatar - Remove our avatar | ||
32 | * | ||
33 | */ | ||
34 | |||
35 | #define DATA_FILE_NAME "data" | ||
36 | #define AVATAR_DIR_NAME "avatars" | ||
37 | |||
38 | #ifdef HAVE_CONFIG_H | ||
39 | #include "config.h" | ||
40 | #endif | ||
41 | |||
42 | #include "../toxcore/tox.h" | ||
43 | |||
44 | #include <stdlib.h> | ||
45 | #include <stdio.h> | ||
46 | #include <string.h> | ||
47 | #include <unistd.h> | ||
48 | #include <time.h> | ||
49 | #include <stdbool.h> | ||
50 | #include <linux/limits.h> | ||
51 | #include <sys/stat.h> | ||
52 | #include <unistd.h> | ||
53 | #include <errno.h> | ||
54 | #include <stdarg.h> | ||
55 | |||
56 | |||
57 | |||
58 | /* Basic debug utils */ | ||
59 | |||
60 | #define DEBUG(format, ...) debug_printf("DEBUG: %s:%d %s: " format "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__) | ||
61 | |||
62 | static bool print_debug_msgs = true; | ||
63 | |||
64 | static void debug_printf(const char *fmt, ...) | ||
65 | { | ||
66 | if (print_debug_msgs == true) { | ||
67 | va_list ap; | ||
68 | va_start(ap, fmt); | ||
69 | vprintf(fmt, ap); | ||
70 | va_end(ap); | ||
71 | } | ||
72 | } | ||
73 | |||
74 | |||
75 | |||
76 | |||
77 | |||
78 | |||
79 | /* ------------ Avatar cache managenment functions ------------ */ | ||
80 | |||
81 | typedef struct { | ||
82 | uint8_t format; | ||
83 | char *suffix; | ||
84 | char *file_name; | ||
85 | } def_avatar_name_t; | ||
86 | |||
87 | static const def_avatar_name_t def_avatar_names[] = { | ||
88 | /* In order of preference */ | ||
89 | { TOX_AVATARFORMAT_PNG, "png", "avatar.png" }, | ||
90 | { TOX_AVATARFORMAT_NONE, NULL, NULL }, /* Must be the last one */ | ||
91 | }; | ||
92 | |||
93 | |||
94 | |||
95 | static void set_avatar(Tox *tox, const char *base_dir); | ||
96 | |||
97 | |||
98 | static char *get_avatar_suffix_from_format(uint8_t format) | ||
99 | { | ||
100 | int i; | ||
101 | |||
102 | for (i = 0; def_avatar_names[i].format != TOX_AVATARFORMAT_NONE; i++) | ||
103 | if (def_avatar_names[i].format == format) | ||
104 | return def_avatar_names[i].suffix; | ||
105 | |||
106 | return NULL; | ||
107 | } | ||
108 | |||
109 | |||
110 | /* Load avatar data from a file into a memory buffer 'buf'. | ||
111 | * buf must have at least TOX_MAX_AVATAR_DATA_LENGTH bytes | ||
112 | * Returns the length of the data sucess or < 0 on error | ||
113 | */ | ||
114 | static int load_avatar_data(char *fname, uint8_t *buf) | ||
115 | { | ||
116 | FILE *fp = fopen(fname, "rb"); | ||
117 | |||
118 | if (fp == NULL) | ||
119 | return -1; /* Error */ | ||
120 | |||
121 | size_t n = fread(buf, 1, TOX_MAX_AVATAR_DATA_LENGTH, fp); | ||
122 | int ret; | ||
123 | |||
124 | if (ferror(fp) != 0 || n == 0) | ||
125 | ret = -1; /* Error */ | ||
126 | else | ||
127 | ret = n; | ||
128 | |||
129 | fclose(fp); | ||
130 | return ret; | ||
131 | } | ||
132 | |||
133 | |||
134 | /* Save avatar data to a file */ | ||
135 | static int save_avatar_data(char *fname, uint8_t *data, uint32_t len) | ||
136 | { | ||
137 | FILE *fp = fopen(fname, "wb"); | ||
138 | |||
139 | if (fp == NULL) | ||
140 | return -1; /* Error */ | ||
141 | |||
142 | int ret = 0; /* Ok */ | ||
143 | |||
144 | if (fwrite(data, 1, len, fp) != len) | ||
145 | ret = -1; /* Error */ | ||
146 | |||
147 | if (fclose(fp) != 0) | ||
148 | ret = -1; /* Error */ | ||
149 | |||
150 | return ret; | ||
151 | } | ||
152 | |||
153 | |||
154 | static void byte_to_hex_str(const uint8_t *buf, const size_t buflen, char *dst) | ||
155 | { | ||
156 | const char *hex_chars = "0123456789ABCDEF"; | ||
157 | size_t i = 0; | ||
158 | size_t j = 0; | ||
159 | |||
160 | while (i < buflen) { | ||
161 | dst[j++] = hex_chars[(buf[i] >> 4) & 0xf]; | ||
162 | dst[j++] = hex_chars[buf[i] & 0xf]; | ||
163 | i++; | ||
164 | } | ||
165 | |||
166 | dst[j++] = '\0'; | ||
167 | } | ||
168 | |||
169 | /* Make the cache file name for a avatar of the given format for the given | ||
170 | * client id. | ||
171 | */ | ||
172 | static int make_avatar_file_name(char *dst, size_t dst_len, | ||
173 | char *base_dir, uint8_t format, uint8_t *client_id) | ||
174 | { | ||
175 | char client_id_str[2 * TOX_CLIENT_ID_SIZE + 1]; | ||
176 | byte_to_hex_str(client_id, TOX_CLIENT_ID_SIZE, client_id_str); | ||
177 | |||
178 | const char *suffix = get_avatar_suffix_from_format(format); | ||
179 | |||
180 | if (suffix == NULL) | ||
181 | return -1; /* Error */ | ||
182 | |||
183 | int n = snprintf(dst, dst_len, "%s/%s/%s.%s", base_dir, AVATAR_DIR_NAME, | ||
184 | client_id_str, suffix); | ||
185 | dst[dst_len - 1] = '\0'; | ||
186 | |||
187 | if (n >= dst_len) | ||
188 | return -1; /* Error: Output truncated */ | ||
189 | |||
190 | return 0; /* Ok */ | ||
191 | } | ||
192 | |||
193 | |||
194 | /* Load a cached avatar into the buffer 'data' (which must be at least | ||
195 | * TOX_MAX_AVATAR_DATA_LENGTH bytes long). Gets the file name from client | ||
196 | * id and the given data format. | ||
197 | * Returns 0 on success, or -1 on error. | ||
198 | */ | ||
199 | static int load_user_avatar(Tox *tox, char *base_dir, int friendnum, | ||
200 | uint8_t format, uint8_t *hash, uint8_t *data, uint32_t *datalen) | ||
201 | { | ||
202 | uint8_t addr[TOX_CLIENT_ID_SIZE]; | ||
203 | |||
204 | if (tox_get_client_id(tox, friendnum, addr) != 0) { | ||
205 | DEBUG("Bad client id, friendnumber=%d", friendnum); | ||
206 | return -1; | ||
207 | } | ||
208 | |||
209 | char path[PATH_MAX]; | ||
210 | int ret = make_avatar_file_name(path, sizeof(path), base_dir, format, addr); | ||
211 | |||
212 | if (ret != 0) { | ||
213 | DEBUG("Can't create an file name for this user/avatar."); | ||
214 | return -1; | ||
215 | } | ||
216 | |||
217 | ret = load_avatar_data(path, data); | ||
218 | |||
219 | if (ret < 0) { | ||
220 | DEBUG("Failed to load avatar data."); | ||
221 | return -1; | ||
222 | } | ||
223 | |||
224 | *datalen = ret; | ||
225 | tox_avatar_hash(tox, hash, data, *datalen); | ||
226 | |||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | /* Save a user avatar into the cache. Gets the file name from client id and | ||
231 | * the given data format. | ||
232 | * Returns 0 on success, or -1 on error. | ||
233 | */ | ||
234 | static int save_user_avatar(Tox *tox, char *base_dir, int friendnum, | ||
235 | uint8_t format, uint8_t *data, uint32_t datalen) | ||
236 | { | ||
237 | uint8_t addr[TOX_CLIENT_ID_SIZE]; | ||
238 | |||
239 | if (tox_get_client_id(tox, friendnum, addr) != 0) { | ||
240 | DEBUG("Bad client id, friendnumber=%d", friendnum); | ||
241 | return -1; | ||
242 | } | ||
243 | |||
244 | char path[PATH_MAX]; | ||
245 | int ret = make_avatar_file_name(path, sizeof(path), base_dir, format, addr); | ||
246 | |||
247 | if (ret != 0) { | ||
248 | DEBUG("Can't create a file name for this user/avatar"); | ||
249 | return -1; | ||
250 | } | ||
251 | |||
252 | return save_avatar_data(path, data, datalen); | ||
253 | } | ||
254 | |||
255 | /* Delete all cached avatars for a given user */ | ||
256 | static int delete_user_avatar(Tox *tox, char *base_dir, int friendnum) | ||
257 | { | ||
258 | uint8_t addr[TOX_CLIENT_ID_SIZE]; | ||
259 | |||
260 | if (tox_get_client_id(tox, friendnum, addr) != 0) { | ||
261 | DEBUG("Bad client id, friendnumber=%d", friendnum); | ||
262 | return -1; | ||
263 | } | ||
264 | |||
265 | char path[PATH_MAX]; | ||
266 | |||
267 | /* This iteration is dumb and inefficient */ | ||
268 | int i; | ||
269 | |||
270 | for (i = 0; def_avatar_names[i].format != TOX_AVATARFORMAT_NONE; i++) { | ||
271 | int ret = make_avatar_file_name(path, sizeof(path), base_dir, | ||
272 | def_avatar_names[i].format, addr); | ||
273 | |||
274 | if (ret != 0) { | ||
275 | DEBUG("Failed to create avatar path for friend #%d, format %d\n", | ||
276 | friendnum, def_avatar_names[i].format); | ||
277 | continue; | ||
278 | } | ||
279 | |||
280 | if (unlink(path) == 0) | ||
281 | printf("Avatar file %s deleted.\n", path); | ||
282 | } | ||
283 | |||
284 | return 0; | ||
285 | } | ||
286 | |||
287 | |||
288 | |||
289 | |||
290 | /* ------------ Protocol callbacks ------------ */ | ||
291 | |||
292 | static void friend_status_cb(Tox *tox, int n, uint8_t status, void *ud) | ||
293 | { | ||
294 | uint8_t addr[TOX_CLIENT_ID_SIZE]; | ||
295 | char addr_str[2 * TOX_CLIENT_ID_SIZE + 1]; | ||
296 | |||
297 | if (tox_get_client_id(tox, n, addr) == 0) { | ||
298 | byte_to_hex_str(addr, TOX_CLIENT_ID_SIZE, addr_str); | ||
299 | printf("Receiving status from %s: %u\n", addr_str, status); | ||
300 | } | ||
301 | } | ||
302 | |||
303 | static void friend_avatar_info_cb(Tox *tox, int32_t n, uint8_t format, uint8_t *hash, void *ud) | ||
304 | { | ||
305 | char *base_dir = (char *) ud; | ||
306 | uint8_t addr[TOX_CLIENT_ID_SIZE]; | ||
307 | char addr_str[2 * TOX_CLIENT_ID_SIZE + 1]; | ||
308 | char hash_str[2 * TOX_AVATAR_HASH_LENGTH + 1]; | ||
309 | |||
310 | if (tox_get_client_id(tox, n, addr) == 0) { | ||
311 | byte_to_hex_str(addr, TOX_CLIENT_ID_SIZE, addr_str); | ||
312 | printf("Receiving avatar information from %s.\n", addr_str); | ||
313 | } else { | ||
314 | DEBUG("tox_get_client_id failed"); | ||
315 | printf("Receiving avatar information from friend number %u.\n", n); | ||
316 | } | ||
317 | |||
318 | byte_to_hex_str(hash, TOX_AVATAR_HASH_LENGTH, hash_str); | ||
319 | DEBUG("format=%u, hash=%s", format, hash_str); | ||
320 | |||
321 | if (format == TOX_AVATARFORMAT_NONE) { | ||
322 | printf(" -> User do not have an avatar.\n"); | ||
323 | /* User have no avatar anymore, delete it from our cache */ | ||
324 | delete_user_avatar(tox, base_dir, n); | ||
325 | } else { | ||
326 | /* Check the hash of the currently cached user avatar | ||
327 | * WARNING: THIS IS ONLY AN EXAMPLE! | ||
328 | * | ||
329 | * Real clients should keep the hashes in memory (eg. in the object | ||
330 | * used to represent a friend in the friend list) and do not access | ||
331 | * the file system or do anything resource intensive in reply of | ||
332 | * these events. | ||
333 | */ | ||
334 | uint32_t cur_av_len; | ||
335 | uint8_t cur_av_data[TOX_MAX_AVATAR_DATA_LENGTH]; | ||
336 | uint8_t cur_av_hash[TOX_AVATAR_HASH_LENGTH]; | ||
337 | int ret; | ||
338 | |||
339 | ret = load_user_avatar(tox, base_dir, n, format, cur_av_hash, cur_av_data, &cur_av_len); | ||
340 | |||
341 | if (ret != 0 | ||
342 | && memcpy(cur_av_hash, hash, TOX_AVATAR_HASH_LENGTH) != 0) { | ||
343 | printf(" -> Cached avatar is outdated. Requesting avatar data.\n"); | ||
344 | tox_request_avatar_data(tox, n); | ||
345 | } else { | ||
346 | printf(" -> Cached avatar is still updated.\n"); | ||
347 | } | ||
348 | } | ||
349 | |||
350 | } | ||
351 | |||
352 | static void friend_avatar_data_cb(Tox *tox, int32_t n, uint8_t format, | ||
353 | uint8_t *hash, uint8_t *data, uint32_t datalen, void *ud) | ||
354 | { | ||
355 | char *base_dir = (char *) ud; | ||
356 | uint8_t addr[TOX_CLIENT_ID_SIZE]; | ||
357 | char addr_str[2 * TOX_CLIENT_ID_SIZE + 1]; | ||
358 | char hash_str[2 * TOX_AVATAR_HASH_LENGTH + 1]; | ||
359 | |||
360 | if (tox_get_client_id(tox, n, addr) == 0) { | ||
361 | byte_to_hex_str(addr, TOX_CLIENT_ID_SIZE, addr_str); | ||
362 | printf("Receiving avatar data from %s.\n", addr_str); | ||
363 | } else { | ||
364 | DEBUG("tox_get_client_id failed"); | ||
365 | printf("Receiving avatar data from friend number %u.\n", n); | ||
366 | } | ||
367 | |||
368 | byte_to_hex_str(hash, TOX_AVATAR_HASH_LENGTH, hash_str); | ||
369 | DEBUG("format=%u, datalen=%d, hash=%s\n", format, datalen, hash_str); | ||
370 | |||
371 | delete_user_avatar(tox, base_dir, n); | ||
372 | |||
373 | if (format != TOX_AVATARFORMAT_NONE) { | ||
374 | int ret = save_user_avatar(tox, base_dir, n, format, data, datalen); | ||
375 | |||
376 | if (ret == 0) | ||
377 | printf(" -> Avatar updated in the cache.\n"); | ||
378 | else | ||
379 | printf(" -> Failed to save user avatar.\n"); | ||
380 | } | ||
381 | } | ||
382 | |||
383 | |||
384 | static void friend_msg_cb(Tox *tox, int n, const uint8_t *msg, uint16_t len, void *ud) | ||
385 | { | ||
386 | const char *base_dir = (char *) ud; | ||
387 | const char *msg_str = (char *) msg; | ||
388 | uint8_t addr[TOX_CLIENT_ID_SIZE]; | ||
389 | char addr_str[2 * TOX_CLIENT_ID_SIZE + 1]; | ||
390 | |||
391 | if (tox_get_client_id(tox, n, addr) == 0) { | ||
392 | byte_to_hex_str(addr, TOX_FRIEND_ADDRESS_SIZE, addr_str); | ||
393 | printf("Receiving message from %s:\n %s\n", addr_str, msg); | ||
394 | } | ||
395 | |||
396 | /* Handle bot commands for the tests */ | ||
397 | char *reply_ptr = NULL; | ||
398 | |||
399 | if (strstr(msg_str, "!debug-on") != NULL) { | ||
400 | print_debug_msgs = true; | ||
401 | reply_ptr = "Debug enabled."; | ||
402 | } else if (strstr(msg_str, "!debug-off") != NULL) { | ||
403 | print_debug_msgs = false; | ||
404 | reply_ptr = "Debug disabled."; | ||
405 | } else if (strstr(msg_str, "!set-avatar") != NULL) { | ||
406 | set_avatar(tox, base_dir); | ||
407 | reply_ptr = "Setting image avatar"; | ||
408 | } else if (strstr(msg_str, "!remove-avatar") != NULL) { | ||
409 | int r = tox_set_avatar(tox, TOX_AVATARFORMAT_NONE, NULL, 0); | ||
410 | DEBUG("tox_set_avatar returned %d", r); | ||
411 | reply_ptr = "Removing avatar"; | ||
412 | } | ||
413 | |||
414 | /* Add more useful commands here: add friend, etc. */ | ||
415 | |||
416 | char reply[TOX_MAX_MESSAGE_LENGTH]; | ||
417 | int reply_len; | ||
418 | |||
419 | if (reply_ptr) | ||
420 | reply_len = snprintf(reply, sizeof(reply), "%s", reply_ptr); | ||
421 | else | ||
422 | reply_len = snprintf(reply, sizeof(reply), | ||
423 | "No command found in message: %s", msg); | ||
424 | |||
425 | reply[sizeof(reply) - 1] = '\0'; | ||
426 | printf(" -> Reply: %s\n", reply); | ||
427 | tox_send_message(tox, n, (uint8_t *) reply, reply_len); | ||
428 | } | ||
429 | |||
430 | |||
431 | static void friend_request_cb(Tox *tox, const uint8_t *public_key, | ||
432 | const uint8_t *data, uint16_t length, void *ud) | ||
433 | { | ||
434 | char addr_str[2 * TOX_CLIENT_ID_SIZE + 1]; | ||
435 | byte_to_hex_str(public_key, TOX_CLIENT_ID_SIZE, addr_str); | ||
436 | printf("Accepting friend request from %s.\n %s\n", addr_str, data); | ||
437 | tox_add_friend_norequest(tox, public_key); | ||
438 | } | ||
439 | |||
440 | |||
441 | static int try_avatar_file(Tox *tox, const char *base_dir, const def_avatar_name_t *an) | ||
442 | { | ||
443 | char path[PATH_MAX]; | ||
444 | int n = snprintf(path, sizeof(path), "%s/%s", base_dir, an->file_name); | ||
445 | path[sizeof(path) - 1] = '\0'; | ||
446 | |||
447 | if (n >= sizeof(path)) { | ||
448 | DEBUG("error: path %s too big\n", path); | ||
449 | return -1; | ||
450 | } | ||
451 | |||
452 | DEBUG("trying file %s: ", path); | ||
453 | FILE *fp = fopen(path, "rb"); | ||
454 | |||
455 | if (fp != NULL) { | ||
456 | uint8_t buf[2 * TOX_MAX_AVATAR_DATA_LENGTH]; | ||
457 | int len = fread(buf, 1, sizeof(buf), fp); | ||
458 | |||
459 | if (len >= 0 && len <= TOX_MAX_AVATAR_DATA_LENGTH) { | ||
460 | int r = tox_set_avatar(tox, an->format, buf, len); | ||
461 | DEBUG("%d bytes, tox_set_avatar returned=%d", len, r); | ||
462 | |||
463 | if (r == 0) | ||
464 | printf("Setting avatar file %s\n", path); | ||
465 | else | ||
466 | printf("Error setting avatar file %s\n", path); | ||
467 | } else if (len < 0) { | ||
468 | DEBUG("read error %d", len); | ||
469 | } else { | ||
470 | printf("Avatar file %s if too big (more than %d bytes)", | ||
471 | path, TOX_MAX_AVATAR_DATA_LENGTH); | ||
472 | } | ||
473 | |||
474 | fclose(fp); | ||
475 | return 0; | ||
476 | } else { | ||
477 | DEBUG("File %s not found", path); | ||
478 | } | ||
479 | |||
480 | return -1; | ||
481 | } | ||
482 | |||
483 | |||
484 | static void set_avatar(Tox *tox, const char *base_dir) | ||
485 | { | ||
486 | int i; | ||
487 | |||
488 | for (i = 0; i < 4; i++) { | ||
489 | if (def_avatar_names[i].format == TOX_AVATARFORMAT_NONE) { | ||
490 | tox_set_avatar(tox, TOX_AVATARFORMAT_NONE, NULL, 0); | ||
491 | printf("No avatar file found, setting to NONE.\n"); | ||
492 | return; | ||
493 | } else { | ||
494 | if (try_avatar_file(tox, base_dir, &def_avatar_names[i]) == 0) | ||
495 | return; | ||
496 | } | ||
497 | } | ||
498 | |||
499 | /* Should be unreachable */ | ||
500 | printf("UNEXPECTED CODE PATH\n"); | ||
501 | } | ||
502 | |||
503 | |||
504 | static void print_avatar_info(Tox *tox) | ||
505 | { | ||
506 | uint8_t format; | ||
507 | uint8_t data[TOX_MAX_AVATAR_DATA_LENGTH]; | ||
508 | uint8_t hash[TOX_AVATAR_HASH_LENGTH]; | ||
509 | uint32_t data_length; | ||
510 | char hash_str[2 * TOX_AVATAR_HASH_LENGTH + 1]; | ||
511 | |||
512 | int ret = tox_get_self_avatar(tox, &format, data, &data_length, sizeof(data), hash); | ||
513 | DEBUG("tox_get_self_avatar returned %d", ret); | ||
514 | DEBUG("format: %d, data_length: %d", format, data_length); | ||
515 | byte_to_hex_str(hash, TOX_AVATAR_HASH_LENGTH, hash_str); | ||
516 | DEBUG("hash: %s", hash_str); | ||
517 | } | ||
518 | |||
519 | |||
520 | /* ------------ Initialization functions ------------ */ | ||
521 | |||
522 | /* Create directory to store tha avatars. Returns 0 if it was sucessfuly | ||
523 | * created or already existed. Returns -1 on error. | ||
524 | */ | ||
525 | static int create_avatar_diretory(const char *base_dir) | ||
526 | { | ||
527 | char path[PATH_MAX]; | ||
528 | int n = snprintf(path, sizeof(path), "%s/%s", base_dir, AVATAR_DIR_NAME); | ||
529 | path[sizeof(path) - 1] = '\0'; | ||
530 | |||
531 | if (n >= sizeof(path)) | ||
532 | return -1; | ||
533 | |||
534 | if (mkdir(path, 0755) == 0) { | ||
535 | return 0; /* Done */ | ||
536 | } else if (errno == EEXIST) { | ||
537 | /* Check if the existing path is a directory */ | ||
538 | struct stat st; | ||
539 | |||
540 | if (stat(path, &st) != 0) { | ||
541 | perror("stat()ing avatar directory"); | ||
542 | return -1; | ||
543 | } | ||
544 | |||
545 | if (S_ISDIR(st.st_mode)) | ||
546 | return 0; | ||
547 | } | ||
548 | |||
549 | return -1; /* Error */ | ||
550 | } | ||
551 | |||
552 | |||
553 | static void *load_bootstrap_data(const char *base_dir, uint32_t *len) | ||
554 | { | ||
555 | char path[PATH_MAX]; | ||
556 | int n = snprintf(path, sizeof(path), "%s/%s", base_dir, DATA_FILE_NAME); | ||
557 | path[sizeof(path) - 1] = '\0'; | ||
558 | |||
559 | if (n >= sizeof(path)) { | ||
560 | printf("Load error: path %s too long\n", path); | ||
561 | return NULL; | ||
562 | } | ||
563 | |||
564 | /* We should be using POSIX functions here, but let's try to be | ||
565 | * compatible with Windows. | ||
566 | */ | ||
567 | |||
568 | FILE *fp = fopen(path, "rb"); | ||
569 | |||
570 | if (fp == NULL) { | ||
571 | printf("fatal error: file %s not found.\n", path); | ||
572 | return NULL; | ||
573 | } | ||
574 | |||
575 | if (fseek(fp, 0, SEEK_END) != 0) { | ||
576 | printf("seek fail\n"); | ||
577 | fclose(fp); | ||
578 | return NULL; | ||
579 | } | ||
580 | |||
581 | int32_t flen = ftell(fp); | ||
582 | |||
583 | if (flen < 8 || flen > 2e6) { | ||
584 | printf("Fatal error: file %s have %u bytes. Out of acceptable range.\n", path, flen); | ||
585 | fclose(fp); | ||
586 | return NULL; | ||
587 | } | ||
588 | |||
589 | if (fseek(fp, 0, SEEK_SET) != 0) { | ||
590 | printf("seek fail\n"); | ||
591 | fclose(fp); | ||
592 | return NULL; | ||
593 | } | ||
594 | |||
595 | void *buf = malloc(flen); | ||
596 | |||
597 | if (buf == NULL) { | ||
598 | printf("malloc failed, %u bytes", flen); | ||
599 | fclose(fp); | ||
600 | return NULL; | ||
601 | } | ||
602 | |||
603 | *len = fread(buf, 1, flen, fp); | ||
604 | fclose(fp); | ||
605 | |||
606 | if (*len != flen) { | ||
607 | printf("fatal: %s have %u bytes, read only %u\n", path, flen, *len); | ||
608 | free(buf); | ||
609 | return NULL; | ||
610 | } | ||
611 | |||
612 | printf("bootstrap data loaded from %s (%u bytes)\n", path, flen); | ||
613 | return buf; | ||
614 | } | ||
615 | |||
616 | static int save_bootstrap_data(Tox *tox, const char *base_dir) | ||
617 | { | ||
618 | char path[PATH_MAX]; | ||
619 | int n = snprintf(path, sizeof(path), "%s/%s", base_dir, DATA_FILE_NAME); | ||
620 | path[sizeof(path) - 1] = '\0'; | ||
621 | |||
622 | if (n >= sizeof(path)) { | ||
623 | printf("Save error: path %s too long\n", path); | ||
624 | return -1; | ||
625 | } | ||
626 | |||
627 | char path_tmp[PATH_MAX]; | ||
628 | n = snprintf(path_tmp, sizeof(path_tmp), "%s.tmp", path); | ||
629 | path_tmp[sizeof(path_tmp) - 1] = '\0'; | ||
630 | |||
631 | if (n >= sizeof(path_tmp)) { | ||
632 | printf("error: path %s too long\n", path); | ||
633 | return -1; | ||
634 | } | ||
635 | |||
636 | uint32_t len = tox_size(tox); | ||
637 | |||
638 | if (len < 8 || len > 2e6) { | ||
639 | printf("save data length == %u, out of acceptable range\n", len); | ||
640 | return -1; | ||
641 | } | ||
642 | |||
643 | void *buf = malloc(len); | ||
644 | |||
645 | if (buf == NULL) { | ||
646 | printf("save data: malloc failed\n"); | ||
647 | return -1; | ||
648 | } | ||
649 | |||
650 | tox_save(tox, buf); | ||
651 | |||
652 | FILE *fp = fopen(path_tmp, "wb"); | ||
653 | |||
654 | if (fp == NULL) { | ||
655 | printf("Error saving data: can't open %s\n", path_tmp); | ||
656 | free(buf); | ||
657 | return -1; | ||
658 | } | ||
659 | |||
660 | if (fwrite(buf, 1, len, fp) != len) { | ||
661 | printf("Error writing data to %s\n", path_tmp); | ||
662 | free(buf); | ||
663 | fclose(fp); | ||
664 | return -1; | ||
665 | } | ||
666 | |||
667 | free(buf); | ||
668 | |||
669 | if (fclose(fp) != 0) { | ||
670 | printf("Error writing data to %s\n", path_tmp); | ||
671 | return -1; | ||
672 | } | ||
673 | |||
674 | if (rename(path_tmp, path) != 0) { | ||
675 | printf("Error renaming %s to %s\n", path_tmp, path); | ||
676 | return -1; | ||
677 | } | ||
678 | |||
679 | printf("Bootstrap data saved to %s\n", path); | ||
680 | return 0; /* Done */ | ||
681 | } | ||
682 | |||
683 | |||
684 | |||
685 | |||
686 | int main(int argc, char *argv[]) | ||
687 | { | ||
688 | int ret; | ||
689 | |||
690 | if (argc != 2) { | ||
691 | printf("usage: %s <data dir>\n", argv[0]); | ||
692 | return 1; | ||
693 | } | ||
694 | |||
695 | char *base_dir = argv[1]; | ||
696 | |||
697 | if (create_avatar_diretory(base_dir) != 0) | ||
698 | printf("Error creating avatar directory.\n"); | ||
699 | |||
700 | Tox *tox = tox_new(NULL); | ||
701 | |||
702 | uint32_t len; | ||
703 | void *data = load_bootstrap_data(base_dir, &len); | ||
704 | |||
705 | if (data == NULL) | ||
706 | return 1; | ||
707 | |||
708 | ret = tox_load(tox, data, len); | ||
709 | free(data); | ||
710 | |||
711 | if (ret == 0) { | ||
712 | printf("Tox initialized\n"); | ||
713 | } else { | ||
714 | printf("Fatal: tox_load returned %d\n", ret); | ||
715 | return 1; | ||
716 | } | ||
717 | |||
718 | tox_callback_connection_status(tox, friend_status_cb, NULL); | ||
719 | tox_callback_friend_message(tox, friend_msg_cb, base_dir); | ||
720 | tox_callback_friend_request(tox, friend_request_cb, NULL); | ||
721 | tox_callback_avatar_info(tox, friend_avatar_info_cb, base_dir); | ||
722 | tox_callback_avatar_data(tox, friend_avatar_data_cb, base_dir); | ||
723 | |||
724 | uint8_t addr[TOX_FRIEND_ADDRESS_SIZE]; | ||
725 | char addr_str[2 * TOX_FRIEND_ADDRESS_SIZE + 1]; | ||
726 | tox_get_address(tox, addr); | ||
727 | byte_to_hex_str(addr, TOX_FRIEND_ADDRESS_SIZE, addr_str); | ||
728 | printf("Using local tox address: %s\n", addr_str); | ||
729 | |||
730 | #ifdef TEST_SET_RESET_AVATAR | ||
731 | printf("Printing default avatar information:\n"); | ||
732 | print_avatar_info(tox); | ||
733 | |||
734 | printf("Setting a new avatar:\n"); | ||
735 | set_avatar(tox, base_dir); | ||
736 | print_avatar_info(tox); | ||
737 | |||
738 | printf("Removing the avatar we just set:\n"); | ||
739 | tox_avatar(tox, TOX_AVATARFORMAT_NONE, NULL, 0); | ||
740 | print_avatar_info(tox); | ||
741 | |||
742 | printf("Setting that avatar again:\n"); | ||
743 | #endif /* TEST_SET_RESET_AVATAR */ | ||
744 | |||
745 | set_avatar(tox, base_dir); | ||
746 | print_avatar_info(tox); | ||
747 | |||
748 | bool waiting = true; | ||
749 | time_t last_save = time(0); | ||
750 | |||
751 | while (1) { | ||
752 | if (tox_isconnected(tox) && waiting) { | ||
753 | printf("DHT connected.\n"); | ||
754 | waiting = false; | ||
755 | } | ||
756 | |||
757 | tox_do(tox); | ||
758 | |||
759 | time_t now = time(0); | ||
760 | |||
761 | if (now - last_save > 120) { | ||
762 | save_bootstrap_data(tox, base_dir); | ||
763 | last_save = now; | ||
764 | } | ||
765 | |||
766 | usleep(500000); | ||
767 | } | ||
768 | |||
769 | return 0; | ||
770 | } | ||
diff --git a/toxcore/Messenger.c b/toxcore/Messenger.c index 21cb2671..108159a3 100644 --- a/toxcore/Messenger.c +++ b/toxcore/Messenger.c | |||
@@ -42,6 +42,7 @@ | |||
42 | static void set_friend_status(Messenger *m, int32_t friendnumber, uint8_t status); | 42 | static void set_friend_status(Messenger *m, int32_t friendnumber, uint8_t status); |
43 | static int write_cryptpacket_id(const Messenger *m, int32_t friendnumber, uint8_t packet_id, const uint8_t *data, | 43 | static int write_cryptpacket_id(const Messenger *m, int32_t friendnumber, uint8_t packet_id, const uint8_t *data, |
44 | uint32_t length, uint8_t congestion_control); | 44 | uint32_t length, uint8_t congestion_control); |
45 | static int send_avatar_data_control(const Messenger *m, const uint32_t friendnumber, uint8_t op); | ||
45 | 46 | ||
46 | // friend_not_valid determines if the friendnumber passed is valid in the Messenger object | 47 | // friend_not_valid determines if the friendnumber passed is valid in the Messenger object |
47 | static uint8_t friend_not_valid(const Messenger *m, int32_t friendnumber) | 48 | 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 | |||
247 | m->friendlist[i].statusmessage = calloc(1, 1); | 248 | m->friendlist[i].statusmessage = calloc(1, 1); |
248 | m->friendlist[i].statusmessage_length = 1; | 249 | m->friendlist[i].statusmessage_length = 1; |
249 | m->friendlist[i].userstatus = USERSTATUS_NONE; | 250 | m->friendlist[i].userstatus = USERSTATUS_NONE; |
251 | m->friendlist[i].avatar_info_sent = 0; | ||
252 | m->friendlist[i].avatar_recv_data = NULL; | ||
253 | m->friendlist[i].avatar_send_data.bytes_sent = 0; | ||
254 | m->friendlist[i].avatar_send_data.last_reset = 0; | ||
250 | m->friendlist[i].is_typing = 0; | 255 | m->friendlist[i].is_typing = 0; |
251 | memcpy(m->friendlist[i].info, data, length); | 256 | memcpy(m->friendlist[i].info, data, length); |
252 | m->friendlist[i].info_size = length; | 257 | m->friendlist[i].info_size = length; |
@@ -330,6 +335,7 @@ int m_delfriend(Messenger *m, int32_t friendnumber) | |||
330 | onion_delfriend(m->onion_c, m->friendlist[friendnumber].onion_friendnum); | 335 | onion_delfriend(m->onion_c, m->friendlist[friendnumber].onion_friendnum); |
331 | crypto_kill(m->net_crypto, m->friendlist[friendnumber].crypt_connection_id); | 336 | crypto_kill(m->net_crypto, m->friendlist[friendnumber].crypt_connection_id); |
332 | free(m->friendlist[friendnumber].statusmessage); | 337 | free(m->friendlist[friendnumber].statusmessage); |
338 | free(m->friendlist[friendnumber].avatar_recv_data); | ||
333 | remove_request_received(&(m->fr), m->friendlist[friendnumber].client_id); | 339 | remove_request_received(&(m->fr), m->friendlist[friendnumber].client_id); |
334 | memset(&(m->friendlist[friendnumber]), 0, sizeof(Friend)); | 340 | memset(&(m->friendlist[friendnumber]), 0, sizeof(Friend)); |
335 | uint32_t i; | 341 | uint32_t i; |
@@ -564,6 +570,134 @@ int m_set_userstatus(Messenger *m, uint8_t status) | |||
564 | return 0; | 570 | return 0; |
565 | } | 571 | } |
566 | 572 | ||
573 | int m_set_avatar(Messenger *m, uint8_t format, const uint8_t *data, uint32_t length) | ||
574 | { | ||
575 | if (length > MAX_AVATAR_DATA_LENGTH) | ||
576 | return -1; | ||
577 | |||
578 | if (format == AVATARFORMAT_NONE) { | ||
579 | free(m->avatar_data); | ||
580 | m->avatar_data = NULL; | ||
581 | m->avatar_data_length = 0; | ||
582 | m->avatar_format = format; | ||
583 | memset(m->avatar_hash, 0, AVATAR_HASH_LENGTH); | ||
584 | } else { | ||
585 | if (length == 0 || data == NULL) | ||
586 | return -1; | ||
587 | |||
588 | uint8_t *tmp = realloc(m->avatar_data, length); | ||
589 | |||
590 | if (tmp == NULL) | ||
591 | return -1; | ||
592 | |||
593 | m->avatar_format = format; | ||
594 | m->avatar_data = tmp; | ||
595 | m->avatar_data_length = length; | ||
596 | memcpy(m->avatar_data, data, length); | ||
597 | |||
598 | m_avatar_hash(m->avatar_hash, m->avatar_data, m->avatar_data_length); | ||
599 | } | ||
600 | |||
601 | uint32_t i; | ||
602 | |||
603 | for (i = 0; i < m->numfriends; ++i) | ||
604 | m->friendlist[i].avatar_info_sent = 0; | ||
605 | |||
606 | return 0; | ||
607 | } | ||
608 | |||
609 | int m_get_self_avatar(const Messenger *m, uint8_t *format, uint8_t *buf, uint32_t *length, uint32_t maxlen, | ||
610 | uint8_t *hash) | ||
611 | { | ||
612 | if (format) | ||
613 | *format = m->avatar_format; | ||
614 | |||
615 | if (length) | ||
616 | *length = m->avatar_data_length; | ||
617 | |||
618 | if (hash) | ||
619 | memcpy(hash, m->avatar_hash, AVATAR_HASH_LENGTH); | ||
620 | |||
621 | if (buf != NULL && maxlen > 0) { | ||
622 | if (m->avatar_data_length <= maxlen) | ||
623 | memcpy(buf, m->avatar_data, m->avatar_data_length); | ||
624 | else | ||
625 | return -1; | ||
626 | } | ||
627 | |||
628 | return 0; | ||
629 | } | ||
630 | |||
631 | int m_avatar_hash(uint8_t *hash, const uint8_t *data, const uint32_t datalen) | ||
632 | { | ||
633 | if (hash == NULL) | ||
634 | return -1; | ||
635 | |||
636 | crypto_hash_sha256(hash, data, datalen); | ||
637 | return 0; | ||
638 | } | ||
639 | |||
640 | |||
641 | int m_request_avatar_info(const Messenger *m, const int32_t friendnumber) | ||
642 | { | ||
643 | if (friend_not_valid(m, friendnumber)) | ||
644 | return -1; | ||
645 | |||
646 | if (write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_INFO_REQ, 0, 0, 0) >= 0) | ||
647 | return 0; | ||
648 | else | ||
649 | return -1; | ||
650 | } | ||
651 | |||
652 | int m_send_avatar_info(const Messenger *m, const int32_t friendnumber) | ||
653 | { | ||
654 | if (friend_not_valid(m, friendnumber)) | ||
655 | return -1; | ||
656 | |||
657 | uint8_t data[sizeof(uint8_t) + AVATAR_HASH_LENGTH]; | ||
658 | data[0] = m->avatar_format; | ||
659 | memcpy(data + 1, m->avatar_hash, AVATAR_HASH_LENGTH); | ||
660 | |||
661 | int ret = write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_INFO, data, sizeof(data), 0); | ||
662 | |||
663 | if (ret >= 0) | ||
664 | return 0; | ||
665 | else | ||
666 | return -1; | ||
667 | } | ||
668 | |||
669 | int m_request_avatar_data(const Messenger *m, const int32_t friendnumber) | ||
670 | { | ||
671 | if (friend_not_valid(m, friendnumber)) | ||
672 | return -1; | ||
673 | |||
674 | AVATARRECEIVEDATA *avrd = m->friendlist[friendnumber].avatar_recv_data; | ||
675 | |||
676 | if (avrd == NULL) { | ||
677 | avrd = malloc(sizeof(AVATARRECEIVEDATA)); | ||
678 | |||
679 | if (avrd == NULL) | ||
680 | return -1; | ||
681 | |||
682 | memset(avrd, 0, sizeof(AVATARRECEIVEDATA)); | ||
683 | avrd->started = 0; | ||
684 | m->friendlist[friendnumber].avatar_recv_data = avrd; | ||
685 | } | ||
686 | |||
687 | if (avrd->started) { | ||
688 | LOGGER_DEBUG("Resetting already started data request. " | ||
689 | "friendnumber == %u", friendnumber); | ||
690 | } | ||
691 | |||
692 | avrd->started = 0; | ||
693 | avrd->bytes_received = 0; | ||
694 | avrd->total_length = 0; | ||
695 | avrd->format = AVATARFORMAT_NONE; | ||
696 | |||
697 | return send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_REQ); | ||
698 | } | ||
699 | |||
700 | |||
567 | /* return the size of friendnumber's user status. | 701 | /* return the size of friendnumber's user status. |
568 | * Guaranteed to be at most MAX_STATUSMESSAGE_LENGTH. | 702 | * Guaranteed to be at most MAX_STATUSMESSAGE_LENGTH. |
569 | */ | 703 | */ |
@@ -806,6 +940,20 @@ void m_callback_connectionstatus_internal_av(Messenger *m, void (*function)(Mess | |||
806 | m->friend_connectionstatuschange_internal_userdata = userdata; | 940 | m->friend_connectionstatuschange_internal_userdata = userdata; |
807 | } | 941 | } |
808 | 942 | ||
943 | void m_callback_avatar_info(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint8_t *, void *), | ||
944 | void *userdata) | ||
945 | { | ||
946 | m->avatar_info_recv = function; | ||
947 | m->avatar_info_recv_userdata = userdata; | ||
948 | } | ||
949 | |||
950 | void m_callback_avatar_data(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint8_t *, uint8_t *, | ||
951 | uint32_t, void *), void *userdata) | ||
952 | { | ||
953 | m->avatar_data_recv = function; | ||
954 | m->avatar_data_recv_userdata = userdata; | ||
955 | } | ||
956 | |||
809 | static void break_files(const Messenger *m, int32_t friendnumber); | 957 | static void break_files(const Messenger *m, int32_t friendnumber); |
810 | static void check_friend_connectionstatus(Messenger *m, int32_t friendnumber, uint8_t status) | 958 | static void check_friend_connectionstatus(Messenger *m, int32_t friendnumber, uint8_t status) |
811 | { | 959 | { |
@@ -1857,6 +2005,9 @@ Messenger *new_messenger(Messenger_Options *options) | |||
1857 | m->net = new_networking(ip, TOX_PORT_DEFAULT); | 2005 | m->net = new_networking(ip, TOX_PORT_DEFAULT); |
1858 | } | 2006 | } |
1859 | 2007 | ||
2008 | m->avatar_format = AVATARFORMAT_NONE; | ||
2009 | m->avatar_data = NULL; | ||
2010 | |||
1860 | if (m->net == NULL) { | 2011 | if (m->net == NULL) { |
1861 | free(m); | 2012 | free(m); |
1862 | return NULL; | 2013 | return NULL; |
@@ -1934,6 +2085,7 @@ void kill_messenger(Messenger *m) | |||
1934 | free(m->friendlist[i].statusmessage); | 2085 | free(m->friendlist[i].statusmessage); |
1935 | } | 2086 | } |
1936 | 2087 | ||
2088 | free(m->avatar_data); | ||
1937 | free(m->friendlist); | 2089 | free(m->friendlist); |
1938 | free(m); | 2090 | free(m); |
1939 | } | 2091 | } |
@@ -1973,11 +2125,287 @@ static int handle_status(void *object, int i, uint8_t status) | |||
1973 | if (m->friendlist[i].status == FRIEND_ONLINE) { | 2125 | if (m->friendlist[i].status == FRIEND_ONLINE) { |
1974 | set_friend_status(m, i, FRIEND_CONFIRMED); | 2126 | set_friend_status(m, i, FRIEND_CONFIRMED); |
1975 | } | 2127 | } |
2128 | |||
2129 | /* Clear avatar transfer state */ | ||
2130 | if (m->friendlist[i].avatar_recv_data) { | ||
2131 | free(m->friendlist[i].avatar_recv_data); | ||
2132 | m->friendlist[i].avatar_recv_data = NULL; | ||
2133 | } | ||
2134 | } | ||
2135 | |||
2136 | return 0; | ||
2137 | } | ||
2138 | |||
2139 | |||
2140 | /* Sends an avatar data control packet to the peer. Usually to return status | ||
2141 | * values or request data. | ||
2142 | */ | ||
2143 | static int send_avatar_data_control(const Messenger *m, const uint32_t friendnumber, | ||
2144 | uint8_t op) | ||
2145 | { | ||
2146 | int ret = write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_DATA_CONTROL, | ||
2147 | &op, sizeof(op), 0); | ||
2148 | LOGGER_DEBUG("friendnumber = %u, op = %u, ret = %d", | ||
2149 | friendnumber, op, ret); | ||
2150 | return (ret >= 0) ? 0 : -1; | ||
2151 | } | ||
2152 | |||
2153 | |||
2154 | static int handle_avatar_data_control(Messenger *m, uint32_t friendnumber, | ||
2155 | uint8_t *data, uint32_t data_length) | ||
2156 | { | ||
2157 | if (data_length != 1) { | ||
2158 | LOGGER_DEBUG("Error: PACKET_ID_AVATAR_DATA_CONTROL with bad " | ||
2159 | "data_length = %u, friendnumber = %u", | ||
2160 | data_length, friendnumber); | ||
2161 | send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR); | ||
2162 | return -1; /* Error */ | ||
2163 | } | ||
2164 | |||
2165 | LOGGER_DEBUG("friendnumber = %u, op = %u", friendnumber, data[0]); | ||
2166 | |||
2167 | switch (data[0]) { | ||
2168 | case AVATARDATACONTROL_REQ: { | ||
2169 | |||
2170 | /* Check data transfer limits for this friend */ | ||
2171 | AVATARSENDDATA *const avsd = &(m->friendlist[friendnumber].avatar_send_data); | ||
2172 | |||
2173 | if (avsd->bytes_sent >= AVATAR_DATA_TRANSFER_LIMIT) { | ||
2174 | /* User reached data limit. Check timeout */ | ||
2175 | uint64_t now = unix_time(); | ||
2176 | |||
2177 | if (avsd->last_reset > 0 | ||
2178 | && (avsd->last_reset + AVATAR_DATA_TRANSFER_TIMEOUT < now)) { | ||
2179 | avsd->bytes_sent = 0; | ||
2180 | avsd->last_reset = now; | ||
2181 | } else { | ||
2182 | /* Friend still rate-limitted. Send an error and stops. */ | ||
2183 | LOGGER_DEBUG("Avatar data transfer limit reached. " | ||
2184 | "friendnumber = %u", friendnumber); | ||
2185 | send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR); | ||
2186 | return 0; | ||
2187 | } | ||
2188 | } | ||
2189 | |||
2190 | /* Start the transmission with a DATA_START message. Format: | ||
2191 | * uint8_t format | ||
2192 | * uint8_t hash[AVATAR_HASH_LENGTH] | ||
2193 | * uint32_t total_length | ||
2194 | */ | ||
2195 | LOGGER_DEBUG("Sending start msg to friend number %u. " | ||
2196 | "m->avatar_format = %u, m->avatar_data_length = %u", | ||
2197 | friendnumber, m->avatar_format, m->avatar_data_length); | ||
2198 | uint8_t start_data[1 + AVATAR_HASH_LENGTH + sizeof(uint32_t)]; | ||
2199 | uint32_t avatar_len = htonl(m->avatar_data_length); | ||
2200 | |||
2201 | start_data[0] = m->avatar_format; | ||
2202 | memcpy(start_data + 1, m->avatar_hash, AVATAR_HASH_LENGTH); | ||
2203 | memcpy(start_data + 1 + AVATAR_HASH_LENGTH, &avatar_len, sizeof(uint32_t)); | ||
2204 | |||
2205 | avsd->bytes_sent += sizeof(start_data); /* For rate limit */ | ||
2206 | |||
2207 | int ret = write_cryptpacket_id(m, friendnumber, PACKET_ID_AVATAR_DATA_START, | ||
2208 | start_data, sizeof(start_data), 0); | ||
2209 | |||
2210 | if (ret < 0) { | ||
2211 | /* Something went wrong, try to signal the error so the friend | ||
2212 | * can clear up the state. */ | ||
2213 | send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR); | ||
2214 | return 0; | ||
2215 | } | ||
2216 | |||
2217 | /* User have no avatar data, nothing more to do. */ | ||
2218 | if (m->avatar_format == AVATARFORMAT_NONE) | ||
2219 | return 0; | ||
2220 | |||
2221 | /* Send the actual avatar data. */ | ||
2222 | uint32_t offset = 0; | ||
2223 | |||
2224 | while (offset < m->avatar_data_length) { | ||
2225 | uint32_t chunk_len = m->avatar_data_length - offset; | ||
2226 | |||
2227 | if (chunk_len > AVATAR_DATA_MAX_CHUNK_SIZE) | ||
2228 | chunk_len = AVATAR_DATA_MAX_CHUNK_SIZE; | ||
2229 | |||
2230 | uint8_t chunk[AVATAR_DATA_MAX_CHUNK_SIZE]; | ||
2231 | memcpy(chunk, m->avatar_data + offset, chunk_len); | ||
2232 | offset += chunk_len; | ||
2233 | avsd->bytes_sent += chunk_len; /* For rate limit */ | ||
2234 | |||
2235 | int ret = write_cryptpacket_id(m, friendnumber, | ||
2236 | PACKET_ID_AVATAR_DATA_PUSH, | ||
2237 | chunk, chunk_len, 0); | ||
2238 | |||
2239 | if (ret < 0) { | ||
2240 | LOGGER_DEBUG("write_cryptpacket_id failed. ret = %d, " | ||
2241 | "friendnumber = %u, offset = %u", | ||
2242 | ret, friendnumber, offset); | ||
2243 | send_avatar_data_control(m, friendnumber, AVATARDATACONTROL_ERROR); | ||
2244 | return -1; | ||
2245 | } | ||
2246 | } | ||
2247 | |||
2248 | return 0; | ||
2249 | } | ||
2250 | |||
2251 | case AVATARDATACONTROL_ERROR: { | ||
2252 | if (m->friendlist[friendnumber].avatar_recv_data) { | ||
2253 | /* We were receiving the data, sender detected an error | ||
2254 | (eg. changing avatar) and asked us to stop. */ | ||
2255 | free(m->friendlist[friendnumber].avatar_recv_data); | ||
2256 | m->friendlist[friendnumber].avatar_recv_data = NULL; | ||
2257 | } | ||
2258 | |||
2259 | return 0; | ||
2260 | } | ||
2261 | } | ||
2262 | |||
2263 | return -1; | ||
2264 | } | ||
2265 | |||
2266 | |||
2267 | static int handle_avatar_data_start(Messenger *m, uint32_t friendnumber, | ||
2268 | uint8_t *data, uint32_t data_length) | ||
2269 | { | ||
2270 | LOGGER_DEBUG("data_length = %u, friendnumber = %u", data_length, friendnumber); | ||
2271 | |||
2272 | if (data_length != 1 + AVATAR_HASH_LENGTH + sizeof(uint32_t)) { | ||
2273 | LOGGER_DEBUG("Invalid msg length = %u, friendnumber = %u", | ||
2274 | data_length, friendnumber); | ||
2275 | return -1; | ||
2276 | } | ||
2277 | |||
2278 | AVATARRECEIVEDATA *avrd = m->friendlist[friendnumber].avatar_recv_data; | ||
2279 | |||
2280 | if (avrd == NULL) { | ||
2281 | LOGGER_DEBUG("Received an unrequested DATA_START, friendnumber = %u", | ||
2282 | friendnumber); | ||
2283 | return -1; | ||
2284 | } | ||
2285 | |||
2286 | if (avrd->started) { | ||
2287 | /* Already receiving data from this friend. Must be an error | ||
2288 | * or an malicious request, because we zeroed the started bit | ||
2289 | * when we requested the data. */ | ||
2290 | LOGGER_DEBUG("Received an unrequested duplicated DATA_START, " | ||
2291 | "friendnumber = %u", friendnumber); | ||
2292 | return -1; | ||
2293 | } | ||
2294 | |||
2295 | /* Copy data from message to our control structure */ | ||
2296 | avrd->started = 1; | ||
2297 | avrd->format = data[0]; | ||
2298 | memcpy(avrd->hash, data + 1, AVATAR_HASH_LENGTH); | ||
2299 | uint32_t tmp_len; | ||
2300 | memcpy(&tmp_len, data + 1 + AVATAR_HASH_LENGTH, sizeof(uint32_t)); | ||
2301 | avrd->total_length = ntohl(tmp_len); | ||
2302 | avrd->bytes_received = 0; | ||
2303 | |||
2304 | LOGGER_DEBUG("friendnumber = %u, avrd->format = %u, " | ||
2305 | "avrd->total_length = %u, avrd->bytes_received = %u", | ||
2306 | friendnumber, avrd->format, avrd->total_length, | ||
2307 | avrd->bytes_received); | ||
2308 | |||
2309 | if (avrd->total_length > MAX_AVATAR_DATA_LENGTH) { | ||
2310 | /* Invalid data length. Stops. */ | ||
2311 | LOGGER_DEBUG("Error: total_length > MAX_AVATAR_DATA_LENGTH, " | ||
2312 | "friendnumber = %u", friendnumber); | ||
2313 | free(avrd); | ||
2314 | avrd = NULL; | ||
2315 | m->friendlist[friendnumber].avatar_recv_data = NULL; | ||
2316 | return 0; | ||
2317 | } | ||
2318 | |||
2319 | if (avrd->format == AVATARFORMAT_NONE || avrd->total_length == 0) { | ||
2320 | /* No real data to receive. Run callback function and finish. */ | ||
2321 | LOGGER_DEBUG("format == NONE, friendnumber = %u", friendnumber); | ||
2322 | |||
2323 | if (m->avatar_data_recv) { | ||
2324 | memset(avrd->hash, 0, AVATAR_HASH_LENGTH); | ||
2325 | (m->avatar_data_recv)(m, friendnumber, avrd->format, avrd->hash, | ||
2326 | NULL, 0, m->avatar_data_recv_userdata); | ||
2327 | } | ||
2328 | |||
2329 | free(avrd); | ||
2330 | avrd = NULL; | ||
2331 | m->friendlist[friendnumber].avatar_recv_data = NULL; | ||
2332 | return 0; | ||
2333 | } | ||
2334 | |||
2335 | /* Waits for more data to be received */ | ||
2336 | return 0; | ||
2337 | } | ||
2338 | |||
2339 | |||
2340 | static int handle_avatar_data_push(Messenger *m, uint32_t friendnumber, | ||
2341 | uint8_t *data, uint32_t data_length) | ||
2342 | { | ||
2343 | LOGGER_DEBUG("friendnumber = %u, data_length = %u", friendnumber, data_length); | ||
2344 | |||
2345 | AVATARRECEIVEDATA *avrd = m->friendlist[friendnumber].avatar_recv_data; | ||
2346 | |||
2347 | if (avrd == NULL) { | ||
2348 | /* No active transfer. It must be an error or a malicious request, | ||
2349 | * because we set the avatar_recv_data on the first DATA_START. */ | ||
2350 | LOGGER_DEBUG("Error: avrd == NULL, friendnumber = %u", friendnumber); | ||
2351 | return -1; /* Error */ | ||
2352 | } | ||
2353 | |||
2354 | if (avrd->started == 0) { | ||
2355 | /* Receiving data for a non-started request. Must be an error | ||
2356 | * or an malicious request. */ | ||
2357 | LOGGER_DEBUG("Received an data push for a yet non started data " | ||
2358 | "request. friendnumber = %u", friendnumber); | ||
2359 | return -1; /* Error */ | ||
2360 | } | ||
2361 | |||
2362 | uint32_t new_length = avrd->bytes_received + data_length; | ||
2363 | |||
2364 | if (new_length > avrd->total_length | ||
2365 | || new_length >= MAX_AVATAR_DATA_LENGTH) { | ||
2366 | /* Invalid data length due to error or malice. Stops. */ | ||
2367 | LOGGER_DEBUG("Invalid data length. friendnumber = %u, " | ||
2368 | "new_length = %u, avrd->total_length = %u", | ||
2369 | friendnumber, new_length, avrd->total_length); | ||
2370 | free(avrd); | ||
2371 | m->friendlist[friendnumber].avatar_recv_data = NULL; | ||
2372 | return 0; | ||
2373 | } | ||
2374 | |||
2375 | memcpy(avrd->data + avrd->bytes_received, data, data_length); | ||
2376 | avrd->bytes_received += data_length; | ||
2377 | |||
2378 | if (avrd->bytes_received == avrd->total_length) { | ||
2379 | LOGGER_DEBUG("All data received. friendnumber = %u", friendnumber); | ||
2380 | |||
2381 | /* All data was received. Check if the hashes match. It the | ||
2382 | * requester's responsability to do this. The sender may have done | ||
2383 | * anything with its avatar data between the DATA_START and now. | ||
2384 | */ | ||
2385 | uint8_t cur_hash[AVATAR_HASH_LENGTH]; | ||
2386 | m_avatar_hash(cur_hash, avrd->data, avrd->bytes_received); | ||
2387 | |||
2388 | if (memcmp(cur_hash, avrd->hash, AVATAR_HASH_LENGTH) == 0) { | ||
2389 | /* Avatar successfuly received! */ | ||
2390 | if (m->avatar_data_recv) { | ||
2391 | (m->avatar_data_recv)(m, friendnumber, avrd->format, cur_hash, | ||
2392 | avrd->data, avrd->bytes_received, m->avatar_data_recv_userdata); | ||
2393 | } | ||
2394 | } else { | ||
2395 | LOGGER_DEBUG("Avatar hash error. friendnumber = %u", friendnumber); | ||
2396 | } | ||
2397 | |||
2398 | free(avrd); | ||
2399 | m->friendlist[friendnumber].avatar_recv_data = NULL; | ||
2400 | return 0; | ||
1976 | } | 2401 | } |
1977 | 2402 | ||
2403 | /* Waits for more data to be received */ | ||
1978 | return 0; | 2404 | return 0; |
1979 | } | 2405 | } |
1980 | 2406 | ||
2407 | |||
2408 | |||
1981 | static int handle_packet(void *object, int i, uint8_t *temp, uint16_t len) | 2409 | static int handle_packet(void *object, int i, uint8_t *temp, uint16_t len) |
1982 | { | 2410 | { |
1983 | if (len == 0) | 2411 | if (len == 0) |
@@ -2115,6 +2543,42 @@ static int handle_packet(void *object, int i, uint8_t *temp, uint16_t len) | |||
2115 | break; | 2543 | break; |
2116 | } | 2544 | } |
2117 | 2545 | ||
2546 | case PACKET_ID_AVATAR_INFO_REQ: { | ||
2547 | /* Send our avatar information */ | ||
2548 | m_send_avatar_info(m, i); | ||
2549 | break; | ||
2550 | } | ||
2551 | |||
2552 | case PACKET_ID_AVATAR_INFO: { | ||
2553 | if (m->avatar_info_recv) { | ||
2554 | /* | ||
2555 | * A malicious user may send an incomplete avatar info message. | ||
2556 | * Check if it have the correct size for the format: | ||
2557 | * [1 uint8_t: avatar format] [32 uint8_t: hash] | ||
2558 | */ | ||
2559 | if (data_length == AVATAR_HASH_LENGTH + 1) { | ||
2560 | (m->avatar_info_recv)(m, i, data[0], data + 1, m->avatar_info_recv_userdata); | ||
2561 | } | ||
2562 | } | ||
2563 | |||
2564 | break; | ||
2565 | } | ||
2566 | |||
2567 | case PACKET_ID_AVATAR_DATA_CONTROL: { | ||
2568 | handle_avatar_data_control(m, i, data, data_length); | ||
2569 | break; | ||
2570 | } | ||
2571 | |||
2572 | case PACKET_ID_AVATAR_DATA_START: { | ||
2573 | handle_avatar_data_start(m, i, data, data_length); | ||
2574 | break; | ||
2575 | } | ||
2576 | |||
2577 | case PACKET_ID_AVATAR_DATA_PUSH: { | ||
2578 | handle_avatar_data_push(m, i, data, data_length); | ||
2579 | break; | ||
2580 | } | ||
2581 | |||
2118 | case PACKET_ID_RECEIPT: { | 2582 | case PACKET_ID_RECEIPT: { |
2119 | uint32_t msgid; | 2583 | uint32_t msgid; |
2120 | 2584 | ||
@@ -2343,6 +2807,11 @@ void do_friends(Messenger *m) | |||
2343 | m->friendlist[i].userstatus_sent = 1; | 2807 | m->friendlist[i].userstatus_sent = 1; |
2344 | } | 2808 | } |
2345 | 2809 | ||
2810 | if (m->friendlist[i].avatar_info_sent == 0) { | ||
2811 | if (m_send_avatar_info(m, i) == 0) | ||
2812 | m->friendlist[i].avatar_info_sent = 1; | ||
2813 | } | ||
2814 | |||
2346 | if (m->friendlist[i].user_istyping_sent == 0) { | 2815 | if (m->friendlist[i].user_istyping_sent == 0) { |
2347 | if (send_user_istyping(m, i, m->friendlist[i].user_istyping)) | 2816 | if (send_user_istyping(m, i, m->friendlist[i].user_istyping)) |
2348 | m->friendlist[i].user_istyping_sent = 1; | 2817 | m->friendlist[i].user_istyping_sent = 1; |
diff --git a/toxcore/Messenger.h b/toxcore/Messenger.h index 6c641a9a..01632126 100644 --- a/toxcore/Messenger.h +++ b/toxcore/Messenger.h | |||
@@ -36,6 +36,9 @@ | |||
36 | #define MAX_NAME_LENGTH 128 | 36 | #define MAX_NAME_LENGTH 128 |
37 | /* TODO: this must depend on other variable. */ | 37 | /* TODO: this must depend on other variable. */ |
38 | #define MAX_STATUSMESSAGE_LENGTH 1007 | 38 | #define MAX_STATUSMESSAGE_LENGTH 1007 |
39 | #define MAX_AVATAR_DATA_LENGTH 16384 | ||
40 | #define AVATAR_HASH_LENGTH 32 | ||
41 | |||
39 | 42 | ||
40 | #define FRIEND_ADDRESS_SIZE (crypto_box_PUBLICKEYBYTES + sizeof(uint32_t) + sizeof(uint16_t)) | 43 | #define FRIEND_ADDRESS_SIZE (crypto_box_PUBLICKEYBYTES + sizeof(uint32_t) + sizeof(uint16_t)) |
41 | 44 | ||
@@ -46,6 +49,11 @@ | |||
46 | #define PACKET_ID_STATUSMESSAGE 49 | 49 | #define PACKET_ID_STATUSMESSAGE 49 |
47 | #define PACKET_ID_USERSTATUS 50 | 50 | #define PACKET_ID_USERSTATUS 50 |
48 | #define PACKET_ID_TYPING 51 | 51 | #define PACKET_ID_TYPING 51 |
52 | #define PACKET_ID_AVATAR_INFO_REQ 52 | ||
53 | #define PACKET_ID_AVATAR_INFO 53 | ||
54 | #define PACKET_ID_AVATAR_DATA_CONTROL 54 | ||
55 | #define PACKET_ID_AVATAR_DATA_START 55 | ||
56 | #define PACKET_ID_AVATAR_DATA_PUSH 56 | ||
49 | #define PACKET_ID_RECEIPT 63 | 57 | #define PACKET_ID_RECEIPT 63 |
50 | #define PACKET_ID_MESSAGE 64 | 58 | #define PACKET_ID_MESSAGE 64 |
51 | #define PACKET_ID_ACTION 65 | 59 | #define PACKET_ID_ACTION 65 |
@@ -109,6 +117,13 @@ enum { | |||
109 | /* If no packets are received from friend in this time interval, kill the connection. */ | 117 | /* If no packets are received from friend in this time interval, kill the connection. */ |
110 | #define FRIEND_CONNECTION_TIMEOUT (FRIEND_PING_INTERVAL * 3) | 118 | #define FRIEND_CONNECTION_TIMEOUT (FRIEND_PING_INTERVAL * 3) |
111 | 119 | ||
120 | /* Must be < MAX_CRYPTO_DATA_SIZE */ | ||
121 | #define AVATAR_DATA_MAX_CHUNK_SIZE (MAX_CRYPTO_DATA_SIZE-1) | ||
122 | |||
123 | /* Per-friend data limit for avatar data requests */ | ||
124 | #define AVATAR_DATA_TRANSFER_LIMIT (10*MAX_AVATAR_DATA_LENGTH) | ||
125 | #define AVATAR_DATA_TRANSFER_TIMEOUT (20*60) | ||
126 | |||
112 | 127 | ||
113 | /* USERSTATUS - | 128 | /* USERSTATUS - |
114 | * Represents userstatuses someone can have. | 129 | * Represents userstatuses someone can have. |
@@ -122,6 +137,42 @@ typedef enum { | |||
122 | } | 137 | } |
123 | USERSTATUS; | 138 | USERSTATUS; |
124 | 139 | ||
140 | /* AVATARFORMAT - | ||
141 | * Data formats for user avatar images | ||
142 | */ | ||
143 | typedef enum { | ||
144 | AVATARFORMAT_NONE, | ||
145 | AVATARFORMAT_PNG | ||
146 | } | ||
147 | AVATARFORMAT; | ||
148 | |||
149 | /* AVATARDATACONTROL | ||
150 | * To control avatar data requests (PACKET_ID_AVATAR_DATA_CONTROL) | ||
151 | */ | ||
152 | typedef enum { | ||
153 | AVATARDATACONTROL_REQ, | ||
154 | AVATARDATACONTROL_ERROR | ||
155 | } | ||
156 | AVATARDATACONTROL; | ||
157 | |||
158 | typedef struct { | ||
159 | uint8_t started; | ||
160 | AVATARFORMAT format; | ||
161 | uint8_t hash[AVATAR_HASH_LENGTH]; | ||
162 | uint32_t total_length; | ||
163 | uint32_t bytes_received; | ||
164 | uint8_t data[MAX_AVATAR_DATA_LENGTH]; | ||
165 | } | ||
166 | AVATARRECEIVEDATA; | ||
167 | |||
168 | typedef struct { | ||
169 | /* Fields only used to limit the network usage from a given friend */ | ||
170 | uint32_t bytes_sent; /* Total bytes send to this user */ | ||
171 | uint64_t last_reset; /* Time the data counter was last reset */ | ||
172 | } | ||
173 | AVATARSENDDATA; | ||
174 | |||
175 | |||
125 | struct File_Transfers { | 176 | struct File_Transfers { |
126 | uint64_t size; | 177 | uint64_t size; |
127 | uint64_t transferred; | 178 | uint64_t transferred; |
@@ -163,6 +214,7 @@ typedef struct { | |||
163 | uint8_t statusmessage_sent; | 214 | uint8_t statusmessage_sent; |
164 | USERSTATUS userstatus; | 215 | USERSTATUS userstatus; |
165 | uint8_t userstatus_sent; | 216 | uint8_t userstatus_sent; |
217 | uint8_t avatar_info_sent; | ||
166 | uint8_t user_istyping; | 218 | uint8_t user_istyping; |
167 | uint8_t user_istyping_sent; | 219 | uint8_t user_istyping_sent; |
168 | uint8_t is_typing; | 220 | uint8_t is_typing; |
@@ -178,6 +230,9 @@ typedef struct { | |||
178 | int invited_groups[MAX_INVITED_GROUPS]; | 230 | int invited_groups[MAX_INVITED_GROUPS]; |
179 | uint16_t invited_groups_num; | 231 | uint16_t invited_groups_num; |
180 | 232 | ||
233 | AVATARSENDDATA avatar_send_data; | ||
234 | AVATARRECEIVEDATA *avatar_recv_data; // We are receiving avatar data from this friend. | ||
235 | |||
181 | struct { | 236 | struct { |
182 | int (*function)(void *object, const uint8_t *data, uint32_t len); | 237 | int (*function)(void *object, const uint8_t *data, uint32_t len); |
183 | void *object; | 238 | void *object; |
@@ -209,6 +264,11 @@ typedef struct Messenger { | |||
209 | 264 | ||
210 | USERSTATUS userstatus; | 265 | USERSTATUS userstatus; |
211 | 266 | ||
267 | AVATARFORMAT avatar_format; | ||
268 | uint8_t *avatar_data; | ||
269 | uint32_t avatar_data_length; | ||
270 | uint8_t avatar_hash[AVATAR_HASH_LENGTH]; | ||
271 | |||
212 | Friend *friendlist; | 272 | Friend *friendlist; |
213 | uint32_t numfriends; | 273 | uint32_t numfriends; |
214 | 274 | ||
@@ -243,6 +303,10 @@ typedef struct Messenger { | |||
243 | void *friend_connectionstatuschange_userdata; | 303 | void *friend_connectionstatuschange_userdata; |
244 | void (*friend_connectionstatuschange_internal)(struct Messenger *m, int32_t, uint8_t, void *); | 304 | void (*friend_connectionstatuschange_internal)(struct Messenger *m, int32_t, uint8_t, void *); |
245 | void *friend_connectionstatuschange_internal_userdata; | 305 | void *friend_connectionstatuschange_internal_userdata; |
306 | void *avatar_info_recv_userdata; | ||
307 | void (*avatar_info_recv)(struct Messenger *m, int32_t, uint8_t, uint8_t *, void *); | ||
308 | void *avatar_data_recv_userdata; | ||
309 | void (*avatar_data_recv)(struct Messenger *m, int32_t, uint8_t, uint8_t *, uint8_t *, uint32_t, void *); | ||
246 | 310 | ||
247 | void (*group_invite)(struct Messenger *m, int32_t, const uint8_t *, void *); | 311 | void (*group_invite)(struct Messenger *m, int32_t, const uint8_t *, void *); |
248 | void *group_invite_userdata; | 312 | void *group_invite_userdata; |
@@ -437,6 +501,94 @@ int m_copy_self_statusmessage(const Messenger *m, uint8_t *buf, uint32_t maxlen) | |||
437 | uint8_t m_get_userstatus(const Messenger *m, int32_t friendnumber); | 501 | uint8_t m_get_userstatus(const Messenger *m, int32_t friendnumber); |
438 | uint8_t m_get_self_userstatus(const Messenger *m); | 502 | uint8_t m_get_self_userstatus(const Messenger *m); |
439 | 503 | ||
504 | |||
505 | /* Set the user avatar image data. | ||
506 | * This should be made before connecting, so we will not announce that the user have no avatar | ||
507 | * before setting and announcing a new one, forcing the peers to re-download it. | ||
508 | * | ||
509 | * Notice that the library treats the image as raw data and does not interpret it by any way. | ||
510 | * | ||
511 | * Arguments: | ||
512 | * format - Avatar image format or NONE for user with no avatar (see AVATARFORMAT); | ||
513 | * data - pointer to the avatar data (may be NULL it the format is NONE); | ||
514 | * length - length of image data. Must be <= MAX_AVATAR_DATA_LENGTH. | ||
515 | * | ||
516 | * returns 0 on success | ||
517 | * returns -1 on failure. | ||
518 | */ | ||
519 | int m_set_avatar(Messenger *m, uint8_t format, const uint8_t *data, uint32_t length); | ||
520 | |||
521 | /* Get avatar data from the current user. | ||
522 | * Copies the current user avatar data to the destination buffer and sets the image format | ||
523 | * accordingly. | ||
524 | * | ||
525 | * If the avatar format is NONE, the buffer 'buf' isleft uninitialized, 'hash' is zeroed, and | ||
526 | * 'length' is set to zero. | ||
527 | * | ||
528 | * If any of the pointers format, buf, length, and hash are NULL, that particular field will be ignored. | ||
529 | * | ||
530 | * Arguments: | ||
531 | * format - destination pointer to the avatar image format (see AVATARFORMAT); | ||
532 | * buf - destination buffer to the image data. Must have at least 'maxlen' bytes; | ||
533 | * length - destination pointer to the image data length; | ||
534 | * maxlen - length of the destination buffer 'buf'; | ||
535 | * hash - destination pointer to the avatar hash (it must be exactly AVATAR_HASH_LENGTH bytes long). | ||
536 | * | ||
537 | * returns 0 on success; | ||
538 | * returns -1 on failure. | ||
539 | * | ||
540 | */ | ||
541 | int m_get_self_avatar(const Messenger *m, uint8_t *format, uint8_t *buf, uint32_t *length, uint32_t maxlen, | ||
542 | uint8_t *hash); | ||
543 | |||
544 | /* Generates a cryptographic hash of the given avatar data. | ||
545 | * This function is a wrapper to internal message-digest functions and specifically provided | ||
546 | * to generate hashes from user avatars that may be memcmp()ed with the values returned by the | ||
547 | * other avatar functions. It is specially important to validate cached avatars. | ||
548 | * | ||
549 | * Arguments: | ||
550 | * hash - destination buffer for the hash data, it must be exactly AVATAR_HASH_LENGTH bytes long. | ||
551 | * data - avatar image data; | ||
552 | * datalen - length of the avatar image data; it must be <= MAX_AVATAR_DATA_LENGTH. | ||
553 | * | ||
554 | * returns 0 on success | ||
555 | * returns -1 on failure. | ||
556 | */ | ||
557 | int m_avatar_hash(uint8_t *hash, const uint8_t *data, const uint32_t datalen); | ||
558 | |||
559 | /* Request avatar information from a friend. | ||
560 | * Asks a friend to provide their avatar information (image format and hash). The friend may | ||
561 | * or may not answer this request and, if answered, the information will be provided through | ||
562 | * the callback 'avatar_info'. | ||
563 | * | ||
564 | * returns 0 on success | ||
565 | * returns -1 on failure. | ||
566 | */ | ||
567 | int m_request_avatar_info(const Messenger *m, const int32_t friendnumber); | ||
568 | |||
569 | /* Send an unrequested avatar information to a friend. | ||
570 | * Sends our avatar format and hash to a friend; he/she can use this information to validate | ||
571 | * an avatar from the cache and may (or not) reply with an avatar data request. | ||
572 | * | ||
573 | * Notice: it is NOT necessary to send these notification after changing the avatar or | ||
574 | * connecting. The library already does this. | ||
575 | * | ||
576 | * returns 0 on success | ||
577 | * returns -1 on failure. | ||
578 | */ | ||
579 | int m_send_avatar_info(const Messenger *m, const int32_t friendnumber); | ||
580 | |||
581 | |||
582 | /* Request the avatar data from a friend. | ||
583 | * Ask a friend to send their avatar data. The friend may or may not answer this request and, | ||
584 | * if answered, the information will be provided in callback 'avatar_data'. | ||
585 | * | ||
586 | * returns 0 on sucess | ||
587 | * returns -1 on failure. | ||
588 | */ | ||
589 | int m_request_avatar_data(const Messenger *m, const int32_t friendnumber); | ||
590 | |||
591 | |||
440 | /* returns timestamp of last time friendnumber was seen online, or 0 if never seen. | 592 | /* returns timestamp of last time friendnumber was seen online, or 0 if never seen. |
441 | * returns -1 on error. | 593 | * returns -1 on error. |
442 | */ | 594 | */ |
@@ -533,6 +685,49 @@ void m_callback_connectionstatus(Messenger *m, void (*function)(Messenger *m, in | |||
533 | void m_callback_connectionstatus_internal_av(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, void *), | 685 | void m_callback_connectionstatus_internal_av(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, void *), |
534 | void *userdata); | 686 | void *userdata); |
535 | 687 | ||
688 | |||
689 | /* Set the callback function for avatar information. | ||
690 | * This callback will be called when avatar information are received from friends. These events | ||
691 | * can arrive at anytime, but are usually received uppon connection and in reply of avatar | ||
692 | * information requests. | ||
693 | * | ||
694 | * Function format is: | ||
695 | * function(Tox *tox, int32_t friendnumber, uint8_t format, uint8_t *hash, void *userdata) | ||
696 | * | ||
697 | * where 'format' is the avatar image format (see AVATARFORMAT) and 'hash' is the hash of | ||
698 | * the avatar data for caching purposes and it is exactly AVATAR_HASH_LENGTH long. If the | ||
699 | * image format is NONE, the hash is zeroed. | ||
700 | * | ||
701 | */ | ||
702 | void m_callback_avatar_info(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint8_t *, void *), | ||
703 | void *userdata); | ||
704 | |||
705 | |||
706 | /* Set the callback function for avatar data. | ||
707 | * This callback will be called when the complete avatar data was correctly received from a | ||
708 | * friend. This only happens in reply of a avatar data request (see tox_request_avatar_data); | ||
709 | * | ||
710 | * Function format is: | ||
711 | * function(Tox *tox, int32_t friendnumber, uint8_t format, uint8_t *hash, uint8_t *data, uint32_t datalen, void *userdata) | ||
712 | * | ||
713 | * where 'format' is the avatar image format (see AVATARFORMAT); 'hash' is the | ||
714 | * locally-calculated cryptographic hash of the avatar data and it is exactly | ||
715 | * AVATAR_HASH_LENGTH long; 'data' is the avatar image data and 'datalen' is the length | ||
716 | * of such data. | ||
717 | * | ||
718 | * If format is NONE, 'data' is NULL, 'datalen' is zero, and the hash is zeroed. The hash is | ||
719 | * always validated locally with the function tox_avatar_hash and ensured to match the image | ||
720 | * data, so this value can be safely used to compare with cached avatars. | ||
721 | * | ||
722 | * WARNING: users MUST treat all avatar image data received from another peer as untrusted and | ||
723 | * potentially malicious. The library only ensures that the data which arrived is the same the | ||
724 | * other user sent, and does not interpret or validate any image data. | ||
725 | */ | ||
726 | void m_callback_avatar_data(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint8_t *, uint8_t *, | ||
727 | uint32_t, void *), void *userdata); | ||
728 | |||
729 | |||
730 | |||
536 | /**********GROUP CHATS************/ | 731 | /**********GROUP CHATS************/ |
537 | 732 | ||
538 | /* Set the callback for group invites. | 733 | /* Set the callback for group invites. |
diff --git a/toxcore/tox.c b/toxcore/tox.c index a4413c4f..c5bea846 100644 --- a/toxcore/tox.c +++ b/toxcore/tox.c | |||
@@ -275,6 +275,42 @@ uint8_t tox_get_self_user_status(const Tox *tox) | |||
275 | return m_get_self_userstatus(m); | 275 | return m_get_self_userstatus(m); |
276 | } | 276 | } |
277 | 277 | ||
278 | int tox_set_avatar(Tox *tox, uint8_t format, const uint8_t *data, uint32_t length) | ||
279 | { | ||
280 | Messenger *m = tox; | ||
281 | return m_set_avatar(m, format, data, length); | ||
282 | } | ||
283 | |||
284 | int tox_get_self_avatar(const Tox *tox, uint8_t *format, uint8_t *buf, uint32_t *length, uint32_t maxlen, uint8_t *hash) | ||
285 | { | ||
286 | const Messenger *m = tox; | ||
287 | return m_get_self_avatar(m, format, buf, length, maxlen, hash); | ||
288 | } | ||
289 | |||
290 | int tox_avatar_hash(const Tox *tox, uint8_t *hash, const uint8_t *data, const uint32_t datalen) | ||
291 | { | ||
292 | return m_avatar_hash(hash, data, datalen); | ||
293 | } | ||
294 | |||
295 | int tox_request_avatar_info(const Tox *tox, const int32_t friendnumber) | ||
296 | { | ||
297 | const Messenger *m = tox; | ||
298 | return m_request_avatar_info(m, friendnumber); | ||
299 | } | ||
300 | |||
301 | int tox_send_avatar_info(Tox *tox, const int32_t friendnumber) | ||
302 | { | ||
303 | const Messenger *m = tox; | ||
304 | return m_send_avatar_info(m, friendnumber); | ||
305 | } | ||
306 | |||
307 | int tox_request_avatar_data(const Tox *tox, const int32_t friendnumber) | ||
308 | { | ||
309 | const Messenger *m = tox; | ||
310 | return m_request_avatar_data(m, friendnumber); | ||
311 | } | ||
312 | |||
313 | |||
278 | /* returns timestamp of last time friendnumber was seen online, or 0 if never seen. | 314 | /* returns timestamp of last time friendnumber was seen online, or 0 if never seen. |
279 | * returns -1 on error. | 315 | * returns -1 on error. |
280 | */ | 316 | */ |
@@ -439,6 +475,24 @@ void tox_callback_connection_status(Tox *tox, void (*function)(Messenger *tox, i | |||
439 | m_callback_connectionstatus(m, function, userdata); | 475 | m_callback_connectionstatus(m, function, userdata); |
440 | } | 476 | } |
441 | 477 | ||
478 | void tox_callback_avatar_info(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t *, void *), void *userdata) | ||
479 | { | ||
480 | Messenger *m = tox; | ||
481 | m_callback_avatar_info(m, function, userdata); | ||
482 | } | ||
483 | |||
484 | |||
485 | void tox_callback_avatar_data(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t *, uint8_t *, uint32_t, | ||
486 | void *), void *userdata) | ||
487 | { | ||
488 | Messenger *m = tox; | ||
489 | m_callback_avatar_data(m, function, userdata); | ||
490 | } | ||
491 | |||
492 | |||
493 | |||
494 | |||
495 | |||
442 | /**********ADVANCED FUNCTIONS (If you don't know what they do you can safely ignore them.) ************/ | 496 | /**********ADVANCED FUNCTIONS (If you don't know what they do you can safely ignore them.) ************/ |
443 | 497 | ||
444 | /* Functions to get/set the nospam part of the id. | 498 | /* Functions to get/set the nospam part of the id. |
diff --git a/toxcore/tox.h b/toxcore/tox.h index 278a19cd..8f54697f 100644 --- a/toxcore/tox.h +++ b/toxcore/tox.h | |||
@@ -37,6 +37,8 @@ extern "C" { | |||
37 | #define TOX_MAX_MESSAGE_LENGTH 1368 | 37 | #define TOX_MAX_MESSAGE_LENGTH 1368 |
38 | #define TOX_MAX_STATUSMESSAGE_LENGTH 1007 | 38 | #define TOX_MAX_STATUSMESSAGE_LENGTH 1007 |
39 | #define TOX_CLIENT_ID_SIZE 32 | 39 | #define TOX_CLIENT_ID_SIZE 32 |
40 | #define TOX_MAX_AVATAR_DATA_LENGTH 16384 | ||
41 | #define TOX_AVATAR_HASH_LENGTH 32 | ||
40 | 42 | ||
41 | #define TOX_FRIEND_ADDRESS_SIZE (TOX_CLIENT_ID_SIZE + sizeof(uint32_t) + sizeof(uint16_t)) | 43 | #define TOX_FRIEND_ADDRESS_SIZE (TOX_CLIENT_ID_SIZE + sizeof(uint32_t) + sizeof(uint16_t)) |
42 | 44 | ||
@@ -70,6 +72,16 @@ typedef enum { | |||
70 | } | 72 | } |
71 | TOX_USERSTATUS; | 73 | TOX_USERSTATUS; |
72 | 74 | ||
75 | |||
76 | /* AVATARFORMAT - | ||
77 | * Data formats for user avatar images | ||
78 | */ | ||
79 | typedef enum { | ||
80 | TOX_AVATARFORMAT_NONE, | ||
81 | TOX_AVATARFORMAT_PNG | ||
82 | } | ||
83 | TOX_AVATARFORMAT; | ||
84 | |||
73 | #ifndef __TOX_DEFINED__ | 85 | #ifndef __TOX_DEFINED__ |
74 | #define __TOX_DEFINED__ | 86 | #define __TOX_DEFINED__ |
75 | typedef struct Tox Tox; | 87 | typedef struct Tox Tox; |
@@ -243,6 +255,97 @@ uint8_t tox_get_user_status(const Tox *tox, int32_t friendnumber); | |||
243 | uint8_t tox_get_self_user_status(const Tox *tox); | 255 | uint8_t tox_get_self_user_status(const Tox *tox); |
244 | 256 | ||
245 | 257 | ||
258 | /* Set the user avatar image data. | ||
259 | * This should be made before connecting, so we will not announce that the user have no avatar | ||
260 | * before setting and announcing a new one, forcing the peers to re-download it. | ||
261 | * | ||
262 | * Notice that the library treats the image as raw data and does not interpret it by any way. | ||
263 | * | ||
264 | * Arguments: | ||
265 | * format - Avatar image format or NONE for user with no avatar (see TOX_AVATARFORMAT); | ||
266 | * data - pointer to the avatar data (may be NULL it the format is NONE); | ||
267 | * length - length of image data. Must be <= TOX_MAX_AVATAR_DATA_LENGTH. | ||
268 | * | ||
269 | * returns 0 on success | ||
270 | * returns -1 on failure. | ||
271 | */ | ||
272 | int tox_set_avatar(Tox *tox, uint8_t format, const uint8_t *data, uint32_t length); | ||
273 | |||
274 | |||
275 | /* Get avatar data from the current user. | ||
276 | * Copies the current user avatar data to the destination buffer and sets the image format | ||
277 | * accordingly. | ||
278 | * | ||
279 | * If the avatar format is NONE, the buffer 'buf' isleft uninitialized, 'hash' is zeroed, and | ||
280 | * 'length' is set to zero. | ||
281 | * | ||
282 | * If any of the pointers format, buf, length, and hash are NULL, that particular field will be ignored. | ||
283 | * | ||
284 | * Arguments: | ||
285 | * format - destination pointer to the avatar image format (see TOX_AVATARFORMAT); | ||
286 | * buf - destination buffer to the image data. Must have at least 'maxlen' bytes; | ||
287 | * length - destination pointer to the image data length; | ||
288 | * maxlen - length of the destination buffer 'buf'; | ||
289 | * hash - destination pointer to the avatar hash (it must be exactly TOX_AVATAR_HASH_LENGTH bytes long). | ||
290 | * | ||
291 | * returns 0 on success; | ||
292 | * returns -1 on failure. | ||
293 | * | ||
294 | */ | ||
295 | int tox_get_self_avatar(const Tox *tox, uint8_t *format, uint8_t *buf, uint32_t *length, uint32_t maxlen, | ||
296 | uint8_t *hash); | ||
297 | |||
298 | |||
299 | /* Generates a cryptographic hash of the given avatar data. | ||
300 | * This function is a wrapper to internal message-digest functions and specifically provided | ||
301 | * to generate hashes from user avatars that may be memcmp()ed with the values returned by the | ||
302 | * other avatar functions. It is specially important to validate cached avatars. | ||
303 | * | ||
304 | * Arguments: | ||
305 | * hash - destination buffer for the hash data, it must be exactly TOX_AVATAR_HASH_LENGTH bytes long. | ||
306 | * data - avatar image data; | ||
307 | * datalen - length of the avatar image data; it must be <= TOX_MAX_AVATAR_DATA_LENGTH. | ||
308 | * | ||
309 | * returns 0 on success | ||
310 | * returns -1 on failure. | ||
311 | */ | ||
312 | int tox_avatar_hash(const Tox *tox, uint8_t *hash, const uint8_t *data, const uint32_t datalen); | ||
313 | |||
314 | |||
315 | /* Request avatar information from a friend. | ||
316 | * Asks a friend to provide their avatar information (image format and hash). The friend may | ||
317 | * or may not answer this request and, if answered, the information will be provided through | ||
318 | * the callback 'avatar_info'. | ||
319 | * | ||
320 | * returns 0 on success | ||
321 | * returns -1 on failure. | ||
322 | */ | ||
323 | int tox_request_avatar_info(const Tox *tox, const int32_t friendnumber); | ||
324 | |||
325 | |||
326 | /* Send an unrequested avatar information to a friend. | ||
327 | * Sends our avatar format and hash to a friend; he/she can use this information to validate | ||
328 | * an avatar from the cache and may (or not) reply with an avatar data request. | ||
329 | * | ||
330 | * Notice: it is NOT necessary to send these notification after changing the avatar or | ||
331 | * connecting. The library already does this. | ||
332 | * | ||
333 | * returns 0 on success | ||
334 | * returns -1 on failure. | ||
335 | */ | ||
336 | int tox_send_avatar_info(Tox *tox, const int32_t friendnumber); | ||
337 | |||
338 | |||
339 | /* Request the avatar data from a friend. | ||
340 | * Ask a friend to send their avatar data. The friend may or may not answer this request and, | ||
341 | * if answered, the information will be provided in callback 'avatar_data'. | ||
342 | * | ||
343 | * returns 0 on sucess | ||
344 | * returns -1 on failure. | ||
345 | */ | ||
346 | int tox_request_avatar_data(const Tox *tox, const int32_t friendnumber); | ||
347 | |||
348 | |||
246 | /* returns timestamp of last time friendnumber was seen online, or 0 if never seen. | 349 | /* returns timestamp of last time friendnumber was seen online, or 0 if never seen. |
247 | * returns -1 on error. | 350 | * returns -1 on error. |
248 | */ | 351 | */ |
@@ -344,6 +447,48 @@ void tox_callback_read_receipt(Tox *tox, void (*function)(Tox *tox, int32_t, uin | |||
344 | */ | 447 | */ |
345 | void tox_callback_connection_status(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, void *), void *userdata); | 448 | void tox_callback_connection_status(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, void *), void *userdata); |
346 | 449 | ||
450 | /* Set the callback function for avatar information. | ||
451 | * This callback will be called when avatar information are received from friends. These events | ||
452 | * can arrive at anytime, but are usually received uppon connection and in reply of avatar | ||
453 | * information requests. | ||
454 | * | ||
455 | * Function format is: | ||
456 | * function(Tox *tox, int32_t friendnumber, uint8_t format, uint8_t *hash, void *userdata) | ||
457 | * | ||
458 | * where 'format' is the avatar image format (see TOX_AVATARFORMAT) and 'hash' is the hash of | ||
459 | * the avatar data for caching purposes and it is exactly TOX_AVATAR_HASH_LENGTH long. If the | ||
460 | * image format is NONE, the hash is zeroed. | ||
461 | * | ||
462 | */ | ||
463 | void tox_callback_avatar_info(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t *, void *), | ||
464 | void *userdata); | ||
465 | |||
466 | |||
467 | /* Set the callback function for avatar data. | ||
468 | * This callback will be called when the complete avatar data was correctly received from a | ||
469 | * friend. This only happens in reply of a avatar data request (see tox_request_avatar_data); | ||
470 | * | ||
471 | * Function format is: | ||
472 | * function(Tox *tox, int32_t friendnumber, uint8_t format, uint8_t *hash, uint8_t *data, uint32_t datalen, void *userdata) | ||
473 | * | ||
474 | * where 'format' is the avatar image format (see TOX_AVATARFORMAT); 'hash' is the | ||
475 | * locally-calculated cryptographic hash of the avatar data and it is exactly | ||
476 | * TOX_AVATAR_HASH_LENGTH long; 'data' is the avatar image data and 'datalen' is the length | ||
477 | * of such data. | ||
478 | * | ||
479 | * If format is NONE, 'data' is NULL, 'datalen' is zero, and the hash is zeroed. The hash is | ||
480 | * always validated locally with the function tox_avatar_hash and ensured to match the image | ||
481 | * data, so this value can be safely used to compare with cached avatars. | ||
482 | * | ||
483 | * WARNING: users MUST treat all avatar image data received from another peer as untrusted and | ||
484 | * potentially malicious. The library only ensures that the data which arrived is the same the | ||
485 | * other user sent, and does not interpret or validate any image data. | ||
486 | */ | ||
487 | void tox_callback_avatar_data(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, uint8_t *, uint8_t *, uint32_t, | ||
488 | void *), void *userdata); | ||
489 | |||
490 | |||
491 | |||
347 | 492 | ||
348 | /**********ADVANCED FUNCTIONS (If you don't know what they do you can safely ignore them.) ************/ | 493 | /**********ADVANCED FUNCTIONS (If you don't know what they do you can safely ignore them.) ************/ |
349 | 494 | ||