Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
developers:client_examples:echo_bot [2015/11/03 06:38] – Tox IDs are actually even longer than ip+port numbers nurupo | developers:client_examples:echo_bot [2020/03/23 22:08] (current) – Minor code fixes nurupo | ||
---|---|---|---|
Line 6: | Line 6: | ||
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 the toxcore library, which can be done by following the [[https:// | + | Before jumping to coding, we need to install the toxcore library, which can be done by following the [[https:// |
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:// | + | Although not required for this example, it's also a good idea to glance over [[https:// |
===== Coding ===== | ===== Coding ===== | ||
Line 24: | Line 24: | ||
It takes a structure of options as the first argument and an 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 '' | + | The '' |
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 '' | 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 '' | ||
'' | '' | ||
Line 40: | Line 40: | ||
==== Setting name and status message ==== | ==== Setting name and status message ==== | ||
- | When creating a new Tox instance, | + | When creating a new Tox instance, |
We will use "Echo Bot" for the name and " | We will use "Echo Bot" for the name and " | ||
Line 62: | Line 62: | ||
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. | 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, | + | 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, |
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. '' | 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. '' | ||
- | 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. 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. | + | 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. |
- | + | ^ IP ^ Port | |
- | ^ IP ^ Port ^ Public Key ^ | + | | 85.143.221.42 | 33445 | '' |
- | | 144.76.60.215 | 33445 | '' | + | | 2a04: |
- | | 23.226.230.47 | 33445 | '' | + | | 78.46.73.141 |
- | | 178.21.112.187 | 33445 | '' | + | | 2a01: |
- | | 195.154.119.113 | + | | tox.initramfs.io |
- | | 192.210.149.121 | 33445 | '' | + | | tox2.abilinski.com |
+ | | 205.185.115.131 | ||
+ | | tox.kurnevsky.net | 33445 | '' | ||
We will use '' | We will use '' | ||
Line 104: | Line 106: | ||
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; | ||
Line 111: | Line 112: | ||
DHT_node nodes[] = | DHT_node nodes[] = | ||
{ | { | ||
- | {"144.76.60.215", | + | {"85.143.221.42", |
- | {"23.226.230.47", | + | |
- | {"178.21.112.187", | + | {"78.46.73.141", |
- | {"195.154.119.113", 33445, "E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354", {0}}, | + | |
- | {"192.210.149.121", | + | {"tox.initramfs.io", |
+ | {"tox2.abilinski.com", | ||
+ | {"205.185.115.131", | ||
+ | | ||
}; | }; | ||
for (size_t i = 0; i < sizeof(nodes)/ | for (size_t i = 0; i < sizeof(nodes)/ | ||
- | sodium_hex2bin(nodes[i].key_bin, sizeof(nodes[i].key_bin), | + | |
- | nodes[i].key_hex, | + | |
- | tox_bootstrap(tox, | + | NULL, NULL, NULL); |
+ | tox_bootstrap(tox, | ||
} | } | ||
</ | </ | ||
Line 180: | Line 185: | ||
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); |
</ | </ | ||
Line 189: | Line 194: | ||
| | ||
- | 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); |
</ | </ | ||
- | Functions for registering callbacks have the '' | + | Functions for registering callbacks have the '' |
- | The arguments of callback functions have pretty self-explanatory names. '' | + | The arguments of callback functions have pretty self-explanatory names. '' |
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. | ||
Line 224: | Line 229: | ||
... | ... | ||
- | tox_callback_friend_request(tox, | + | tox_callback_friend_request(tox, |
- | tox_callback_friend_message(tox, | + | tox_callback_friend_message(tox, |
</ | </ | ||
Line 233: | Line 238: | ||
typedef void tox_self_connection_status_cb(Tox *tox, TOX_CONNECTION connection_status, | typedef void tox_self_connection_status_cb(Tox *tox, TOX_CONNECTION connection_status, | ||
- | 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); |
</ | </ | ||
Line 256: | Line 261: | ||
... | ... | ||
- | tox_callback_self_connection_status(tox, | + | tox_callback_self_connection_status(tox, |
</ | </ | ||
Line 264: | Line 269: | ||
<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); | ||
</ | </ | ||
- | '' | + | '' |
So, all we need to do is to call '' | So, all we need to do is to call '' | ||
Line 275: | Line 280: | ||
<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); | ||
} | } | ||
Line 282: | Line 287: | ||
==== 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 '' | + | 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 '' |
<code C> | <code C> | ||
Line 314: | Line 319: | ||
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, | void friend_request_cb(Tox *tox, const uint8_t *public_key, | ||
- | void *user_data) | + | void *user_data) |
{ | { | ||
tox_friend_add_norequest(tox, | tox_friend_add_norequest(tox, | ||
Line 324: | Line 328: | ||
void friend_message_cb(Tox *tox, uint32_t friend_number, | void friend_message_cb(Tox *tox, uint32_t friend_number, | ||
- | size_t length, void *user_data) | + | size_t length, void *user_data) |
{ | { | ||
tox_friend_send_message(tox, | tox_friend_send_message(tox, | ||
Line 356: | Line 360: | ||
DHT_node nodes[] = | DHT_node nodes[] = | ||
{ | { | ||
- | {"144.76.60.215", | + | {"85.143.221.42", |
- | {"23.226.230.47", | + | |
- | {"178.21.112.187", | + | {"78.46.73.141", |
- | {"195.154.119.113", 33445, "E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354", {0}}, | + | |
- | {"192.210.149.121", | + | {"tox.initramfs.io", |
+ | {"tox2.abilinski.com", | ||
+ | {"205.185.115.131", | ||
+ | | ||
}; | }; | ||
for (size_t i = 0; i < sizeof(nodes)/ | for (size_t i = 0; i < sizeof(nodes)/ | ||
- | sodium_hex2bin(nodes[i].key_bin, sizeof(nodes[i].key_bin), | + | |
- | nodes[i].key_hex, | + | |
- | tox_bootstrap(tox, | + | NULL, NULL, NULL); |
+ | tox_bootstrap(tox, | ||
} | } | ||
Line 381: | Line 389: | ||
printf(" | printf(" | ||
- | tox_callback_friend_request(tox, | + | tox_callback_friend_request(tox, |
- | tox_callback_friend_message(tox, | + | tox_callback_friend_message(tox, |
- | tox_callback_self_connection_status(tox, | + | tox_callback_self_connection_status(tox, |
+ | |||
+ | printf(" | ||
while (1) { | while (1) { | ||
- | tox_iterate(tox); | + | tox_iterate(tox, NULL); |
usleep(tox_iteration_interval(tox) * 1000); | usleep(tox_iteration_interval(tox) * 1000); | ||
} | } | ||
Line 408: | Line 419: | ||
$ ./echo_bot | $ ./echo_bot | ||
Tox ID: 4F8E7814B40B22F7DBB8B18B8518EBB369F45DE6B40309F43F39AFECF340FD7624FC706CE668 | Tox ID: 4F8E7814B40B22F7DBB8B18B8518EBB369F45DE6B40309F43F39AFECF340FD7624FC706CE668 | ||
+ | Connecting... | ||
Online, using UDP | Online, using UDP | ||
</ | </ | ||
Line 445: | Line 457: | ||
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. | 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: '' | + | Applying all mentioned, we add two new functions to our code: '' |
<code C echo_bot.c> | <code C echo_bot.c> | ||
Line 463: | Line 475: | ||
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; | ||
Line 483: | Line 494: | ||
fseek(f, 0, SEEK_SET); | fseek(f, 0, SEEK_SET); | ||
- | | + | |
fread(savedata, | fread(savedata, | ||
Line 505: | Line 516: | ||
{ | { | ||
size_t size = tox_get_savedata_size(tox); | size_t size = tox_get_savedata_size(tox); | ||
- | | + | |
tox_get_savedata(tox, | tox_get_savedata(tox, | ||
Line 521: | Line 532: | ||
DHT_node nodes[] = | DHT_node nodes[] = | ||
{ | { | ||
- | {"144.76.60.215", | + | {"85.143.221.42", |
- | {"23.226.230.47", | + | |
- | {"178.21.112.187", | + | {"78.46.73.141", |
- | {"195.154.119.113", 33445, "E398A69646B8CEACA9F0B84F553726C1C49270558C57DF5F3C368F05A7D71354", {0}}, | + | |
- | {"192.210.149.121", | + | {"tox.initramfs.io", |
+ | {"tox2.abilinski.com", | ||
+ | {"205.185.115.131", | ||
+ | | ||
}; | }; | ||
for (size_t i = 0; i < sizeof(nodes)/ | for (size_t i = 0; i < sizeof(nodes)/ | ||
- | sodium_hex2bin(nodes[i].key_bin, sizeof(nodes[i].key_bin), | + | |
- | nodes[i].key_hex, | + | |
- | tox_bootstrap(tox, | + | NULL, NULL, NULL); |
+ | tox_bootstrap(tox, | ||
} | } | ||
} | } | ||
Line 551: | Line 566: | ||
void friend_request_cb(Tox *tox, const uint8_t *public_key, | void friend_request_cb(Tox *tox, const uint8_t *public_key, | ||
- | void *user_data) | + | void *user_data) |
{ | { | ||
tox_friend_add_norequest(tox, | tox_friend_add_norequest(tox, | ||
Line 559: | Line 574: | ||
void friend_message_cb(Tox *tox, uint32_t friend_number, | void friend_message_cb(Tox *tox, uint32_t friend_number, | ||
- | size_t length, void *user_data) | + | size_t length, void *user_data) |
{ | { | ||
tox_friend_send_message(tox, | tox_friend_send_message(tox, | ||
Line 593: | Line 608: | ||
print_tox_id(tox); | print_tox_id(tox); | ||
- | tox_callback_friend_request(tox, | + | tox_callback_friend_request(tox, |
- | tox_callback_friend_message(tox, | + | tox_callback_friend_message(tox, |
- | tox_callback_self_connection_status(tox, | + | tox_callback_self_connection_status(tox, |
update_savedata_file(tox); | update_savedata_file(tox); | ||
+ | |||
+ | printf(" | ||
while (1) { | while (1) { | ||
- | tox_iterate(tox); | + | tox_iterate(tox, NULL); |
usleep(tox_iteration_interval(tox) * 1000); | usleep(tox_iteration_interval(tox) * 1000); | ||
} | } | ||
Line 610: | Line 627: | ||
} | } | ||
</ | </ | ||
+ | |||
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 / | ||
+ | </ |