This example is aimed at introducing people to basics of using toxcore C API. It's assumed that the reader is familiar with the C programming language.
Before jumping to coding, we need to install the toxcore library, which can be done by following the installation instructions from toxcore's git repository.
For the sake of simplicity, we will use a couple of helper functions from the sodium library, which is a dependency of toxcore, so it should be installed along with it.
Although not required for this example, it's also a good idea to glance over tox.h file, which is the Tox public header file, to familiarize yourself with the API and concepts used in it. It's very well documented, so you can figure out how to use toxcore by simply reading through it.
One starts their Tox adventure by using the tox_new
function, which creates a Tox instance and has the following signature:
Tox *tox_new(const struct Tox_Options *options, TOX_ERR_NEW *error);
It takes a structure of options as the first argument and an error enum as the second argument.
The Tox_Options
struct can be used to specify such options as whether toxcore should be using a proxy for routing the network traffic, whether to use ipv4 or ipv4 and ipv6, which ports should it be using, and several other options.
Toxcore provides a way to get and store internal data of the Tox instance, which includes the asymmetric key pair (our identity), name, status message, friend list, list of sent friend requests and some other data. Loading off the stored internal Tox instance data is also one of many options available through Tox_Options
struct.
Tox_Options
can be set to NULL
, in which case default options will be used.
The TOX_ERR_NEW
enum is used to indicate if an error has occurred during the function execution. It can be set to NULL
, in which case we will just ignore the error. Such way of error reporting – allowing user to pass a pointer to an error enum as the last function argument, in which the function will store an error code – is widely used throughout toxcore's public API.
In addition to the error enum, some function also use the unused range of return types to report a failure, e.g. return a NULL
pointer or a negative number, but it's not always possible to have part of the return type's range to be unused, so the presence of such failure reporting varies from function to function. The term “failure” is used here, in contrast to the previously used “error”, because such reporting does not specify what caused the failure, i.e. what the error is, it just tells whether it has failed or not, while the error enum can tell us in more detail what exactly was the error that caused the failure.
For the purposes of our echo bot, the default options of the tox_new
function is all we want. We won't be doing any error handling in this example in order to keep it simple, so we will just ignore the error value and won't be doing much, if any, error checking throughout this example. This results in the following code:
Tox *tox = tox_new(NULL, NULL);
When creating a new Tox instance, unless you are creating it off data saved from the previous Tox instance, by default the name and status message are empty and user status is set to none. Let's update the name and status message fields.
We will use “Echo Bot” for the name and “Echoing your messages” for the status message. We will leave user status set to none.
The user status is a “flavor” of the online status, which can be set to any of TOX_USER_STATUS_AWAY
, TOX_USER_STATUS_BUSY
and TOX_USER_STATUS_NONE
, which will mark us as being away, busy or simply online respectably.
Here is the corresponding code:
const char *name = "Echo Bot"; tox_self_set_name(tox, name, strlen(name), NULL); const char *status_message = "Echoing your messages"; tox_self_set_status_message(tox, status_message, strlen(status_message), NULL); // if we wanted to change our user status to something else, we would do it like this // tox_self_set_status(tox, TOX_USER_STATUS_BUSY);
It's a bit early for us to go online, as there are still a few things we need to do, but let's set up network connection first as it will make things a bit easier to explain later. Note that we are not going online just yet; we are just setting up a network connection.
Let's try to think about what it means in the context of peer-to-peer application to be online. In a centralized server model it's easy: there is a central server to which everyone connects. However, in a distributed peer-to-peer network such as the one Tox uses, peers connect to one another without using centralized servers. Essentially, for a peer-to-peer software the notion of being “online” means to be connected to at least one other peer. The typical method for connecting to someone is to know their IP address and port, but that's neither convenient nor practical. Entering the IP address and port of every person we want to communicate with through Tox is cumbersome, and the IP of whoever we want to communicate with can change at any given time. In the centralized server model, the IP and port can be stored on the central server and associated with some unique identifier, such as username or email, so that you could query the central server of the IP and port of user under username X and the server would give you the most recent IP and port for user X. However in our case there is no central server where such information could be stored, as centralization goes against the Tox philosophy. Luckily, there is a solution that allows the store of IP and port of a peer associated with peer's unique identifier in a decentralized fashion – DHT (Distributed Hash Table). DHT is essentially a table of key-value pairs that is distributed among all the peers in the network. So, to be able to connect to Tox users that we want to communicate with, we need first to connect to DHT network and then look up these Tox users' IP and port using their unique identifiers.
Tox uses a 32-byte random number as a user unique identifier, and not just any random number, but a public key of the user's permanent asymmetric key pair. The key pair is generated using a cryptographic random function, so the probability of two people generating the random number is extremely low, making it a good unique identifier. The 32-byte public key is textually represented as 64 upper case hex characters, each 2 characters encoding a single byte, e.g. F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67
. A new key pair is generated every time tox_new
function is called without loading a previous Tox instance's data.
Now we know that to be online in the context of Tox it means to connect to the DHT, so let's do that. First we need to know IP and port of a DHT node, as well as its public key, to be able to protect the communication. In Tox, every peer who has UDP connections enabled is a DHT node, even our Echo Bot. This means that it is possible for someone to connect to the DHT (i.e. to bootstrap) through our bot. However, in contrast to regular Tox peers, users using Tox to communicate with their friends, DHT nodes can be dedicated, i.e. have a more permanent IP address and be online most of the time, which means that knowing IP, port and public key of just few nodes is generally enough to be able to always connect to the DHT network. Because using dedicated DHT nodes to connect to the DHT is more reliable, since they are more likely to be online when we want to bootstrap into the DHT, that's what we will be using. Tox's wiki contains a list of DHT bootstrap nodes ran by Tox community (you can run one and be on that list too!). To connect to the DHT network we will use the following nodes taken from the wiki page:
IP | Port | Public Key |
---|---|---|
85.143.221.42 | 33445 | DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43 |
2a04:ac00:1:9f00:5054:ff:fe01:becd | 33445 | DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43 |
78.46.73.141 | 33445 | 02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46 |
2a01:4f8:120:4091::3 | 33445 | 02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46 |
tox.initramfs.io | 33445 | 3F0A45A268367C1BEA652F258C85F4A66DA76BCAA667A49E770BCC4917AB6A25 |
tox2.abilinski.com | 33445 | 7A6098B590BDC73F9723FC59F82B3F9085A64D1B213AAF8E610FD351930D052D |
205.185.115.131 | 53 | 3091C6BEB2A993F1C6300C16549FABA67098FF3D62C6D253828B531470B53D68 |
tox.kurnevsky.net | 33445 | 82EF82BA33445A1F91A7DB27189ECFC0C013E06E3DA71F588ED692BED625EC23 |
We will use tox_boostrap
function for bootstraping into DHT network. The function has following signature:
bool tox_bootstrap(Tox *tox, const char *address, uint16_t port, const uint8_t *public_key, TOX_ERR_BOOTSTRAP *error);
It takes Tox instance as the first argument, the hostname or IP address of a node in text format as the second argument, the port number of the node in system endianness as the third argument, a binary representation of the public key of the node as the fourth argument and an error enum as the fifth argument. The function returns true on success and false on failure.
Note that the port number should be passed in the system's endianness, not in network byte order, as it's usually done in networking code.
Also note that we need to pass the binary representation of the public key, but we have only hex representation of it. To solve this, we will use libsodium's sodium_hex2bin
helper function, which converts the hex representation into the binary representation:
int sodium_hex2bin(unsigned char * const bin, const size_t bin_maxlen, const char * const hex, const size_t hex_len, const char * const ignore, size_t * const bin_len, const char ** const hex_end);
The first two arguments are pointer to a buffer where the binary representation should be written and size of that buffer, the next two arguments are pointer to the hex representation buffer and size of that buffer. We will ignore the last three arguments and the return value. There is a more in-depth description of this function in libsodium's documentation.
With this, we arrive at the following code for bootstrapping:
typedef struct DHT_node { const char *ip; uint16_t port; const char key_hex[TOX_PUBLIC_KEY_SIZE*2 + 1]; // 1 for null terminator } DHT_node; ... DHT_node nodes[] = { {"85.143.221.42", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"}, {"2a04:ac00:1:9f00:5054:ff:fe01:becd", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"}, {"78.46.73.141", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"}, {"2a01:4f8:120:4091::3", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"}, {"tox.initramfs.io", 33445, "3F0A45A268367C1BEA652F258C85F4A66DA76BCAA667A49E770BCC4917AB6A25"}, {"tox2.abilinski.com", 33445, "7A6098B590BDC73F9723FC59F82B3F9085A64D1B213AAF8E610FD351930D052D"}, {"205.185.115.131", 53, "3091C6BEB2A993F1C6300C16549FABA67098FF3D62C6D253828B531470B53D68"}, {"tox.kurnevsky.net", 33445, "82EF82BA33445A1F91A7DB27189ECFC0C013E06E3DA71F588ED692BED625EC23"} }; for (size_t i = 0; i < sizeof(nodes)/sizeof(DHT_node); i ++) { unsigned char key_bin[TOX_PUBLIC_KEY_SIZE]; sodium_hex2bin(key_bin, sizeof(key_bin), nodes[i].key_hex, sizeof(nodes[i].key_hex)-1, NULL, NULL, NULL); tox_bootstrap(tox, nodes[i].ip, nodes[i].port, key_bin, NULL); }
At this point we are not online yet. We still need to start toxcore's event loop for that to happen. But before doing that, there are a few little things that need to be taken care of.
When we go online, how will people be able to add our Echo Bot to their friend list? They need to know our unique identifier for that. The unique identifier used for adding friends is a bit different from the one used when adding DHT nodes. It's still the 32-byte public key, but there are additional 6 bytes appended at the end of it. This unique identifier is referred in toxcore API as “friend address” or “Tox address”. People in Tox community usually call it “Tox ID”, which is what we will refer to it as for here on out.
The last 6 bytes at the end of Tox ID are used for spam prevention and can be changed on Tox ID holder's whim. If we get many spammy friend requests, we can just change last 6 bytes of our Tox ID and the friend requests for Tox ID with the previous 6 bytes won't get through anymore. In order to receive a friend request, our new Tox ID should be used (the one with updated last 6 bytes). These 6 bytes can be thought of as a password, which should be known in order to be able to send us a friend request. Technically, we have direct control only of the first 4 bytes out of these 6 spam prevention bytes. These first 4 bytes are called “nospam”. The remaining 2 bytes are just a checksum, used for a simple check of whether Tox ID is valid. The first byte of the checksum is a XOR of all even bytes if the public key and nospam, while the second byte is XOR of all odd bytes of them.
Because the Tox ID is 38 bytes, its hex representation is 76 characters long:
We will use tox_self_get_address
function to get our Tox ID, or in toxcore's terms, our Tox/friend address. The function has following signature:
void tox_self_get_address(const Tox *tox, uint8_t *address);
It takes Tox instance as the first argument and a pointer to a buffer of at least TOX_ADDRESS_SIZE
bytes as the second argument, which is where our Tox ID will be written in the binary representation.
Note that the Tox ID will be written in the binary representation, so we need to convert it to the hex representation, which can be printed out and shared with our friends. We will use libsodium's sodium_bin2hex
helper function, which converts the binary representation into the hex representation:
char *sodium_bin2hex(char * const hex, const size_t hex_maxlen, const unsigned char * const bin, const size_t bin_len);
The first two arguments are a pointer to a buffer where the hex representation should be written and a size of that buffer. The next two arguments are a pointer to the binary representation buffer and the size of that buffer. We will ignore the return value. There is a more in-depth description of this function in libsodium's documentation.
sodium_bin2hex
uses lowercase letter for the hex representation, but Tox IDs are generally represented in upper case, so we will use the toupper
function to fix that.
Now we can print out our Tox ID using the following code:
uint8_t tox_id_bin[TOX_ADDRESS_SIZE]; tox_self_get_address(tox, tox_id_bin); char tox_id_hex[TOX_ADDRESS_SIZE*2 + 1]; sodium_bin2hex(tox_id_hex, sizeof(tox_id_hex), tox_id_bin, sizeof(tox_id_bin)); for (size_t i = 0; i < sizeof(tox_id_hex)-1; i ++) { tox_id_hex[i] = toupper(tox_id_hex[i]); } printf("Tox ID: %s\n", tox_id_hex);
When someone friends our Echo Bot or sends a message to it, we want the bot to respond appropriately. The way this is done in toxcore is by registering callback functions for specific events that we are interested in, and handling them in these callback functions. For example, there are the tox_callback_friend_request
and tox_callback_friend_message
callback registering functions – the first is for handling received friend requests and the second is for handing received messages. They have the following signature:
typedef void tox_friend_request_cb(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, void *user_data); void tox_callback_friend_request(Tox *tox, tox_friend_request_cb *callback);
and
typedef void tox_friend_message_cb(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length, void *user_data); void tox_callback_friend_message(Tox *tox, tox_friend_message_cb *callback);
Functions for registering callbacks have the tox_callback_<event>
name pattern, taking a pointer to a callback function, which are typedef'd as tox_<event>_cb
. The callback function handling the event can be changed by registering for the same event with a different callback function specified. It's also possible to unregister by passing NULL
for the function pointer argument.
The arguments of callback functions have pretty self-explanatory names. message
is a UTF-8 encoded string. length
is the length of the message in bytes. We already know what public_key
is. friend_number
is just a number used for referring to an existing friend, which toxcore might reuse if a friend is deleted and another one is added. TOX_MESSAGE_TYPE
does what its name says – tells us what type of message we are dealing with. user_data
is the pointer that we will later ask toxcore to pass back into our callbacks, it's generally used for passing context data.
Let's register for these events, accepting all friend requests and replying to a message with exactly the same message.
Accepting a friend request is done by tox_friend_add_norequest
and sending a message is done by tox_friend_send_message
:
uint32_t tox_friend_add_norequest(Tox *tox, const uint8_t *public_key, TOX_ERR_FRIEND_ADD *error); uint32_t tox_friend_send_message(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length, TOX_ERR_FRIEND_SEND_MESSAGE *error);
It's just a matter of passing the arguments we receive in callback functions to tox_friend_add_norequest
and tox_friend_send_message
:
void friend_request_cb(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, void *user_data) { tox_friend_add_norequest(tox, public_key, NULL); } void friend_message_cb(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length, void *user_data) { tox_friend_send_message(tox, friend_number, type, message, length, NULL); } ... tox_callback_friend_request(tox, friend_request_cb); tox_callback_friend_message(tox, friend_message_cb);
We might also want to know when we come online, i.e. connect to DHT. This can be done through tox_callback_self_connection_status
, which is defined as:
typedef void tox_self_connection_status_cb(Tox *tox, TOX_CONNECTION connection_status, void *user_data); void tox_callback_self_connection_status(Tox *tox, tox_self_connection_status_cb *callback);
Here is how we would use it:
void self_connection_status_cb(Tox *tox, TOX_CONNECTION connection_status, void *user_data) { switch (connection_status) { case TOX_CONNECTION_NONE: printf("Offline\n"); break; case TOX_CONNECTION_TCP: printf("Online, using TCP\n"); break; case TOX_CONNECTION_UDP: printf("Online, using UDP\n"); break; } } ... tox_callback_self_connection_status(tox, self_connection_status_cb);
Now our Echo Bot is ready to go online. To do so, we need to start toxcore's event loop, which will handle networking and trigger our callbacks. The Tox event loop consists of two functions: tox_iterate
and tox_iteration_interval
:
void tox_iterate(Tox *tox, void *user_data); uint32_t tox_iteration_interval(const Tox *tox);
tox_iterate
is the function that handles all the networking and calls our callbacks with the user_data
pointer we specify here. It has to be called periodically for toxcore to keep doing the networking. If we don't call it often enough, we will go offline. tox_iteration_interval
function tells us how often tox_iterate
should be called – how much time in milliseconds should pass from previous tox_iterate
call to the next tox_iterate
call, for optional performance.
So, all we need to do is to call tox_interval
every tox_iteration_interval
milliseconds. We use usleep
function for sleeping, assuming that we are on POSIX-compliant system:
while (1) { tox_iterate(tox, NULL); usleep(tox_iteration_interval(tox) * 1000); }
Our Tox event loop is an infinite while loop, so anything we put after it won't get executed. But just for the sake of the example we do a tox_kill
call, which is the counter part of tox_new
– it de-initialized the Tox instance, releasing resources and disconnecting us from the network:
void tox_kill(Tox *tox);
And it's used as:
tox_kill(tox);
With this our Echo Bot is complete. If we put all of out parts together, we get the following code:
#include <ctype.h> #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sodium/utils.h> #include <tox/tox.h> typedef struct DHT_node { const char *ip; uint16_t port; const char key_hex[TOX_PUBLIC_KEY_SIZE*2 + 1]; } DHT_node; void friend_request_cb(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, void *user_data) { tox_friend_add_norequest(tox, public_key, NULL); } void friend_message_cb(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length, void *user_data) { tox_friend_send_message(tox, friend_number, type, message, length, NULL); } void self_connection_status_cb(Tox *tox, TOX_CONNECTION connection_status, void *user_data) { switch (connection_status) { case TOX_CONNECTION_NONE: printf("Offline\n"); break; case TOX_CONNECTION_TCP: printf("Online, using TCP\n"); break; case TOX_CONNECTION_UDP: printf("Online, using UDP\n"); break; } } int main() { Tox *tox = tox_new(NULL, NULL); const char *name = "Echo Bot"; tox_self_set_name(tox, name, strlen(name), NULL); const char *status_message = "Echoing your messages"; tox_self_set_status_message(tox, status_message, strlen(status_message), NULL); DHT_node nodes[] = { {"85.143.221.42", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"}, {"2a04:ac00:1:9f00:5054:ff:fe01:becd", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"}, {"78.46.73.141", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"}, {"2a01:4f8:120:4091::3", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"}, {"tox.initramfs.io", 33445, "3F0A45A268367C1BEA652F258C85F4A66DA76BCAA667A49E770BCC4917AB6A25"}, {"tox2.abilinski.com", 33445, "7A6098B590BDC73F9723FC59F82B3F9085A64D1B213AAF8E610FD351930D052D"}, {"205.185.115.131", 53, "3091C6BEB2A993F1C6300C16549FABA67098FF3D62C6D253828B531470B53D68"}, {"tox.kurnevsky.net", 33445, "82EF82BA33445A1F91A7DB27189ECFC0C013E06E3DA71F588ED692BED625EC23"} }; for (size_t i = 0; i < sizeof(nodes)/sizeof(DHT_node); i ++) { unsigned char key_bin[TOX_PUBLIC_KEY_SIZE]; sodium_hex2bin(key_bin, sizeof(key_bin), nodes[i].key_hex, sizeof(nodes[i].key_hex)-1, NULL, NULL, NULL); tox_bootstrap(tox, nodes[i].ip, nodes[i].port, key_bin, NULL); } uint8_t tox_id_bin[TOX_ADDRESS_SIZE]; tox_self_get_address(tox, tox_id_bin); char tox_id_hex[TOX_ADDRESS_SIZE*2 + 1]; sodium_bin2hex(tox_id_hex, sizeof(tox_id_hex), tox_id_bin, sizeof(tox_id_bin)); for (size_t i = 0; i < sizeof(tox_id_hex)-1; i ++) { tox_id_hex[i] = toupper(tox_id_hex[i]); } printf("Tox ID: %s\n", tox_id_hex); tox_callback_friend_request(tox, friend_request_cb); tox_callback_friend_message(tox, friend_message_cb); tox_callback_self_connection_status(tox, self_connection_status_cb); printf("Connecting...\n"); while (1) { tox_iterate(tox, NULL); usleep(tox_iteration_interval(tox) * 1000); } tox_kill(tox); return 0; }
This code can be saved into echo_bot.c
and compiled with:
gcc -o echo_bot echo_bot.c -std=gnu99 -lsodium -I /usr/local/include/ -ltoxcore
When you run it, you should see something similar to the following:
$ ./echo_bot
Tox ID: 4F8E7814B40B22F7DBB8B18B8518EBB369F45DE6B40309F43F39AFECF340FD7624FC706CE668
Connecting...
Online, using UDP
Note that it takes several seconds for it to connect to the network and print the connection message.
Now you can test the bot by friending this Tox ID in any Tox client and sending messages to it.
The bot can be further improved my making it preserve its Tox ID and friend list across restarts. This can be achieved by using the tox_get_savedata*
family of functions to store Tox instance's internal data to disk every time Tox's internal data changes (in our case every time new friend is added). Later when the bot is restarted, we can load the data back from the disk and set it in the Tox_Options
struct when creating new Tox instance with tox_new
.
Here are tox_get_savedata*
family of functions that we are interested in:
size_t tox_get_savedata_size(const Tox *tox); void tox_get_savedata(const Tox *tox, uint8_t *savedata);
The first function just tells us how big the Tox instance's internal data is in bytes, and the second function writes these bytes to the provided buffer, which should be allocated by us.
Tox_Options
members we are interested in are:
TOX_SAVEDATA_TYPE savedata_type; const uint8_t *savedata_data; size_t savedata_length;
Since we want to store the entire Tox instance's internal data, we will set savedata_type
to TOX_SAVEDATA_TYPE_TOX_SAVE
. The last two struct members listed need to be set to the things we receive from tox_get_savedata*
family of functions.
Since we will be storing the data to disk, we want to make sure that it won't get corrupted by a system restart, power outage etc. On Linux, file corruption can be prevented by writing data to a temporary file, followed by renaming the temporary file to the file we originally wanted to store this data as. Note that this behaviour varies from system to system, so if you are not using Linux this might not be necessarily true.
Applying all mentioned, we add two new functions to our code: create_tox
and update_savedata_file
. create_tox
creates Tox instance using the previously stored internal data, if available, or plain new Tox instance otherwise. update_savedata_file
just writes Tox internal data to a file. We also refactored DHT boostrapping and Tox ID printing into separate functions, bootstrap
and print_tox_id
.
#include <ctype.h> #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sodium/utils.h> #include <tox/tox.h> typedef struct DHT_node { const char *ip; uint16_t port; const char key_hex[TOX_PUBLIC_KEY_SIZE*2 + 1]; } DHT_node; const char *savedata_filename = "savedata.tox"; const char *savedata_tmp_filename = "savedata.tox.tmp"; Tox *create_tox() { Tox *tox; struct Tox_Options options; tox_options_default(&options); FILE *f = fopen(savedata_filename, "rb"); if (f) { fseek(f, 0, SEEK_END); long fsize = ftell(f); fseek(f, 0, SEEK_SET); uint8_t *savedata = malloc(fsize); fread(savedata, fsize, 1, f); fclose(f); options.savedata_type = TOX_SAVEDATA_TYPE_TOX_SAVE; options.savedata_data = savedata; options.savedata_length = fsize; tox = tox_new(&options, NULL); free(savedata); } else { tox = tox_new(&options, NULL); } return tox; } void update_savedata_file(const Tox *tox) { size_t size = tox_get_savedata_size(tox); uint8_t *savedata = malloc(size); tox_get_savedata(tox, savedata); FILE *f = fopen(savedata_tmp_filename, "wb"); fwrite(savedata, size, 1, f); fclose(f); rename(savedata_tmp_filename, savedata_filename); free(savedata); } void bootstrap(Tox *tox) { DHT_node nodes[] = { {"85.143.221.42", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"}, {"2a04:ac00:1:9f00:5054:ff:fe01:becd", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"}, {"78.46.73.141", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"}, {"2a01:4f8:120:4091::3", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"}, {"tox.initramfs.io", 33445, "3F0A45A268367C1BEA652F258C85F4A66DA76BCAA667A49E770BCC4917AB6A25"}, {"tox2.abilinski.com", 33445, "7A6098B590BDC73F9723FC59F82B3F9085A64D1B213AAF8E610FD351930D052D"}, {"205.185.115.131", 53, "3091C6BEB2A993F1C6300C16549FABA67098FF3D62C6D253828B531470B53D68"}, {"tox.kurnevsky.net", 33445, "82EF82BA33445A1F91A7DB27189ECFC0C013E06E3DA71F588ED692BED625EC23"} }; for (size_t i = 0; i < sizeof(nodes)/sizeof(DHT_node); i ++) { unsigned char key_bin[TOX_PUBLIC_KEY_SIZE]; sodium_hex2bin(key_bin, sizeof(key_bin), nodes[i].key_hex, sizeof(nodes[i].key_hex)-1, NULL, NULL, NULL); tox_bootstrap(tox, nodes[i].ip, nodes[i].port, key_bin, NULL); } } void print_tox_id(Tox *tox) { uint8_t tox_id_bin[TOX_ADDRESS_SIZE]; tox_self_get_address(tox, tox_id_bin); char tox_id_hex[TOX_ADDRESS_SIZE*2 + 1]; sodium_bin2hex(tox_id_hex, sizeof(tox_id_hex), tox_id_bin, sizeof(tox_id_bin)); for (size_t i = 0; i < sizeof(tox_id_hex)-1; i ++) { tox_id_hex[i] = toupper(tox_id_hex[i]); } printf("Tox ID: %s\n", tox_id_hex); } void friend_request_cb(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, void *user_data) { tox_friend_add_norequest(tox, public_key, NULL); update_savedata_file(tox); } void friend_message_cb(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length, void *user_data) { tox_friend_send_message(tox, friend_number, type, message, length, NULL); } void self_connection_status_cb(Tox *tox, TOX_CONNECTION connection_status, void *user_data) { switch (connection_status) { case TOX_CONNECTION_NONE: printf("Offline\n"); break; case TOX_CONNECTION_TCP: printf("Online, using TCP\n"); break; case TOX_CONNECTION_UDP: printf("Online, using UDP\n"); break; } } int main() { Tox *tox = create_tox(); const char *name = "Echo Bot"; tox_self_set_name(tox, name, strlen(name), NULL); const char *status_message = "Echoing your messages"; tox_self_set_status_message(tox, status_message, strlen(status_message), NULL); bootstrap(tox); print_tox_id(tox); tox_callback_friend_request(tox, friend_request_cb); tox_callback_friend_message(tox, friend_message_cb); tox_callback_self_connection_status(tox, self_connection_status_cb); update_savedata_file(tox); printf("Connecting...\n"); while (1) { tox_iterate(tox, NULL); usleep(tox_iteration_interval(tox) * 1000); } tox_kill(tox); return 0; }
Now Echo Bot's Tox ID and friend list should be persistent across restarts.
Compile it using the same command as the previous code snippet:
gcc -o echo_bot echo_bot.c -std=gnu99 -lsodium -I /usr/local/include/ -ltoxcore