Echo Bot Example

Getting started

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.

Coding

Creating Tox instance

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);

Setting name and status message

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);

Setting up network connection

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.

Getting our Tox ID

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);

Registering for and handling events

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);

Starting the event loop

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);
}

Deinitializing Tox instance

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);

Complete code

With this our Echo Bot is complete. If we put all of out parts together, we get the following code:

echo_bot.c
#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.

Further improvements

Data persistence

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.

echo_bot.c
#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
Print/export