Next revision | Previous revision |
developers:client_examples:echo_bot [2015/11/03 05:10] – created zero_one | developers:client_examples:echo_bot [2020/03/23 22:08] (current) – Minor code fixes nurupo |
---|
It's assumed that the reader is familiar with the C programming language. | It's assumed that the reader is familiar with the C programming language. |
| |
Before jumping to coding, we need to install toxcore library, which can be done by following [[https://github.com/irungentoo/toxcore/blob/master/INSTALL.md|installation instructions from toxcore's git repository]]. | Before jumping to coding, we need to install the toxcore library, which can be done by following the [[https://github.com/TokTok/c-toxcore/blob/master/INSTALL.md|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. | 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 [[https://github.com/irungentoo/toxcore/blob/master/toxcore/tox.h|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. | Although not required for this example, it's also a good idea to glance over [[https://github.com/TokTok/c-toxcore/blob/master/toxcore/tox.h|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 ===== | ===== Coding ===== |
==== Creating Tox instance ==== | ==== Creating Tox instance ==== |
| |
One starts their Tox adventure by using the ''tox_new'' function which creates a Tox instance and has the following signature: | One starts their Tox adventure by using the ''tox_new'' function, which creates a Tox instance and has the following signature: |
| |
<code C> | <code C> |
</code> | </code> |
| |
It takes a structure of options as the first argument and a error enum as the second argument. | 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, should it be using ipv4 or ipv6, which ports should it be using, and several other options. | 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 Tox instance's internal data, 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. | 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. | ''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 any 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. | 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 doesn't really tell what caused the failure, 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. | 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 ''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. This results in the following code: | 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: |
| |
<code C> | <code C> |
==== Setting name and status message ==== | ==== Setting name and status message ==== |
| |
When creating a new Tox instance, without loading data from the previous saved instance, by default the name and status message are empty and user status is set to none. Let's update name and status message fields. | 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. | We will use "Echo Bot" for the name and "Echoing your messages" for the status message. We will leave user status set to none. |
| |
User status is "a flavor" of 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. | 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 to this code: | Here is the corresponding code: |
<code C> | <code C> |
const char *name = "Echo Bot"; | const char *name = "Echo Bot"; |
==== Setting up network connection ==== | ==== Setting up network connection ==== |
| |
It's a bit early for us to go online, 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. | 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 centralized server model it's easy, there is central server to which everyone connects, but in peer-to-peer model every peer connects to another peer. Essentially, for a peer-to-peer software the notion of being "online" means to be connected to at least one other peer. The regular method for connecting to someone is to know their IP address and port, but that's neither convenient nor practical. Entering IP address and port of every person we want to communicate through Tox is cumbersome, humans are bad at remembering long sequences of numbers, and IP of whoever we want to communicate with might be changed by their ISP. In the centralized server model IP and port could 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 tor user X, but in our case there is no central server where such information could be stored, since Tox aims to be decentralized -- it goes against its 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 many peers. 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. | 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 32-byte random number as a user unique identifier, and not just any random number, but a public key of user's asymmetric key pair, generated using cryptographic random function, so the probability of two people generating the random number is extremely low, making it 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''. New key pair is generated every time ''tox_new'' function is called without loading previous Tox instance's data. | 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 become online in the context of Tox it means to connect to DHT, so let's do that. Connecting to DHT is the same problem all over -- we need to know IP and port of a DHT node, and in addition its public key, to be able to protect the communication, but in contrast to regular Tox peers, users using Tox to communicate with their friends, DHT nodes can be dedicated, i.e. have 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. In Tox, every peer is a DHT node, even our Echo Bot, so someone could connect to the DHT (bootstrap) through our bot, but using dedicated DHT bootstrap nodes is more reliable, since they are more likely to be online when we want bootstrap into DHT, thus that's what we will be bootstrapping off. Tox's wiki contains [[https://wiki.tox.chat/users/nodes|a list of DHT bootstrap nodes]] ran by Tox's community ([[https://wiki.tox.chat/users/runningnodes#daemonized_version|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: | 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 [[https://wiki.tox.chat/users/nodes|a list of DHT bootstrap nodes]] ran by Tox community ([[https://wiki.tox.chat/users/runningnodes#daemonized_version|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 ^ |
^ IP ^ Port ^ Public Key ^ | | 85.143.221.42 | 33445 | ''DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43'' | |
| 144.76.60.215 | 33445 | ''04119E835DF3E78BACF0F84235B300546AF8B936F035185E2A8E9E0A67C8924F'' | | | 2a04:ac00:1:9f00:5054:ff:fe01:becd | 33445 | ''DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43'' | |
| 23.226.230.47 | 33445 | ''A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074'' | | | 78.46.73.141 | 33445 | ''02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46'' | |
| 178.21.112.187 | 33445 | ''4B2C19E924972CB9B57732FB172F8A8604DE13EEDA2A6234E348983344B23057'' | | | 2a01:4f8:120:4091::3 | 33445 | ''02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46'' | |
| 195.154.119.113 | 33445 | ''E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354'' | | | tox.initramfs.io | 33445 | ''3F0A45A268367C1BEA652F258C85F4A66DA76BCAA667A49E770BCC4917AB6A25'' | |
| 192.210.149.121 | 33445 | ''F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67'' | | | 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: | We will use ''tox_boostrap'' function for bootstraping into DHT network. The function has following signature: |
</code> | </code> |
| |
It takes Tox instance as the fisrt argument, hostname or IP address of a node in text format as the second argument, 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. | 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 system's endianness, not in network byte order, as it's usually done in networking code. | 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 **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: | 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: |
| |
<code C> | <code C> |
uint16_t port; | uint16_t port; |
const char key_hex[TOX_PUBLIC_KEY_SIZE*2 + 1]; // 1 for null terminator | const char key_hex[TOX_PUBLIC_KEY_SIZE*2 + 1]; // 1 for null terminator |
unsigned char key_bin[TOX_PUBLIC_KEY_SIZE]; | |
} DHT_node; | } DHT_node; |
| |
DHT_node nodes[] = | DHT_node nodes[] = |
{ | { |
{"144.76.60.215", 33445, "04119E835DF3E78BACF0F84235B300546AF8B936F035185E2A8E9E0A67C8924F", {0}}, | {"85.143.221.42", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"}, |
{"23.226.230.47", 33445, "A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074", {0}}, | {"2a04:ac00:1:9f00:5054:ff:fe01:becd", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"}, |
{"178.21.112.187", 33445, "4B2C19E924972CB9B57732FB172F8A8604DE13EEDA2A6234E348983344B23057", {0}}, | {"78.46.73.141", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"}, |
{"195.154.119.113", 33445, "E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354", {0}}, | {"2a01:4f8:120:4091::3", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"}, |
{"192.210.149.121", 33445, "F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67", {0}} | {"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 ++) { | for (size_t i = 0; i < sizeof(nodes)/sizeof(DHT_node); i ++) { |
sodium_hex2bin(nodes[i].key_bin, sizeof(nodes[i].key_bin), | unsigned char key_bin[TOX_PUBLIC_KEY_SIZE]; |
nodes[i].key_hex, sizeof(nodes[i].key_hex)-1, NULL, NULL, NULL); | sodium_hex2bin(key_bin, sizeof(key_bin), nodes[i].key_hex, sizeof(nodes[i].key_hex)-1, |
tox_bootstrap(tox, nodes[i].ip, nodes[i].port, nodes[i].key_bin, NULL); | NULL, NULL, NULL); |
| tox_bootstrap(tox, nodes[i].ip, nodes[i].port, key_bin, NULL); |
} | } |
</code> | </code> |
| |
At this point we are not online yet, we 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. | 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 ==== | ==== Getting our Tox ID ==== |
| |
When we go online, how people will 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. | 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. | 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: | Because the Tox ID is 38 bytes, its hex representation is 76 characters long: |
</code> | </code> |
| |
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 a size of that buffer. We will ignore the return value. There is a more in-debth description of this function in [[https://download.libsodium.org/doc/helpers/index.html|libsodium's documentation]]. | 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 [[https://download.libsodium.org/doc/helpers/index.html|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 ''toupper'' function to fix that. | ''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: | Now we can print out our Tox ID using the following code: |
==== Registering for and handling events ==== | ==== 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 it's done in toxcore is by registering callback functions for specific events we are interested in and handling them in these callback functions. For example, there are ''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: | 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: |
| |
<code C> | <code C> |
void *user_data); | void *user_data); |
| |
void tox_callback_friend_request(Tox *tox, tox_friend_request_cb *callback, void *user_data); | void tox_callback_friend_request(Tox *tox, tox_friend_request_cb *callback); |
</code> | </code> |
| |
size_t length, void *user_data); | size_t length, void *user_data); |
| |
void tox_callback_friend_message(Tox *tox, tox_friend_message_cb *callback, void *user_data); | void tox_callback_friend_message(Tox *tox, tox_friend_message_cb *callback); |
</code> | </code> |
| |
Functions for registering callbacks have ''tox_callback_<event>'' name pattern, taking a pointer to a callback function, which are typedefed as ''tox_<event>_cb'', as the first argument, and any user-specified pointer as the second argument. 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 second argument, ''user_data'', is any pointer that we want to be passed back to us in the callback function. | 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, which is **not** null-terminated and, in fact, can contain nulls in the middle of it, as it's valid UTF-8. ''length'' is the length of the message. 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. | 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. | Let's register for these events, accepting all friend requests and replying to a message with exactly the same message. |
... | ... |
| |
tox_callback_friend_request(tox, friend_request_cb, NULL); | tox_callback_friend_request(tox, friend_request_cb); |
tox_callback_friend_message(tox, friend_message_cb, NULL); | tox_callback_friend_message(tox, friend_message_cb); |
</code> | </code> |
| |
We might also want to know when we become online, i.e. connected to DHT. This can be done through ''tox_callback_self_connection_status'', which is defined as: | 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: |
| |
<code C> | <code C> |
typedef void tox_self_connection_status_cb(Tox *tox, TOX_CONNECTION connection_status, void *user_data); | 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, void *user_data); | void tox_callback_self_connection_status(Tox *tox, tox_self_connection_status_cb *callback); |
</code> | </code> |
| |
... | ... |
| |
tox_callback_self_connection_status(tox, self_connection_status_cb, NULL); | tox_callback_self_connection_status(tox, self_connection_status_cb); |
</code> | </code> |
| |
==== Starting the event loop ==== | ==== 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 call our callbacks. Tox event loop consists of two functions: ''tox_iterate'' and ''tox_iteration_interval'': | 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'': |
| |
<code C> | <code C> |
void tox_iterate(Tox *tox); | void tox_iterate(Tox *tox, void *user_data); |
| |
uint32_t tox_iteration_interval(const Tox *tox); | uint32_t tox_iteration_interval(const Tox *tox); |
</code> | </code> |
| |
''tox_iterate'' is the function that handles all the networking and calls our callbacks. 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. | ''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: | 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: |
<code C> | <code C> |
while (1) { | while (1) { |
tox_iterate(tox); | tox_iterate(tox, NULL); |
usleep(tox_iteration_interval(tox) * 1000); | usleep(tox_iteration_interval(tox) * 1000); |
} | } |
==== Deinitializing Tox instance ==== | ==== 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 deinitialized the Tox instance, releasing resources and disconnecting us from the network: | 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: |
| |
<code C> | <code C> |
uint16_t port; | uint16_t port; |
const char key_hex[TOX_PUBLIC_KEY_SIZE*2 + 1]; | const char key_hex[TOX_PUBLIC_KEY_SIZE*2 + 1]; |
unsigned char key_bin[TOX_PUBLIC_KEY_SIZE]; | |
} DHT_node; | } DHT_node; |
| |
void friend_request_cb(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, | void friend_request_cb(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, |
void *user_data) | void *user_data) |
{ | { |
tox_friend_add_norequest(tox, public_key, NULL); | 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, | void friend_message_cb(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, |
size_t length, void *user_data) | size_t length, void *user_data) |
{ | { |
tox_friend_send_message(tox, friend_number, type, message, length, NULL); | tox_friend_send_message(tox, friend_number, type, message, length, NULL); |
DHT_node nodes[] = | DHT_node nodes[] = |
{ | { |
{"144.76.60.215", 33445, "04119E835DF3E78BACF0F84235B300546AF8B936F035185E2A8E9E0A67C8924F", {0}}, | {"85.143.221.42", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"}, |
{"23.226.230.47", 33445, "A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074", {0}}, | {"2a04:ac00:1:9f00:5054:ff:fe01:becd", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"}, |
{"178.21.112.187", 33445, "4B2C19E924972CB9B57732FB172F8A8604DE13EEDA2A6234E348983344B23057", {0}}, | {"78.46.73.141", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"}, |
{"195.154.119.113", 33445, "E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354", {0}}, | {"2a01:4f8:120:4091::3", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"}, |
{"192.210.149.121", 33445, "F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67", {0}} | {"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 ++) { | for (size_t i = 0; i < sizeof(nodes)/sizeof(DHT_node); i ++) { |
sodium_hex2bin(nodes[i].key_bin, sizeof(nodes[i].key_bin), | unsigned char key_bin[TOX_PUBLIC_KEY_SIZE]; |
nodes[i].key_hex, sizeof(nodes[i].key_hex)-1, NULL, NULL, NULL); | sodium_hex2bin(key_bin, sizeof(key_bin), nodes[i].key_hex, sizeof(nodes[i].key_hex)-1, |
tox_bootstrap(tox, nodes[i].ip, nodes[i].port, nodes[i].key_bin, NULL); | NULL, NULL, NULL); |
| tox_bootstrap(tox, nodes[i].ip, nodes[i].port, key_bin, NULL); |
} | } |
| |
printf("Tox ID: %s\n", tox_id_hex); | printf("Tox ID: %s\n", tox_id_hex); |
| |
tox_callback_friend_request(tox, friend_request_cb, NULL); | tox_callback_friend_request(tox, friend_request_cb); |
tox_callback_friend_message(tox, friend_message_cb, NULL); | tox_callback_friend_message(tox, friend_message_cb); |
| |
tox_callback_self_connection_status(tox, self_connection_status_cb, NULL); | tox_callback_self_connection_status(tox, self_connection_status_cb); |
| |
| printf("Connecting...\n"); |
| |
while (1) { | while (1) { |
tox_iterate(tox); | tox_iterate(tox, NULL); |
usleep(tox_iteration_interval(tox) * 1000); | usleep(tox_iteration_interval(tox) * 1000); |
} | } |
</code> | </code> |
| |
When you run it, you sould see something similar to the following: | When you run it, you should see something similar to the following: |
| |
<code bash> | <code bash> |
$ ./echo_bot | $ ./echo_bot |
Tox ID: 4F8E7814B40B22F7DBB8B18B8518EBB369F45DE6B40309F43F39AFECF340FD7624FC706CE668 | Tox ID: 4F8E7814B40B22F7DBB8B18B8518EBB369F45DE6B40309F43F39AFECF340FD7624FC706CE668 |
| Connecting... |
Online, using UDP | Online, using UDP |
</code> | </code> |
==== Data persistence ==== | ==== 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 ''tox_get_savedata*'' family of functions to store Tox instance's internal data on a disk every time Tox's internal data changes (in our case every time new friend is added) and later, when the bot is restarted, load the data back from the disk and set it in the ''Tox_Options'' struct when creating new Tox instance with ''tox_new''. | 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: | Here are ''tox_get_savedata*'' family of functions that we are interested in: |
</code> | </code> |
| |
The first function just tells us how big is the Tox instance's internal data in bytes and the second function writes these bytes in a provided buffer, which should be allocated by us. | 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_Options'' members we are interested in are: |
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 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 on a disk, we want to make sure that it won't get corrupted by system restart, power outage and other things. 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 necessary true. | 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_it''. | 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''. |
| |
<code C echo_bot.c> | <code C echo_bot.c> |
uint16_t port; | uint16_t port; |
const char key_hex[TOX_PUBLIC_KEY_SIZE*2 + 1]; | const char key_hex[TOX_PUBLIC_KEY_SIZE*2 + 1]; |
unsigned char key_bin[TOX_PUBLIC_KEY_SIZE]; | |
} DHT_node; | } DHT_node; |
| |
fseek(f, 0, SEEK_SET); | fseek(f, 0, SEEK_SET); |
| |
char *savedata = malloc(fsize); | uint8_t *savedata = malloc(fsize); |
| |
fread(savedata, fsize, 1, f); | fread(savedata, fsize, 1, f); |
{ | { |
size_t size = tox_get_savedata_size(tox); | size_t size = tox_get_savedata_size(tox); |
char *savedata = malloc(size); | uint8_t *savedata = malloc(size); |
tox_get_savedata(tox, savedata); | tox_get_savedata(tox, savedata); |
| |
DHT_node nodes[] = | DHT_node nodes[] = |
{ | { |
{"144.76.60.215", 33445, "04119E835DF3E78BACF0F84235B300546AF8B936F035185E2A8E9E0A67C8924F", {0}}, | {"85.143.221.42", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"}, |
{"23.226.230.47", 33445, "A09162D68618E742FFBCA1C2C70385E6679604B2D80EA6E84AD0996A1AC8A074", {0}}, | {"2a04:ac00:1:9f00:5054:ff:fe01:becd", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"}, |
{"178.21.112.187", 33445, "4B2C19E924972CB9B57732FB172F8A8604DE13EEDA2A6234E348983344B23057", {0}}, | {"78.46.73.141", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"}, |
{"195.154.119.113", 33445, "E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354", {0}}, | {"2a01:4f8:120:4091::3", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"}, |
{"192.210.149.121", 33445, "F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67", {0}} | {"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 ++) { | for (size_t i = 0; i < sizeof(nodes)/sizeof(DHT_node); i ++) { |
sodium_hex2bin(nodes[i].key_bin, sizeof(nodes[i].key_bin), | unsigned char key_bin[TOX_PUBLIC_KEY_SIZE]; |
nodes[i].key_hex, sizeof(nodes[i].key_hex)-1, NULL, NULL, NULL); | sodium_hex2bin(key_bin, sizeof(key_bin), nodes[i].key_hex, sizeof(nodes[i].key_hex)-1, |
tox_bootstrap(tox, nodes[i].ip, nodes[i].port, nodes[i].key_bin, NULL); | NULL, NULL, NULL); |
| tox_bootstrap(tox, nodes[i].ip, nodes[i].port, key_bin, NULL); |
} | } |
} | } |
| |
void friend_request_cb(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, | void friend_request_cb(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, |
void *user_data) | void *user_data) |
{ | { |
tox_friend_add_norequest(tox, public_key, NULL); | 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, | void friend_message_cb(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, |
size_t length, void *user_data) | size_t length, void *user_data) |
{ | { |
tox_friend_send_message(tox, friend_number, type, message, length, NULL); | tox_friend_send_message(tox, friend_number, type, message, length, NULL); |
print_tox_id(tox); | print_tox_id(tox); |
| |
tox_callback_friend_request(tox, friend_request_cb, NULL); | tox_callback_friend_request(tox, friend_request_cb); |
tox_callback_friend_message(tox, friend_message_cb, NULL); | tox_callback_friend_message(tox, friend_message_cb); |
| |
tox_callback_self_connection_status(tox, self_connection_status_cb, NULL); | tox_callback_self_connection_status(tox, self_connection_status_cb); |
| |
update_savedata_file(tox); | update_savedata_file(tox); |
| |
| printf("Connecting...\n"); |
| |
while (1) { | while (1) { |
tox_iterate(tox); | tox_iterate(tox, NULL); |
usleep(tox_iteration_interval(tox) * 1000); | usleep(tox_iteration_interval(tox) * 1000); |
} | } |
} | } |
</code> | </code> |
| |
| |
Now Echo Bot's Tox ID and friend list should be persistent across restarts. | 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: |
| |
| <code bash> |
| gcc -o echo_bot echo_bot.c -std=gnu99 -lsodium -I /usr/local/include/ -ltoxcore |
| </code> |