Repository: irungentoo/toxcore
Branch: master
Commit: bf69b54f6400
Files: 178
Total size: 1.7 MB
Directory structure:
gitextract_ltqu1_1u/
├── .gitignore
├── .travis.yml
├── COPYING
├── DONATORS
├── INSTALL.md
├── Makefile.am
├── README.md
├── auto_tests/
│ ├── Makefile.inc
│ ├── TCP_test.c
│ ├── assoc_test.c
│ ├── crypto_test.c
│ ├── dht_test.c
│ ├── encryptsave_test.c
│ ├── friends_test.c
│ ├── helpers.h
│ ├── messenger_test.c
│ ├── network_test.c
│ ├── onion_test.c
│ ├── skeleton_test.c
│ ├── tox_test.c
│ ├── toxav_basic_test.c
│ └── toxav_many_test.c
├── autogen.sh
├── configure.ac
├── dist-build/
│ ├── android-arm.sh
│ ├── android-armv7.sh
│ ├── android-build.sh
│ ├── android-mips.sh
│ └── android-x86.sh
├── docs/
│ ├── Group-Chats.md
│ ├── Hardening.txt
│ ├── Hardening_docs.txt
│ ├── Prevent_Tracking.txt
│ ├── TCP_Network.txt
│ ├── TODO.md
│ ├── Tox_middle_level_network_protocol.txt
│ ├── av_api.md
│ └── updates/
│ ├── Crypto.md
│ ├── DHT.md
│ ├── Spam-Prevention.md
│ └── Symmetric-NAT-Transversal.md
├── libtoxav.pc.in
├── libtoxcore.pc.in
├── m4/
│ └── pkg.m4
├── other/
│ ├── DHT_bootstrap.c
│ ├── DHTnodes
│ ├── Makefile.inc
│ ├── apidsl/
│ │ ├── README.md
│ │ ├── tox.in.h
│ │ └── toxav.in.h
│ ├── astyle/
│ │ ├── README.md
│ │ ├── astylerc
│ │ └── pre-commit
│ ├── bootstrap_daemon/
│ │ ├── README.md
│ │ ├── docker/
│ │ │ ├── Dockerfile
│ │ │ └── get-nodes.py
│ │ ├── src/
│ │ │ ├── Makefile.inc
│ │ │ ├── command_line_arguments.c
│ │ │ ├── command_line_arguments.h
│ │ │ ├── config.c
│ │ │ ├── config_defaults.h
│ │ │ ├── global.h
│ │ │ ├── log.c
│ │ │ ├── log.h
│ │ │ └── tox-bootstrapd.c
│ │ ├── tox-bootstrapd.conf
│ │ ├── tox-bootstrapd.service
│ │ └── tox-bootstrapd.sh
│ ├── bootstrap_node_packets.c
│ ├── bootstrap_node_packets.h
│ ├── fun/
│ │ ├── bootstrap_node_info.py
│ │ ├── cracker.c
│ │ ├── make-funny-savefile.py
│ │ ├── sign.c
│ │ └── strkey.c
│ └── osx_build_script_toxcore.sh
├── super_donators/
│ ├── LittleVulpix
│ ├── grencez_tok5.c
│ └── sir@cmpwn.com
├── testing/
│ ├── DHT_test.c
│ ├── Makefile.inc
│ ├── Messenger_test.c
│ ├── av_test.c
│ ├── dns3_test.c
│ ├── irc_syncbot.c
│ ├── misc_tools.c
│ ├── nTox.c
│ ├── nTox.h
│ ├── tox_shell.c
│ └── tox_sync.c
├── tox.spec.in
├── toxav/
│ ├── Makefile.inc
│ ├── audio.c
│ ├── audio.h
│ ├── bwcontroller.c
│ ├── bwcontroller.h
│ ├── group.c
│ ├── group.h
│ ├── msi.c
│ ├── msi.h
│ ├── rtp.c
│ ├── rtp.h
│ ├── toxav.c
│ ├── toxav.h
│ ├── toxav_old.c
│ ├── video.c
│ └── video.h
├── toxcore/
│ ├── DHT.c
│ ├── DHT.h
│ ├── LAN_discovery.c
│ ├── LAN_discovery.h
│ ├── Makefile.inc
│ ├── Messenger.c
│ ├── Messenger.h
│ ├── TCP_client.c
│ ├── TCP_client.h
│ ├── TCP_connection.c
│ ├── TCP_connection.h
│ ├── TCP_server.c
│ ├── TCP_server.h
│ ├── assoc.c
│ ├── assoc.h
│ ├── crypto_core.c
│ ├── crypto_core.h
│ ├── friend_connection.c
│ ├── friend_connection.h
│ ├── friend_requests.c
│ ├── friend_requests.h
│ ├── group.c
│ ├── group.h
│ ├── list.c
│ ├── list.h
│ ├── logger.c
│ ├── logger.h
│ ├── misc_tools.h
│ ├── net_crypto.c
│ ├── net_crypto.h
│ ├── network.c
│ ├── network.h
│ ├── onion.c
│ ├── onion.h
│ ├── onion_announce.c
│ ├── onion_announce.h
│ ├── onion_client.c
│ ├── onion_client.h
│ ├── ping.c
│ ├── ping.h
│ ├── ping_array.c
│ ├── ping_array.h
│ ├── tox.c
│ ├── tox.h
│ ├── tox_old.h
│ ├── tox_old_code.h
│ ├── util.c
│ └── util.h
├── toxdns/
│ ├── Makefile.inc
│ ├── toxdns.c
│ └── toxdns.h
└── toxencryptsave/
├── Makefile.inc
├── crypto_pwhash_scryptsalsa208sha256/
│ ├── crypto_pwhash_scryptsalsa208sha256.h
│ ├── crypto_scrypt-common.c
│ ├── crypto_scrypt.h
│ ├── export.h
│ ├── nosse/
│ │ └── pwhash_scryptsalsa208sha256_nosse.c
│ ├── note_to_maintainers.txt
│ ├── pbkdf2-sha256.c
│ ├── pbkdf2-sha256.h
│ ├── pwhash_scryptsalsa208sha256.c
│ ├── runtime.c
│ ├── runtime.h
│ ├── scrypt_platform.c
│ ├── sse/
│ │ └── pwhash_scryptsalsa208sha256_sse.c
│ ├── sysendian.h
│ ├── utils.c
│ └── utils.h
├── defines.h
├── toxencryptsave.c
└── toxencryptsave.h
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# OS files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trash*
Icon?
ethumbs.db
Thumbs.db
*.tmp
# Make
CMakeCache.txt
CMakeFiles
Makefile
cmake_install.cmake
install_manifest.txt
tags
Makefile.in
# Testing
testing/data
*~
# Vim
*.swp
# Object files
*.o
*.lo
*.a
# Executables
*.exe
*.out
*.app
*.la
# Misc (?)
m4/*
configure
configure_aux
!m4/pkg.m4
aclocal.m4
config.h*
config.log
config.status
stamp-h1
autom4te.cache
libtoxcore.pc
libtoxav.pc
libtool
.deps
.libs
.dirstamp
build/
#kdevelop
.kdev/
*.kdev*
# Netbeans
nbproject
# astyle
*.orig
# Android buildscript
android-toolchain-*
toxcore-android-*
# cscope files list
cscope.files
# rpm
tox.spec
================================================
FILE: .travis.yml
================================================
sudo: required
dist: trusty
language: c
compiler:
- gcc
- clang
before_script:
- sudo add-apt-repository ppa:avsm/ocaml42+opam12 -y
- sudo apt-get update -qq
- sudo apt-get install ocaml opam astyle -qq
- sudo apt-get install libconfig-dev libvpx-dev libopus-dev check -qq
# build apidsl
- git clone https://github.com/iphydf/apidsl
- cd apidsl
- export OPAMYES=1
- opam init
- opam install ocamlfind ppx_deriving menhir
- eval `opam config env`
- make -j3
- cd ..
# install sodium, as it's not in Ubuntu Trusty
- git clone git://github.com/jedisct1/libsodium.git
- cd libsodium
- git checkout tags/1.0.8
- ./autogen.sh
- ./configure
- make -j3
- sudo make install
- cd ..
- sudo ldconfig
script:
# check if toxcore.h and toxav.h match apidsl tox.in.h and toxav.in.h
# tox.h
- ./apidsl/_build/apigen.native ./other/apidsl/tox.in.h > tox.h
- astyle --options=./other/astyle/astylerc tox.h
- diff -u tox.h ./toxcore/tox.h
# toxav.h
- ./apidsl/_build/apigen.native ./other/apidsl/toxav.in.h > toxav.h
- astyle --options=./other/astyle/astylerc toxav.h
- diff -u toxav.h ./toxav/toxav.h
# build toxcore and run tests
- ./autogen.sh
- CFLAGS="-Ofast -Wall -Wextra" ./configure --enable-daemon --enable-ntox
- make
- make check
- cat build/test-suite.log
- make dist
notifications:
email: false
irc:
channels:
- "chat.freenode.net#tox-dev"
on_success: always
on_failure: always
================================================
FILE: COPYING
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
================================================
FILE: DONATORS
================================================
Minnesota > Florida
vdo
Spitfire is best technicolor horse.
if bad people don't hate you, you're doing something wrong
Pinkie Pie is best pony.
JRS was here
qTox is so bloated it doesn't even fit in a 64-bit address space
================================================
FILE: INSTALL.md
================================================
#Install Instructions
- [Installation](#installation)
- [Unix like](#unix)
- [Quick install](#quick-install)
- [Build manually](#build-manually)
- [Compile toxcore](#compile-toxcore)
- [OS X](#osx)
- [Homebrew](#homebrew)
- [Non-Homebrew](#non-homebrew)
- [Windows](#windows)
- [Cross-Compile](#windows-cross-compile)
- [Setting up a VM](#windows-cross-compile-vm)
- [Setting up the environment](#windows-cross-compile-environment)
- [Compiling](#windows-cross-compile-compiling)
- [Native](#windows-native)
- [Additional](#additional)
- [Advanced configure options](#aconf)
- [A/V support](#av)
- [libtoxav](#libtoxav)
- [Bootstrap daemon](#bootstrapd)
- [nTox](#ntox)
##Installation
###Most Unix like OSes:
#### Quick install:
On Gentoo:
```
# layman -f && layman -a tox-overlay && emerge net-libs/tox
```
And you're done `:)`
If you happen to run some other distro which isn't made for compiling, there are steps below:
#### Build manually
Build dependencies:
Note: package fetching commands may vary by OS.
On Ubuntu `< 15.04` / Debian `< 8`:
```bash
sudo apt-get install build-essential libtool autotools-dev automake checkinstall check git yasm
```
On Ubuntu `>= 15.04` / Debian `>= 8`:
```bash
sudo apt-get install build-essential libtool autotools-dev automake checkinstall check git yasm libsodium13 libsodium-dev
```
On Fedora:
```bash
dnf groupinstall "Development Tools"
dnf install libtool autoconf automake check check-devel
```
Using ``dnf install @"Development Tools"`` is also valid and slightly shorter / cleaner way. ``@"Rpm Development Tools"`` would carry the remaining dependencies listed here.
On SunOS:
```pfexcec
pkg install autoconf automake gcc-47
```
On FreeBSD 10+:
```tcsh
pkg install net-im/tox
```
Note, if you install from ports select NaCl for performance, and sodium if you want it to be portable.
**For A/V support, also install the dependences listed in the [libtoxav](#libtoxav) section.** Note that you have to install those dependencies **before** compiling `toxcore`.
You should get and install [libsodium](https://github.com/jedisct1/libsodium). If you have installed `libsodium` from repo, ommit this step, and jump directly to [compiling toxcore](#compile-toxcore):
```bash
git clone https://github.com/jedisct1/libsodium.git
cd libsodium
git checkout tags/1.0.3
./autogen.sh
./configure && make check
sudo checkinstall --install --pkgname libsodium --pkgversion 1.0.0 --nodoc
sudo ldconfig
cd ..
```
Or if checkinstall is not easily available for your distribution (e.g., Fedora),
this will install the libs to /usr/local/lib and the headers to /usr/local/include:
```bash
git clone https://github.com/jedisct1/libsodium.git
cd libsodium
git checkout tags/1.0.3
./autogen.sh
./configure
make check
sudo make install
cd ..
```
If your default prefix is ``/usr/local`` and you happen to get an error that says ``"error while loading shared libraries: libtoxcore.so.0: cannot open shared object file: No such file or directory"``, then you can try running ``sudo ldconfig``. If that doesn't fix it, run:
```bash
echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf
sudo ldconfig
```
You may run into a situation where there is no ``/etc/ld.so.conf.d`` directory. You could either create it manually, or append path to local library to ``ld.so.conf``:
```bash
echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf
sudo ldconfig
```
##### Compile toxcore
Then clone this repo, generate makefile, and install `toxcore` system-wide:
```bash
git clone https://github.com/irungentoo/toxcore.git
cd toxcore
autoreconf -i
./configure
make
sudo make install
```
###OS X:
You need the latest XCode with the Developer Tools (Preferences -> Downloads -> Command Line Tools).
The following libraries are required along with libsodium and cmake for Mountain Lion and XCode 4.6.3 install libtool, automake and autoconf. You can download them with Homebrew, or install them manually.
**Note: OS X users can also install Toxcore using [osx_build_script_toxcore.sh](other/osx_build_script_toxcore.sh)**
There are no binaries/executables going to /bin/ or /usr/bin/ now. Everything is compiled and ran from the inside your local branch. See [Usage](#usage) below.
####Homebrew:
To install from the formula:
```bash
brew tap Tox/tox
brew install --HEAD libtoxcore
```
To do it manually:
```
brew install libtool automake autoconf libsodium check
```
Then clone this repo and generate makefile:
```bash
git clone https://github.com/irungentoo/toxcore.git
cd toxcore
autoreconf -i
./configure
make
make install
```
If execution fails with errors like "dyld: Library not loaded: /opt/tox-im/lib/libtoxcore.0.dylib", you may need to specify libsodium path:
Determine paths:
```
brew list libsodium
```
Configure include and lib folder and build again:
```bash
./configure --with-libsodium-headers=/usr/local/Cellar/libsodium/1.0.0/include/ --with-libsodium-libs=/usr/local/Cellar/libsodium/1.0.0/lib/
make
make install
```
####Non-homebrew:
Grab the following packages:
* https://gnu.org/software/libtool/
* https://gnu.org/software/autoconf/
* https://gnu.org/software/automake/
* https://github.com/jedisct1/libsodium
* http://check.sourceforge.net/
* http://yasm.tortall.net/Download.html (install before libvpx)
* https://code.google.com/p/webm/downloads/list
* http://www.opus-codec.org/downloads/
* http://www.freedesktop.org/wiki/Software/pkg-config/
Macports: (https://www.macports.org/)
All toxcore dependencies can be installed from MacPorts. This is often easier on PowerPC Macs,
and any version of OS X prior to 10.6, since Homebrew is supported on 10.6 and up, but not much
(or at all) on older systems. A few packages have slightly different names from the corresponding
package in Debian.
Same: libtool autoconf automake libsodium check yasm
Different: libvpx (webm) libopus pkgconfig gettext
(the libintl, from gettext, built into OS X 10.5 is missing libintl_setlocale, but the Macports build has it)
Verify where libintl is on your system: (MacPorts puts it in /opt/local)
```
for d in /usr/local/lib /opt/local/lib /usr/lib /lib; do ls -l $d/libintl.*; done
```
Check if that copy has libintl_setlocale:
```
nm /opt/local/lib/libintl.8.dylib | grep _libintl_setlocale
```
Certain other tools may not be installed, or outdated, and should also be installed from MacPorts for simplicity: git cmake
If libsodium was installed with MacPorts, you may want to symlink the copy in /opt/local/lib to /usr/local/lib. That way you don't need special configure switches for toxcore to find libsodium, and every time MacPorts updates libsodium, the new version will be linked to toxcore every time you build:
```
ln -s /opt/local/lib/libsodium.dylib /usr/local/lib/libsodium.dylib
```
Much of the build can then be done as for other platforms: git clone, and so on. Differences will be noted with (OS X 10.5 specific)
pkg-config is important for enabling a/v support in tox core, failure to install pkg-config will prevent tox core form finding the required libopus/libvpx libraries. (pkg-config may not configure properly, if you get an error about GLIB, run configure with the following parameter, --with-internal-glib).
Uncompress and install them all. Make sure to follow the README as the instructions change, but they all follow the same pattern below:
```bash
./configure
make
sudo make install
```
Compiling and installing Tox Core
```bash
cd toxcore
autoreconf -i
./configure (OS X 10.5 specific)
./configure CC="gcc -arch ppc -arch i386" CXX="g++ -arch ppc -arch i386" CPP="gcc -E" CXXCPP="g++ -E"
make
make install (OS X 10.5 specific)
should be: sudo make install
If it worked, you should have all the toxcore dylibs in /usr/local/lib: (besides the four below, the rest are symlinks to these)
$ ls -la /usr/local/lib/libtox*.dylib
libtoxav.0.dylib
libtoxcore.0.dylib
libtoxdns.0.dylib
libtoxencryptsave.0.dylib
to check what CPU architecture they're compiled for:
$ lipo -i /usr/local/lib/libtoxencryptsave.0.dylib
You should now be able to move on to compiling Toxic/Venom or some other client application
There is also a shell script called "osx_build_script_toxcore.txt" which automates everything from "git pull" to "sudo make install", once the dependencies are already taken care of by MacPorts.
```
If after running ./configure you get an error about core being unable to find libsodium (and you have installed it) run the following in place of ./configure;
```
./configure --with-libsodium-headers=/usr/local/include/ --with-libsodium-libs=/usr/local/lib
```
Ensure you set the locations correctly depending on where you installed libsodium on your computer.
If there is a problem with opus (for A/V) and you don't get a libtoxav, then try to set the pkg-config environment variable beforehand:
```
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
```
###Windows:
####Cross-compile
It's a bit challenging to build Tox and all of its dependencies nativly on Windows, so we will show an easier, less error and headache prone method of building it -- cross-compiling.
#####Setting up a VM
We will assume that you don't have any VM running Linux around and will guide you from the ground up.
First, you would need to get a virtual machine and a Linux distribution image file.
For a virtual machine we will use VirtualBox. You can get it [here](https://www.virtualbox.org/wiki/Downloads).
For a Linux distribution we will use Lubuntu 14.04 32-bit, which you can get [here](https://help.ubuntu.com/community/Lubuntu/GetLubuntu).
After you have those downloaded, install the VirtualBox and create a VM in it. The default of 512mb of RAM and 8gb of dynamically-allocated virtual hard drive would be enough.
When you have created the VM, go into its **Settings** -> **System** -> **Processor** and add some cores, if you have any additional available, for faster builds.
Then, go to **Settings** -> **Storage**, click on **Empty** under **Controller: IDE**, click on the little disc icon on the right side of the window, click on **Choose a virtual CD/DVD disk file** and select the downloaded Lubuntu image file.
Start the VM and follow the installation instructions.
After Lubuntu is installed and you have booted into it, in VirtualBox menu on top of the window select **Devices** -> **Insert Guest Additions CD image...**.
Open terminal from **Lubuntu's menu** -> **Accessories**.
Execute:
```bash
sudo apt-get update
sudo apt-get install build-essential -y
cd /media/*/*/
sudo ./VBoxLinuxAdditions.run
```
After that, create a folder called `toxbuild` somewhere on your Windows system. The go to **Devices** -> **Shared Folders Settings...** in the VirtualBox menu, add the `toxbuild` folder there and set **Auto-mount** and **Make Permanent** options.
Execute:
```bash
sudo adduser `whoami` vboxsf
```
Note the use of a [grave accent](http://en.wikipedia.org/wiki/Grave_accent) instead of an apostrophe.
Then just reboot the system with:
```bash
sudo reboot
```
After the system is booted, go to **Devices** -> **Shared Clipboard** and select **Bidirectional**. Now you will be able to copy-paste text between the host and the guest systems.
Now that the virtual machine is all set up, let's move to getting build dependencies and setting up environment variables.
#####Setting up the environment
First we will install all tools that we would need for building:
```bash
sudo apt-get install build-essential libtool autotools-dev automake checkinstall check git yasm pkg-config mingw-w64 -y
```
Then we will define a few variables, **depending on which you will build either 32-bit or 64-bit Tox**.
For 32-bit Tox build, do:
```bash
WINDOWS_TOOLCHAIN=i686-w64-mingw32
LIB_VPX_TARGET=x86-win32-gcc
```
For 64-bit Tox build, do:
```bash
WINDOWS_TOOLCHAIN=x86_64-w64-mingw32
LIB_VPX_TARGET=x86_64-win64-gcc
```
This is the only difference between 32-bit and 64-bit build procedures.
For speeding up the build process do:
```
MAKEFLAGS=j$(nproc)
export MAKEFLAGS
```
And let's make a folder where we will be building everything at
```bash
cd ~
mkdir prefix
cd prefix
PREFIX_DIR=$(pwd)
cd ..
mkdir build
cd build
```
#####Compiling
Now we will build libraries needed for audio/video: VPX and Opus.
VPX:
```bash
git clone https://chromium.googlesource.com/webm/libvpx
cd libvpx
git checkout tags/v1.3.0
CROSS="$WINDOWS_TOOLCHAIN"- ./configure --target="$LIB_VPX_TARGET" --prefix="$PREFIX_DIR" --disable-examples --disable-unit-tests --disable-shared --enable-static
make
make install
cd ..
```
Opus:
```bash
wget http://downloads.xiph.org/releases/opus/opus-1.1.tar.gz
tar -xf opus-1.1.tar.gz
cd opus-1.1
./configure --host="$WINDOWS_TOOLCHAIN" --prefix="$PREFIX_DIR" --disable-extra-programs --disable-doc --disable-shared --enable-static
make
make install
cd ..
```
Now we will build sodium crypto library:
```bash
git clone https://github.com/jedisct1/libsodium/
cd libsodium
git checkout tags/1.0.3
./autogen.sh
./configure --host="$WINDOWS_TOOLCHAIN" --prefix="$PREFIX_DIR" --disable-shared --enable-static
make
make install
cd ..
```
And finally we will build Tox:
```bash
git clone https://github.com/irungentoo/toxcore
cd toxcore
./autogen.sh
./configure --host="$WINDOWS_TOOLCHAIN" --prefix="$PREFIX_DIR" --disable-ntox --disable-tests --disable-testing --with-dependency-search="$PREFIX_DIR" --disable-shared --enable-static
make
make install
cd ..
```
Then we make Tox shared library:
```bash
cd "$PREFIX_DIR"
mkdir tmp
cd tmp
$WINDOWS_TOOLCHAIN-ar x ../lib/libtoxcore.a
$WINDOWS_TOOLCHAIN-ar x ../lib/libtoxav.a
$WINDOWS_TOOLCHAIN-ar x ../lib/libtoxdns.a
$WINDOWS_TOOLCHAIN-ar x ../lib/libtoxencryptsave.a
$WINDOWS_TOOLCHAIN-gcc -Wl,--export-all-symbols -Wl,--out-implib=libtox.dll.a -shared -o libtox.dll *.o ../lib/*.a /usr/$WINDOWS_TOOLCHAIN/lib/libwinpthread.a -liphlpapi -lws2_32 -static-libgcc
```
And we will copy it over to the `toxbuild` directory:
```bash
mkdir -p /media/sf_toxbuild/release/lib
cp libtox.dll.a /media/sf_toxbuild/release/lib
mkdir -p /media/sf_toxbuild/release/bin
cp libtox.dll /media/sf_toxbuild/release/bin
mkdir -p /media/sf_toxbuild/release/include
cp -r ../include/tox /media/sf_toxbuild/release/include
```
That's it. Now you should have `release/bin/libtox.dll`, `release/bin/libtox.dll.a` and `release/include/tox/` in your `toxbuild` directory on the Windows system.
####Native
Note that the Native instructions are incomplete, in a sense that they miss instructions needed for adding audio/video support to Tox. You also might stumble upon some unknown MinGW+msys issues while trying to build it.
You should install:
- [MinGW](http://sourceforge.net/projects/mingw/)
When installing MinGW, make sure to select the MSYS option in the installer.
MinGW will install an "MinGW shell" (you should get a shortcut for it), make sure to perform all operations (i.e., generating/running configure script, compiling, etc.) from the MinGW shell.
First download the source tarball from https://download.libsodium.org/libsodium/releases/ and build it.
Assuming that you got the libsodium-1.0.0.tar.gz release:
```cmd
tar -zxvf libsodium-1.0.0.tar.gz
cd libsodium-1.0.0
./configure
make
make install
cd ..
```
You can also use a precompiled win32 binary of libsodium, however you will have to place the files in places where they can be found, i.e., dll's go to /bin headers to /include and libraries to /lib directories in your MinGW shell.
Next, install toxcore library, should either clone this repo by using git, or just download a [zip of current Master branch](https://github.com/irungentoo/toxcore/archive/master.zip) and extract it somewhere.
Assuming that you now have the sources in the toxcore directory:
```cmd
cd toxcore
autoreconf -i
./configure
make
make install
```
####Clients:
While [Toxic](https://github.com/tox/toxic) is no longer in core, a list of Tox clients are located in our [wiki](https://wiki.tox.chat/doku.php?id=clients)
##Additional
###Advanced configure options:
- --prefix=/where/to/install
- --with-libsodium-headers=/path/to/libsodium/include/
- --with-libsodium-libs=/path/to/sodiumtest/lib/
- --enable-silent-rules less verbose build output (undo: "make V=1")
- --disable-silent-rules verbose build output (undo: "make V=0")
- --disable-tests build unit tests (default: auto)
- --disable-av disable A/V support (default: auto) see: [libtoxav](#libtoxav)
- --enable-ntox build nTox client (default: no) see: [nTox](#ntox)
- --enable-daemon build DHT bootstrap daemon (default=no) see: [Bootstrap daemon](#bootstrapd)
- --enable-shared[=PKGS] build shared libraries [default=yes]
- --enable-static[=PKGS] build static libraries [default=yes]
###A/V support:
####libtoxav:
'libtoxav' is needed for A/V support and it's enabled by default. You can disable it by adding --disable-av argument to ./configure script like so:
```bash
./configure --disable-av
```
There are 2 dependencies required for libtoxav: libopus and libvpx. If they are not installed A/V support is dropped.
Install on fedora:
```bash
yum install opus-devel libvpx-devel
```
Install on ubuntu:
```bash
sudo apt-get install libopus-dev libvpx-dev pkg-config
```
If you get the "Unable to locate package libopus-dev" message, add the following ppa and try again:
```bash
sudo add-apt-repository ppa:ubuntu-sdk-team/ppa && sudo apt-get update && sudo apt-get dist-upgrade
```
Install from source (example for most unix-like OS's):
libvpx:
```bash
git clone https://chromium.googlesource.com/webm/libvpx
cd libvpx
./configure
make -j3
sudo make install
cd ..
```
libopus:
```bash
wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz
tar xvzf opus-1.0.3.tar.gz
cd opus-1.0.3
./configure
make -j3
sudo make install
cd ..
```
###Bootstrap daemon:
Daemon is disabled by default. You can enable it by adding --enable-daemon argument to ./configure script like so:
```bash
./configure --enable-daemon
```
There is one dependency required for bootstrap daemon: `libconfig-dev` >= 1.4.
Install on fedora:
```bash
yum install libconfig-devel
```
Install on ubuntu:
```bash
sudo apt-get install libconfig-dev
```
OS X homebrew:
```
brew install libconfig
```
OS X non-homebrew:
Grab the following [package](http://www.hyperrealm.com/libconfig/), uncompress and install
See this [readme](other/bootstrap_daemon/README.md) on how to set up the bootstrap daemon.
###nTox test cli:
nTox is disabled by default. You can enable it by adding --enable-ntox argument to ./configure script like so:
```bash
./configure --enable-ntox
```
There is one dependency required for nTox: libncurses.
Install on fedora:
```bash
yum install ncurses-devel
```
Install on ubuntu:
```bash
sudo apt-get install ncurses-dev
```
================================================
FILE: Makefile.am
================================================
SUBDIRS = build
ACLOCAL_AMFLAGS = -I m4
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = $(top_builddir)/libtoxcore.pc
BUILT_SOURCES = $(top_builddir)/libtoxcore.pc
CLEANFILES = $(top_builddir)/libtoxcore.pc
EXTRA_DIST = \
README.md \
libtoxcore.pc.in \
tox.spec \
dist-build/android-arm.sh \
dist-build/android-armv7.sh \
dist-build/android-x86.sh \
dist-build/android-mips.sh \
dist-build/android-build.sh \
$(top_srcdir)/docs/updates/Crypto.md \
$(top_srcdir)/docs/updates/Spam-Prevention.md \
$(top_srcdir)/docs/updates/Symmetric-NAT-Transversal.md \
$(top_srcdir)/other/astyle/README.md \
$(top_srcdir)/other/astyle/astylerc \
$(top_srcdir)/other/astyle/pre-commit
if BUILD_AV
pkgconfig_DATA += $(top_builddir)/libtoxav.pc
BUILT_SOURCES += $(top_builddir)/libtoxav.pc
CLEANFILES += $(top_builddir)/libtoxav.pc
EXTRA_DIST += libtoxav.pc.in
endif
================================================
FILE: README.md
================================================

***
With the rise of government surveillance programs, Tox, a FOSS initiative, aims to be an easy to use, all-in-one communication platform that ensures full privacy and secure message delivery.
[**Website**](https://tox.chat) **|** [**Wiki**](https://wiki.tox.chat/) **|** [**Blog**](https://blog.tox.chat/) **|** [**FAQ**](https://wiki.tox.chat/doku.php?id=users:faq) **|** [**Binaries/Downloads**](https://wiki.tox.chat/Binaries) **|** [**Clients**](https://wiki.tox.chat/doku.php?id=clients) **|** [**Compiling**](/INSTALL.md)
**IRC Channels:** [#tox@freenode](https://webchat.freenode.net/?channels=tox), [#tox-dev@freenode](https://webchat.freenode.net/?channels=tox-dev)
## The Complex Stuff:
### UDP vs. TCP
Tox must use UDP simply because [hole punching](https://en.wikipedia.org/wiki/UDP_hole_punching) with TCP is not as reliable.
However, Tox does use [TCP relays](/docs/TCP_Network.txt) as a fallback if it encounters a firewall that prevents UDP hole punching.
### Connecting & Communicating
Every peer is represented as a [byte string](https://en.wikipedia.org/wiki/String_(computer_science)) (the public key [Tox ID] of the peer). By using torrent-style DHT, peers can find the IP of other peers by using their Tox ID. Once the IP is obtained, peers can initiate a [secure](/docs/updates/Crypto.md) connection with each other. Once the connection is made, peers can exchange messages, send files, start video chats, etc. using encrypted communications.
**Current build status:** [](https://travis-ci.org/irungentoo/toxcore)
## Q&A:
### What are your goals with Tox?
We want Tox to be as simple as possible while remaining as secure as possible.
### Why are you doing this? There are already a bunch of free Skype alternatives.
The goal of this project is to create a configuration-free P2P Skype replacement. “Configuration-free” means that the user will simply have to open the program and will be capable of adding people and communicating with them without having to set up an account. There are many so-called Skype replacements, but all of them are either hard to configure for the normal user or suffer from being way too centralized.
## TODO:
- [TODO](/docs/TODO.md)
## Documentation:
- [Compiling](/INSTALL.md)
- [DHT Protocol](/docs/updates/DHT.md)
- [Crypto](/docs/updates/Crypto.md)
================================================
FILE: auto_tests/Makefile.inc
================================================
if BUILD_TESTS
TESTS = encryptsave_test messenger_autotest crypto_test network_test assoc_test onion_test TCP_test tox_test dht_autotest
check_PROGRAMS = encryptsave_test messenger_autotest crypto_test network_test assoc_test onion_test TCP_test tox_test dht_autotest
AUTOTEST_CFLAGS = \
$(LIBSODIUM_CFLAGS) \
$(NACL_CFLAGS) \
$(CHECK_CFLAGS)
AUTOTEST_LDADD = \
$(LIBSODIUM_LDFLAGS) \
$(NACL_LDFLAGS) \
libtoxcore.la \
libtoxencryptsave.la \
$(LIBSODIUM_LIBS) \
$(NACL_OBJECTS) \
$(NACL_LIBS) \
$(CHECK_LIBS)
if BUILD_AV
TESTS += toxav_basic_test toxav_many_test
check_PROGRAMS += toxav_basic_test toxav_many_test
AUTOTEST_LDADD += libtoxav.la
endif
messenger_autotest_SOURCES = ../auto_tests/messenger_test.c
messenger_autotest_CFLAGS = $(AUTOTEST_CFLAGS)
messenger_autotest_LDADD = $(AUTOTEST_LDADD)
crypto_test_SOURCES = ../auto_tests/crypto_test.c
crypto_test_CFLAGS = $(AUTOTEST_CFLAGS)
crypto_test_LDADD = $(AUTOTEST_LDADD)
network_test_SOURCES = ../auto_tests/network_test.c
network_test_CFLAGS = $(AUTOTEST_CFLAGS)
network_test_LDADD = $(AUTOTEST_LDADD)
assoc_test_SOURCES = ../auto_tests/assoc_test.c
assoc_test_CFLAGS = $(AUTOTEST_CFLAGS)
assoc_test_LDADD = $(AUTOTEST_LDADD)
onion_test_SOURCES = ../auto_tests/onion_test.c
onion_test_CFLAGS = $(AUTOTEST_CFLAGS)
onion_test_LDADD = $(AUTOTEST_LDADD)
TCP_test_SOURCES = ../auto_tests/TCP_test.c
TCP_test_CFLAGS = $(AUTOTEST_CFLAGS)
TCP_test_LDADD = $(AUTOTEST_LDADD)
tox_test_SOURCES = ../auto_tests/tox_test.c
tox_test_CFLAGS = $(AUTOTEST_CFLAGS)
tox_test_LDADD = $(AUTOTEST_LDADD)
dht_autotest_SOURCES = ../auto_tests/dht_test.c
dht_autotest_CFLAGS = $(AUTOTEST_CFLAGS)
dht_autotest_LDADD = $(AUTOTEST_LDADD)
if BUILD_AV
toxav_basic_test_SOURCES = ../auto_tests/toxav_basic_test.c
toxav_basic_test_CFLAGS = $(AUTOTEST_CFLAGS)
toxav_basic_test_LDADD = $(AUTOTEST_LDADD) $(AV_LIBS)
toxav_many_test_SOURCES = ../auto_tests/toxav_many_test.c
toxav_many_test_CFLAGS = $(AUTOTEST_CFLAGS)
toxav_many_test_LDADD = $(AUTOTEST_LDADD)
endif
endif
encryptsave_test_SOURCES = ../auto_tests/encryptsave_test.c
encryptsave_test_CFLAGS = $(AUTOTEST_CFLAGS)
encryptsave_test_LDADD = $(AUTOTEST_LDADD)
EXTRA_DIST += $(top_srcdir)/auto_tests/friends_test.c \
$(top_srcdir)/auto_tests/helpers.h
================================================
FILE: auto_tests/TCP_test.c
================================================
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include
#include
#include
#include
#include
#include "../toxcore/TCP_server.h"
#include "../toxcore/TCP_client.h"
#include "../toxcore/util.h"
#include "helpers.h"
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
#define c_sleep(x) Sleep(1*x)
#else
#include
#define c_sleep(x) usleep(1000*x)
#endif
#define NUM_PORTS 3
uint16_t ports[NUM_PORTS] = {1234, 33445, 25643};
START_TEST(test_basic)
{
uint8_t self_public_key[crypto_box_PUBLICKEYBYTES];
uint8_t self_secret_key[crypto_box_SECRETKEYBYTES];
crypto_box_keypair(self_public_key, self_secret_key);
TCP_Server *tcp_s = new_TCP_server(1, NUM_PORTS, ports, self_secret_key, NULL);
ck_assert_msg(tcp_s != NULL, "Failed to create TCP relay server");
ck_assert_msg(tcp_s->num_listening_socks == NUM_PORTS, "Failed to bind to all ports");
sock_t sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in6 addr6_loopback = {0};
addr6_loopback.sin6_family = AF_INET6;
addr6_loopback.sin6_port = htons(ports[rand() % NUM_PORTS]);
addr6_loopback.sin6_addr = in6addr_loopback;
int ret = connect(sock, (struct sockaddr *)&addr6_loopback, sizeof(addr6_loopback));
ck_assert_msg(ret == 0, "Failed to connect to TCP relay server");
uint8_t f_public_key[crypto_box_PUBLICKEYBYTES];
uint8_t f_secret_key[crypto_box_SECRETKEYBYTES];
uint8_t f_nonce[crypto_box_NONCEBYTES];
crypto_box_keypair(f_public_key, f_secret_key);
random_nonce(f_nonce);
uint8_t t_secret_key[crypto_box_SECRETKEYBYTES];
uint8_t handshake_plain[TCP_HANDSHAKE_PLAIN_SIZE];
crypto_box_keypair(handshake_plain, t_secret_key);
memcpy(handshake_plain + crypto_box_PUBLICKEYBYTES, f_nonce, crypto_box_NONCEBYTES);
uint8_t handshake[TCP_CLIENT_HANDSHAKE_SIZE];
memcpy(handshake, f_public_key, crypto_box_PUBLICKEYBYTES);
new_nonce(handshake + crypto_box_PUBLICKEYBYTES);
ret = encrypt_data(self_public_key, f_secret_key, handshake + crypto_box_PUBLICKEYBYTES, handshake_plain,
TCP_HANDSHAKE_PLAIN_SIZE, handshake + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES);
ck_assert_msg(ret == TCP_CLIENT_HANDSHAKE_SIZE - (crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES),
"Encrypt failed.");
ck_assert_msg(send(sock, handshake, TCP_CLIENT_HANDSHAKE_SIZE - 1, 0) == TCP_CLIENT_HANDSHAKE_SIZE - 1, "send Failed.");
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
ck_assert_msg(send(sock, handshake + (TCP_CLIENT_HANDSHAKE_SIZE - 1), 1, 0) == 1, "send Failed.");
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
uint8_t response[TCP_SERVER_HANDSHAKE_SIZE];
uint8_t response_plain[TCP_HANDSHAKE_PLAIN_SIZE];
ck_assert_msg(recv(sock, response, TCP_SERVER_HANDSHAKE_SIZE, 0) == TCP_SERVER_HANDSHAKE_SIZE, "recv Failed.");
ret = decrypt_data(self_public_key, f_secret_key, response, response + crypto_box_NONCEBYTES,
TCP_SERVER_HANDSHAKE_SIZE - crypto_box_NONCEBYTES, response_plain);
ck_assert_msg(ret == TCP_HANDSHAKE_PLAIN_SIZE, "Decrypt Failed.");
uint8_t f_nonce_r[crypto_box_NONCEBYTES];
uint8_t f_shared_key[crypto_box_BEFORENMBYTES];
encrypt_precompute(response_plain, t_secret_key, f_shared_key);
memcpy(f_nonce_r, response_plain + crypto_box_BEFORENMBYTES, crypto_box_NONCEBYTES);
uint8_t r_req_p[1 + crypto_box_PUBLICKEYBYTES] = {0};
memcpy(r_req_p + 1, f_public_key, crypto_box_PUBLICKEYBYTES);
uint8_t r_req[2 + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_MACBYTES];
uint16_t size = 1 + crypto_box_PUBLICKEYBYTES + crypto_box_MACBYTES;
size = htons(size);
ret = encrypt_data_symmetric(f_shared_key, f_nonce, r_req_p, 1 + crypto_box_PUBLICKEYBYTES, r_req + 2);
increment_nonce(f_nonce);
memcpy(r_req, &size, 2);
uint32_t i;
for (i = 0; i < sizeof(r_req); ++i) {
ck_assert_msg(send(sock, r_req + i, 1, 0) == 1, "send Failed.");
//ck_assert_msg(send(sock, r_req, sizeof(r_req), 0) == sizeof(r_req), "send Failed.");
do_TCP_server(tcp_s);
c_sleep(50);
}
do_TCP_server(tcp_s);
c_sleep(50);
uint8_t packet_resp[4096];
int recv_data_len;
ck_assert_msg((recv_data_len = recv(sock, packet_resp, 2 + 2 + crypto_box_PUBLICKEYBYTES + crypto_box_MACBYTES,
0)) == 2 + 2 + crypto_box_PUBLICKEYBYTES + crypto_box_MACBYTES, "recv Failed. %u", recv_data_len);
memcpy(&size, packet_resp, 2);
ck_assert_msg(ntohs(size) == 2 + crypto_box_PUBLICKEYBYTES + crypto_box_MACBYTES, "Wrong packet size.");
uint8_t packet_resp_plain[4096];
ret = decrypt_data_symmetric(f_shared_key, f_nonce_r, packet_resp + 2, recv_data_len - 2, packet_resp_plain);
ck_assert_msg(ret != -1, "decryption failed");
increment_nonce(f_nonce_r);
ck_assert_msg(packet_resp_plain[0] == 1, "wrong packet id %u", packet_resp_plain[0]);
ck_assert_msg(packet_resp_plain[1] == 0, "connection not refused %u", packet_resp_plain[1]);
ck_assert_msg(public_key_cmp(packet_resp_plain + 2, f_public_key) == 0, "key in packet wrong");
kill_TCP_server(tcp_s);
}
END_TEST
struct sec_TCP_con {
sock_t sock;
uint8_t public_key[crypto_box_PUBLICKEYBYTES];
uint8_t recv_nonce[crypto_box_NONCEBYTES];
uint8_t sent_nonce[crypto_box_NONCEBYTES];
uint8_t shared_key[crypto_box_BEFORENMBYTES];
};
struct sec_TCP_con *new_TCP_con(TCP_Server *tcp_s)
{
struct sec_TCP_con *sec_c = malloc(sizeof(struct sec_TCP_con));
sock_t sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in6 addr6_loopback = {0};
addr6_loopback.sin6_family = AF_INET6;
addr6_loopback.sin6_port = htons(ports[rand() % NUM_PORTS]);
addr6_loopback.sin6_addr = in6addr_loopback;
int ret = connect(sock, (struct sockaddr *)&addr6_loopback, sizeof(addr6_loopback));
ck_assert_msg(ret == 0, "Failed to connect to TCP relay server");
uint8_t f_secret_key[crypto_box_SECRETKEYBYTES];
crypto_box_keypair(sec_c->public_key, f_secret_key);
random_nonce(sec_c->sent_nonce);
uint8_t t_secret_key[crypto_box_SECRETKEYBYTES];
uint8_t handshake_plain[TCP_HANDSHAKE_PLAIN_SIZE];
crypto_box_keypair(handshake_plain, t_secret_key);
memcpy(handshake_plain + crypto_box_PUBLICKEYBYTES, sec_c->sent_nonce, crypto_box_NONCEBYTES);
uint8_t handshake[TCP_CLIENT_HANDSHAKE_SIZE];
memcpy(handshake, sec_c->public_key, crypto_box_PUBLICKEYBYTES);
new_nonce(handshake + crypto_box_PUBLICKEYBYTES);
ret = encrypt_data(tcp_s->public_key, f_secret_key, handshake + crypto_box_PUBLICKEYBYTES, handshake_plain,
TCP_HANDSHAKE_PLAIN_SIZE, handshake + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES);
ck_assert_msg(ret == TCP_CLIENT_HANDSHAKE_SIZE - (crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES),
"Encrypt failed.");
ck_assert_msg(send(sock, handshake, TCP_CLIENT_HANDSHAKE_SIZE - 1, 0) == TCP_CLIENT_HANDSHAKE_SIZE - 1, "send Failed.");
do_TCP_server(tcp_s);
c_sleep(50);
ck_assert_msg(send(sock, handshake + (TCP_CLIENT_HANDSHAKE_SIZE - 1), 1, 0) == 1, "send Failed.");
c_sleep(50);
do_TCP_server(tcp_s);
uint8_t response[TCP_SERVER_HANDSHAKE_SIZE];
uint8_t response_plain[TCP_HANDSHAKE_PLAIN_SIZE];
ck_assert_msg(recv(sock, response, TCP_SERVER_HANDSHAKE_SIZE, 0) == TCP_SERVER_HANDSHAKE_SIZE, "recv Failed.");
ret = decrypt_data(tcp_s->public_key, f_secret_key, response, response + crypto_box_NONCEBYTES,
TCP_SERVER_HANDSHAKE_SIZE - crypto_box_NONCEBYTES, response_plain);
ck_assert_msg(ret == TCP_HANDSHAKE_PLAIN_SIZE, "Decrypt Failed.");
encrypt_precompute(response_plain, t_secret_key, sec_c->shared_key);
memcpy(sec_c->recv_nonce, response_plain + crypto_box_BEFORENMBYTES, crypto_box_NONCEBYTES);
sec_c->sock = sock;
return sec_c;
}
void kill_TCP_con(struct sec_TCP_con *con)
{
kill_sock(con->sock);
free(con);
}
int write_packet_TCP_secure_connection(struct sec_TCP_con *con, uint8_t *data, uint16_t length)
{
uint8_t packet[sizeof(uint16_t) + length + crypto_box_MACBYTES];
uint16_t c_length = htons(length + crypto_box_MACBYTES);
memcpy(packet, &c_length, sizeof(uint16_t));
int len = encrypt_data_symmetric(con->shared_key, con->sent_nonce, data, length, packet + sizeof(uint16_t));
if ((unsigned int)len != (sizeof(packet) - sizeof(uint16_t)))
return -1;
increment_nonce(con->sent_nonce);
ck_assert_msg(send(con->sock, packet, sizeof(packet), 0) == sizeof(packet), "send failed");
return 0;
}
int read_packet_sec_TCP(struct sec_TCP_con *con, uint8_t *data, uint16_t length)
{
int len;
ck_assert_msg((len = recv(con->sock, data, length, 0)) == length, "wrong len %i\n", len);
ck_assert_msg((len = decrypt_data_symmetric(con->shared_key, con->recv_nonce, data + 2, length - 2, data)) != -1,
"Decrypt failed");
increment_nonce(con->recv_nonce);
return len;
}
START_TEST(test_some)
{
uint8_t self_public_key[crypto_box_PUBLICKEYBYTES];
uint8_t self_secret_key[crypto_box_SECRETKEYBYTES];
crypto_box_keypair(self_public_key, self_secret_key);
TCP_Server *tcp_s = new_TCP_server(1, NUM_PORTS, ports, self_secret_key, NULL);
ck_assert_msg(tcp_s != NULL, "Failed to create TCP relay server");
ck_assert_msg(tcp_s->num_listening_socks == NUM_PORTS, "Failed to bind to all ports");
struct sec_TCP_con *con1 = new_TCP_con(tcp_s);
struct sec_TCP_con *con2 = new_TCP_con(tcp_s);
struct sec_TCP_con *con3 = new_TCP_con(tcp_s);
uint8_t requ_p[1 + crypto_box_PUBLICKEYBYTES];
requ_p[0] = 0;
memcpy(requ_p + 1, con3->public_key, crypto_box_PUBLICKEYBYTES);
write_packet_TCP_secure_connection(con1, requ_p, sizeof(requ_p));
memcpy(requ_p + 1, con1->public_key, crypto_box_PUBLICKEYBYTES);
write_packet_TCP_secure_connection(con3, requ_p, sizeof(requ_p));
do_TCP_server(tcp_s);
c_sleep(50);
uint8_t data[2048];
int len = read_packet_sec_TCP(con1, data, 2 + 1 + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_MACBYTES);
ck_assert_msg(len == 1 + 1 + crypto_box_PUBLICKEYBYTES, "wrong len %u", len);
ck_assert_msg(data[0] == 1, "wrong packet id %u", data[0]);
ck_assert_msg(data[1] == 16, "connection not refused %u", data[1]);
ck_assert_msg(public_key_cmp(data + 2, con3->public_key) == 0, "key in packet wrong");
len = read_packet_sec_TCP(con3, data, 2 + 1 + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_MACBYTES);
ck_assert_msg(len == 1 + 1 + crypto_box_PUBLICKEYBYTES, "wrong len %u", len);
ck_assert_msg(data[0] == 1, "wrong packet id %u", data[0]);
ck_assert_msg(data[1] == 16, "connection not refused %u", data[1]);
ck_assert_msg(public_key_cmp(data + 2, con1->public_key) == 0, "key in packet wrong");
uint8_t test_packet[512] = {16, 17, 16, 86, 99, 127, 255, 189, 78};
write_packet_TCP_secure_connection(con3, test_packet, sizeof(test_packet));
write_packet_TCP_secure_connection(con3, test_packet, sizeof(test_packet));
write_packet_TCP_secure_connection(con3, test_packet, sizeof(test_packet));
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
len = read_packet_sec_TCP(con1, data, 2 + 2 + crypto_box_MACBYTES);
ck_assert_msg(len == 2, "wrong len %u", len);
ck_assert_msg(data[0] == 2, "wrong packet id %u", data[0]);
ck_assert_msg(data[1] == 16, "wrong peer id %u", data[1]);
len = read_packet_sec_TCP(con3, data, 2 + 2 + crypto_box_MACBYTES);
ck_assert_msg(len == 2, "wrong len %u", len);
ck_assert_msg(data[0] == 2, "wrong packet id %u", data[0]);
ck_assert_msg(data[1] == 16, "wrong peer id %u", data[1]);
len = read_packet_sec_TCP(con1, data, 2 + sizeof(test_packet) + crypto_box_MACBYTES);
ck_assert_msg(len == sizeof(test_packet), "wrong len %u", len);
ck_assert_msg(memcmp(data, test_packet, sizeof(test_packet)) == 0, "packet is wrong %u %u %u %u", data[0], data[1],
data[sizeof(test_packet) - 2], data[sizeof(test_packet) - 1]);
len = read_packet_sec_TCP(con1, data, 2 + sizeof(test_packet) + crypto_box_MACBYTES);
ck_assert_msg(len == sizeof(test_packet), "wrong len %u", len);
ck_assert_msg(memcmp(data, test_packet, sizeof(test_packet)) == 0, "packet is wrong %u %u %u %u", data[0], data[1],
data[sizeof(test_packet) - 2], data[sizeof(test_packet) - 1]);
len = read_packet_sec_TCP(con1, data, 2 + sizeof(test_packet) + crypto_box_MACBYTES);
ck_assert_msg(len == sizeof(test_packet), "wrong len %u", len);
ck_assert_msg(memcmp(data, test_packet, sizeof(test_packet)) == 0, "packet is wrong %u %u %u %u", data[0], data[1],
data[sizeof(test_packet) - 2], data[sizeof(test_packet) - 1]);
write_packet_TCP_secure_connection(con1, test_packet, sizeof(test_packet));
write_packet_TCP_secure_connection(con1, test_packet, sizeof(test_packet));
write_packet_TCP_secure_connection(con1, test_packet, sizeof(test_packet));
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
len = read_packet_sec_TCP(con3, data, 2 + sizeof(test_packet) + crypto_box_MACBYTES);
ck_assert_msg(len == sizeof(test_packet), "wrong len %u", len);
ck_assert_msg(memcmp(data, test_packet, sizeof(test_packet)) == 0, "packet is wrong %u %u %u %u", data[0], data[1],
data[sizeof(test_packet) - 2], data[sizeof(test_packet) - 1]);
len = read_packet_sec_TCP(con3, data, 2 + sizeof(test_packet) + crypto_box_MACBYTES);
ck_assert_msg(len == sizeof(test_packet), "wrong len %u", len);
ck_assert_msg(memcmp(data, test_packet, sizeof(test_packet)) == 0, "packet is wrong %u %u %u %u", data[0], data[1],
data[sizeof(test_packet) - 2], data[sizeof(test_packet) - 1]);
len = read_packet_sec_TCP(con3, data, 2 + sizeof(test_packet) + crypto_box_MACBYTES);
ck_assert_msg(len == sizeof(test_packet), "wrong len %u", len);
ck_assert_msg(memcmp(data, test_packet, sizeof(test_packet)) == 0, "packet is wrong %u %u %u %u", data[0], data[1],
data[sizeof(test_packet) - 2], data[sizeof(test_packet) - 1]);
uint8_t ping_packet[1 + sizeof(uint64_t)] = {4, 8, 6, 9, 67};
write_packet_TCP_secure_connection(con1, ping_packet, sizeof(ping_packet));
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
len = read_packet_sec_TCP(con1, data, 2 + sizeof(ping_packet) + crypto_box_MACBYTES);
ck_assert_msg(len == sizeof(ping_packet), "wrong len %u", len);
ck_assert_msg(data[0] == 5, "wrong packet id %u", data[0]);
ck_assert_msg(memcmp(ping_packet + 1, data + 1, sizeof(uint64_t)) == 0, "wrong packet data");
kill_TCP_server(tcp_s);
kill_TCP_con(con1);
kill_TCP_con(con2);
kill_TCP_con(con3);
}
END_TEST
static int response_callback_good;
static uint8_t response_callback_connection_id;
static uint8_t response_callback_public_key[crypto_box_PUBLICKEYBYTES];
static int response_callback(void *object, uint8_t connection_id, const uint8_t *public_key)
{
if (set_tcp_connection_number(object - 2, connection_id, 7) != 0)
return 1;
response_callback_connection_id = connection_id;
memcpy(response_callback_public_key, public_key, crypto_box_PUBLICKEYBYTES);
response_callback_good++;
return 0;
}
static int status_callback_good;
static uint8_t status_callback_connection_id;
static uint8_t status_callback_status;
static int status_callback(void *object, uint32_t number, uint8_t connection_id, uint8_t status)
{
if (object != (void *)2)
return 1;
if (number != 7)
return 1;
status_callback_connection_id = connection_id;
status_callback_status = status;
status_callback_good++;
return 0;
}
static int data_callback_good;
static int data_callback(void *object, uint32_t number, uint8_t connection_id, const uint8_t *data, uint16_t length)
{
if (object != (void *)3)
return 1;
if (number != 7)
return 1;
if (length != 5)
return 1;
if (data[0] == 1 && data[1] == 2 && data[2] == 3 && data[3] == 4 && data[4] == 5) {
data_callback_good++;
return 0;
}
return 1;
}
static int oob_data_callback_good;
static uint8_t oob_pubkey[crypto_box_PUBLICKEYBYTES];
static int oob_data_callback(void *object, const uint8_t *public_key, const uint8_t *data, uint16_t length)
{
if (object != (void *)4)
return 1;
if (length != 5)
return 1;
if (public_key_cmp(public_key, oob_pubkey) != 0)
return 1;
if (data[0] == 1 && data[1] == 2 && data[2] == 3 && data[3] == 4 && data[4] == 5) {
oob_data_callback_good++;
return 0;
}
return 1;
}
START_TEST(test_client)
{
unix_time_update();
uint8_t self_public_key[crypto_box_PUBLICKEYBYTES];
uint8_t self_secret_key[crypto_box_SECRETKEYBYTES];
crypto_box_keypair(self_public_key, self_secret_key);
TCP_Server *tcp_s = new_TCP_server(1, NUM_PORTS, ports, self_secret_key, NULL);
ck_assert_msg(tcp_s != NULL, "Failed to create TCP relay server");
ck_assert_msg(tcp_s->num_listening_socks == NUM_PORTS, "Failed to bind to all ports");
uint8_t f_public_key[crypto_box_PUBLICKEYBYTES];
uint8_t f_secret_key[crypto_box_SECRETKEYBYTES];
crypto_box_keypair(f_public_key, f_secret_key);
IP_Port ip_port_tcp_s;
ip_port_tcp_s.port = htons(ports[rand() % NUM_PORTS]);
ip_port_tcp_s.ip.family = AF_INET6;
ip_port_tcp_s.ip.ip6.in6_addr = in6addr_loopback;
TCP_Client_Connection *conn = new_TCP_connection(ip_port_tcp_s, self_public_key, f_public_key, f_secret_key, 0);
c_sleep(50);
do_TCP_connection(conn);
ck_assert_msg(conn->status == TCP_CLIENT_UNCONFIRMED, "Wrong status. Expected: %u, is: %u", TCP_CLIENT_UNCONFIRMED,
conn->status);
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_TCP_connection(conn);
ck_assert_msg(conn->status == TCP_CLIENT_CONFIRMED, "Wrong status. Expected: %u, is: %u", TCP_CLIENT_CONFIRMED,
conn->status);
c_sleep(500);
do_TCP_connection(conn);
ck_assert_msg(conn->status == TCP_CLIENT_CONFIRMED, "Wrong status. Expected: %u, is: %u", TCP_CLIENT_CONFIRMED,
conn->status);
c_sleep(500);
do_TCP_connection(conn);
ck_assert_msg(conn->status == TCP_CLIENT_CONFIRMED, "Wrong status. Expected: %u, is: %u", TCP_CLIENT_CONFIRMED,
conn->status);
do_TCP_server(tcp_s);
c_sleep(50);
ck_assert_msg(conn->status == TCP_CLIENT_CONFIRMED, "Wrong status. Expected: %u, is: %u", TCP_CLIENT_CONFIRMED,
conn->status);
uint8_t f2_public_key[crypto_box_PUBLICKEYBYTES];
uint8_t f2_secret_key[crypto_box_SECRETKEYBYTES];
crypto_box_keypair(f2_public_key, f2_secret_key);
ip_port_tcp_s.port = htons(ports[rand() % NUM_PORTS]);
TCP_Client_Connection *conn2 = new_TCP_connection(ip_port_tcp_s, self_public_key, f2_public_key, f2_secret_key, 0);
routing_response_handler(conn, response_callback, ((void *)conn) + 2);
routing_status_handler(conn, status_callback, (void *)2);
routing_data_handler(conn, data_callback, (void *)3);
oob_data_handler(conn, oob_data_callback, (void *)4);
oob_data_callback_good = response_callback_good = status_callback_good = data_callback_good = 0;
c_sleep(50);
do_TCP_connection(conn);
do_TCP_connection(conn2);
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_TCP_connection(conn);
do_TCP_connection(conn2);
c_sleep(50);
uint8_t data[5] = {1, 2, 3, 4, 5};
memcpy(oob_pubkey, f2_public_key, crypto_box_PUBLICKEYBYTES);
send_oob_packet(conn2, f_public_key, data, 5);
send_routing_request(conn, f2_public_key);
send_routing_request(conn2, f_public_key);
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_TCP_connection(conn);
do_TCP_connection(conn2);
ck_assert_msg(oob_data_callback_good == 1, "oob callback not called");
ck_assert_msg(response_callback_good == 1, "response callback not called");
ck_assert_msg(public_key_cmp(response_callback_public_key, f2_public_key) == 0, "wrong public key");
ck_assert_msg(status_callback_good == 1, "status callback not called");
ck_assert_msg(status_callback_status == 2, "wrong status");
ck_assert_msg(status_callback_connection_id == response_callback_connection_id, "connection ids not equal");
c_sleep(50);
do_TCP_server(tcp_s);
ck_assert_msg(send_data(conn2, 0, data, 5) == 1, "send data failed");
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_TCP_connection(conn);
do_TCP_connection(conn2);
ck_assert_msg(data_callback_good == 1, "data callback not called");
status_callback_good = 0;
send_disconnect_request(conn2, 0);
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_TCP_connection(conn);
do_TCP_connection(conn2);
ck_assert_msg(status_callback_good == 1, "status callback not called");
ck_assert_msg(status_callback_status == 1, "wrong status");
kill_TCP_server(tcp_s);
kill_TCP_connection(conn);
kill_TCP_connection(conn2);
}
END_TEST
START_TEST(test_client_invalid)
{
unix_time_update();
uint8_t self_public_key[crypto_box_PUBLICKEYBYTES];
uint8_t self_secret_key[crypto_box_SECRETKEYBYTES];
crypto_box_keypair(self_public_key, self_secret_key);
uint8_t f_public_key[crypto_box_PUBLICKEYBYTES];
uint8_t f_secret_key[crypto_box_SECRETKEYBYTES];
crypto_box_keypair(f_public_key, f_secret_key);
IP_Port ip_port_tcp_s;
ip_port_tcp_s.port = htons(ports[rand() % NUM_PORTS]);
ip_port_tcp_s.ip.family = AF_INET6;
ip_port_tcp_s.ip.ip6.in6_addr = in6addr_loopback;
TCP_Client_Connection *conn = new_TCP_connection(ip_port_tcp_s, self_public_key, f_public_key, f_secret_key, 0);
c_sleep(50);
do_TCP_connection(conn);
ck_assert_msg(conn->status == TCP_CLIENT_CONNECTING, "Wrong status. Expected: %u, is: %u", TCP_CLIENT_CONNECTING,
conn->status);
c_sleep(5000);
do_TCP_connection(conn);
ck_assert_msg(conn->status == TCP_CLIENT_CONNECTING, "Wrong status. Expected: %u, is: %u", TCP_CLIENT_CONNECTING,
conn->status);
c_sleep(6000);
do_TCP_connection(conn);
ck_assert_msg(conn->status == TCP_CLIENT_DISCONNECTED, "Wrong status. Expected: %u, is: %u", TCP_CLIENT_DISCONNECTED,
conn->status);
kill_TCP_connection(conn);
}
END_TEST
#include "../toxcore/TCP_connection.h"
_Bool tcp_data_callback_called;
static int tcp_data_callback(void *object, int id, const uint8_t *data, uint16_t length)
{
if (object != (void *)120397)
return -1;
if (id != 123)
return -1;
if (length != 6)
return -1;
if (memcmp(data, "Gentoo", length) != 0)
return -1;
tcp_data_callback_called = 1;
return 0;
}
START_TEST(test_tcp_connection)
{
tcp_data_callback_called = 0;
unix_time_update();
uint8_t self_public_key[crypto_box_PUBLICKEYBYTES];
uint8_t self_secret_key[crypto_box_SECRETKEYBYTES];
crypto_box_keypair(self_public_key, self_secret_key);
TCP_Server *tcp_s = new_TCP_server(1, NUM_PORTS, ports, self_secret_key, NULL);
ck_assert_msg(public_key_cmp(tcp_s->public_key, self_public_key) == 0, "Wrong public key");
TCP_Proxy_Info proxy_info;
proxy_info.proxy_type = TCP_PROXY_NONE;
crypto_box_keypair(self_public_key, self_secret_key);
TCP_Connections *tc_1 = new_tcp_connections(self_secret_key, &proxy_info);
ck_assert_msg(public_key_cmp(tc_1->self_public_key, self_public_key) == 0, "Wrong public key");
crypto_box_keypair(self_public_key, self_secret_key);
TCP_Connections *tc_2 = new_tcp_connections(self_secret_key, &proxy_info);
ck_assert_msg(public_key_cmp(tc_2->self_public_key, self_public_key) == 0, "Wrong public key");
IP_Port ip_port_tcp_s;
ip_port_tcp_s.port = htons(ports[rand() % NUM_PORTS]);
ip_port_tcp_s.ip.family = AF_INET6;
ip_port_tcp_s.ip.ip6.in6_addr = in6addr_loopback;
int connection = new_tcp_connection_to(tc_1, tc_2->self_public_key, 123);
ck_assert_msg(connection == 0, "Connection id wrong");
ck_assert_msg(add_tcp_relay_connection(tc_1, connection, ip_port_tcp_s, tcp_s->public_key) == 0,
"Could not add tcp relay to connection\n");
ip_port_tcp_s.port = htons(ports[rand() % NUM_PORTS]);
connection = new_tcp_connection_to(tc_2, tc_1->self_public_key, 123);
ck_assert_msg(connection == 0, "Connection id wrong");
ck_assert_msg(add_tcp_relay_connection(tc_2, connection, ip_port_tcp_s, tcp_s->public_key) == 0,
"Could not add tcp relay to connection\n");
ck_assert_msg(new_tcp_connection_to(tc_2, tc_1->self_public_key, 123) == -1, "Managed to readd same connection\n");
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_tcp_connections(tc_1);
do_tcp_connections(tc_2);
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_tcp_connections(tc_1);
do_tcp_connections(tc_2);
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_tcp_connections(tc_1);
do_tcp_connections(tc_2);
int ret = send_packet_tcp_connection(tc_1, 0, "Gentoo", 6);
ck_assert_msg(ret == 0, "could not send packet.");
set_packet_tcp_connection_callback(tc_2, &tcp_data_callback, (void *) 120397);
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_tcp_connections(tc_1);
do_tcp_connections(tc_2);
ck_assert_msg(tcp_data_callback_called, "could not recv packet.");
ck_assert_msg(tcp_connection_to_online_tcp_relays(tc_1, 0) == 1, "Wrong number of connected relays");
ck_assert_msg(kill_tcp_connection_to(tc_1, 0) == 0, "could not kill connection to\n");
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_tcp_connections(tc_1);
do_tcp_connections(tc_2);
ck_assert_msg(send_packet_tcp_connection(tc_1, 0, "Gentoo", 6) == -1, "could send packet.");
ck_assert_msg(kill_tcp_connection_to(tc_2, 0) == 0, "could not kill connection to\n");
kill_TCP_server(tcp_s);
kill_tcp_connections(tc_1);
kill_tcp_connections(tc_2);
}
END_TEST
_Bool tcp_oobdata_callback_called;
static int tcp_oobdata_callback(void *object, const uint8_t *public_key, unsigned int id, const uint8_t *data,
uint16_t length)
{
if (length != 6)
return -1;
if (memcmp(data, "Gentoo", length) != 0)
return -1;
if (tcp_send_oob_packet(object, id, public_key, data, length) == 0)
tcp_oobdata_callback_called = 1;
return 0;
}
START_TEST(test_tcp_connection2)
{
tcp_oobdata_callback_called = 0;
tcp_data_callback_called = 0;
unix_time_update();
uint8_t self_public_key[crypto_box_PUBLICKEYBYTES];
uint8_t self_secret_key[crypto_box_SECRETKEYBYTES];
crypto_box_keypair(self_public_key, self_secret_key);
TCP_Server *tcp_s = new_TCP_server(1, NUM_PORTS, ports, self_secret_key, NULL);
ck_assert_msg(public_key_cmp(tcp_s->public_key, self_public_key) == 0, "Wrong public key");
TCP_Proxy_Info proxy_info;
proxy_info.proxy_type = TCP_PROXY_NONE;
crypto_box_keypair(self_public_key, self_secret_key);
TCP_Connections *tc_1 = new_tcp_connections(self_secret_key, &proxy_info);
ck_assert_msg(public_key_cmp(tc_1->self_public_key, self_public_key) == 0, "Wrong public key");
crypto_box_keypair(self_public_key, self_secret_key);
TCP_Connections *tc_2 = new_tcp_connections(self_secret_key, &proxy_info);
ck_assert_msg(public_key_cmp(tc_2->self_public_key, self_public_key) == 0, "Wrong public key");
IP_Port ip_port_tcp_s;
ip_port_tcp_s.port = htons(ports[rand() % NUM_PORTS]);
ip_port_tcp_s.ip.family = AF_INET6;
ip_port_tcp_s.ip.ip6.in6_addr = in6addr_loopback;
int connection = new_tcp_connection_to(tc_1, tc_2->self_public_key, 123);
ck_assert_msg(connection == 0, "Connection id wrong");
ck_assert_msg(add_tcp_relay_connection(tc_1, connection, ip_port_tcp_s, tcp_s->public_key) == 0,
"Could not add tcp relay to connection\n");
ck_assert_msg(add_tcp_relay_global(tc_2, ip_port_tcp_s, tcp_s->public_key) == 0, "Could not add global relay");
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_tcp_connections(tc_1);
do_tcp_connections(tc_2);
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_tcp_connections(tc_1);
do_tcp_connections(tc_2);
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_tcp_connections(tc_1);
do_tcp_connections(tc_2);
int ret = send_packet_tcp_connection(tc_1, 0, "Gentoo", 6);
ck_assert_msg(ret == 0, "could not send packet.");
set_oob_packet_tcp_connection_callback(tc_2, &tcp_oobdata_callback, tc_2);
set_packet_tcp_connection_callback(tc_1, &tcp_data_callback, (void *) 120397);
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_tcp_connections(tc_1);
do_tcp_connections(tc_2);
ck_assert_msg(tcp_oobdata_callback_called, "could not recv packet.");
c_sleep(50);
do_TCP_server(tcp_s);
c_sleep(50);
do_tcp_connections(tc_1);
do_tcp_connections(tc_2);
ck_assert_msg(tcp_data_callback_called, "could not recv packet.");
ck_assert_msg(kill_tcp_connection_to(tc_1, 0) == 0, "could not kill connection to\n");
kill_TCP_server(tcp_s);
kill_tcp_connections(tc_1);
kill_tcp_connections(tc_2);
}
END_TEST
Suite *TCP_suite(void)
{
Suite *s = suite_create("TCP");
DEFTESTCASE_SLOW(basic, 5);
DEFTESTCASE_SLOW(some, 10);
DEFTESTCASE_SLOW(client, 10);
DEFTESTCASE_SLOW(client_invalid, 15);
DEFTESTCASE_SLOW(tcp_connection, 20);
DEFTESTCASE_SLOW(tcp_connection2, 20);
return s;
}
int main(int argc, char *argv[])
{
srand((unsigned int) time(NULL));
Suite *TCP = TCP_suite();
SRunner *test_runner = srunner_create(TCP);
int number_failed = 0;
srunner_run_all(test_runner, CK_NORMAL);
number_failed = srunner_ntests_failed(test_runner);
srunner_free(test_runner);
return number_failed;
}
================================================
FILE: auto_tests/assoc_test.c
================================================
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#define AUTO_TEST
#include "../toxcore/DHT.h"
#include "../toxcore/assoc.h"
#include "../toxcore/util.h"
#include
#include
#include
#include
#include "helpers.h"
START_TEST(test_basics)
{
/* TODO: real test */
uint8_t id[crypto_box_PUBLICKEYBYTES];
Assoc *assoc = new_Assoc_default(id);
ck_assert_msg(assoc != NULL, "failed to create default assoc");
kill_Assoc(assoc);
assoc = new_Assoc(17, 4, id); /* results in an assoc of 16/3 */
ck_assert_msg(assoc != NULL, "failed to create customized assoc");
IP_Port ipp;
ipp.ip.family = AF_INET;
ipp.ip.ip4.uint8[0] = 1;
ipp.port = htons(12345);
IPPTs ippts_send;
ippts_send.ip_port = ipp;
ippts_send.timestamp = unix_time();
IP_Port ipp_recv = ipp;
uint8_t res = Assoc_add_entry(assoc, id, &ippts_send, &ipp_recv, 0);
ck_assert_msg(res == 0, "stored self as entry: expected %u, got %u", 0, res);
id[0]++;
res = Assoc_add_entry(assoc, id, &ippts_send, &ipp_recv, 0);
ck_assert_msg(res == 1, "failed to store entry: expected %u, got %u", 1, res);
Assoc_close_entries close_entries;
memset(&close_entries, 0, sizeof(close_entries));
close_entries.count = 4;
close_entries.count_good = 2;
close_entries.wanted_id = id;
Client_data *entries[close_entries.count];
close_entries.result = entries;
uint8_t found = Assoc_get_close_entries(assoc, &close_entries);
ck_assert_msg(found == 1, "get_close_entries(): expected %u, got %u", 1, found);
kill_Assoc(assoc);
}
END_TEST
START_TEST(test_fillup)
{
/* TODO: real test */
int i, j;
uint8_t id[crypto_box_PUBLICKEYBYTES];
//uint32_t a = current_time();
uint32_t a = 2710106197;
srand(a);
for (i = 0; i < crypto_box_PUBLICKEYBYTES; ++i) {
id[i] = rand();
}
Assoc *assoc = new_Assoc(6, 15, id);
ck_assert_msg(assoc != NULL, "failed to create default assoc");
struct entry {
uint8_t id[crypto_box_PUBLICKEYBYTES];
IPPTs ippts_send;
IP_Port ipp_recv;
};
unsigned int fail = 0;
struct entry entries[128];
struct entry closest[8];
for (j = 0; j < 128; ++j) {
for (i = 0; i < crypto_box_PUBLICKEYBYTES; ++i) {
entries[j].id[i] = rand();
}
IP_Port ipp;
ipp.ip.family = AF_INET;
ipp.ip.ip4.uint32 = rand();
ipp.port = rand();
entries[j].ippts_send.ip_port = ipp;
entries[j].ippts_send.timestamp = unix_time();
ipp.ip.ip4.uint32 = rand();
ipp.port = rand();
entries[j].ipp_recv = ipp;
if (j % 16 == 0) {
memcpy(entries[j].id, id, crypto_box_PUBLICKEYBYTES - 30);
memcpy(&closest[j / 16], &entries[j], sizeof(struct entry));
}
uint8_t res = Assoc_add_entry(assoc, entries[j].id, &entries[j].ippts_send, &entries[j].ipp_recv, 1);
ck_assert_msg(res == 1, "failed to store entry: expected %u, got %u, j = %u", 1, res, j);
}
int good = 0;
Assoc_close_entries close_entries;
memset(&close_entries, 0, sizeof(close_entries));
close_entries.count = 8;
close_entries.count_good = 8;
close_entries.wanted_id = id;
Client_data *entri[close_entries.count];
close_entries.result = entri;
uint8_t found = Assoc_get_close_entries(assoc, &close_entries);
ck_assert_msg(found == 8, "get_close_entries(): expected %u, got %u", 1, found);
for (i = 0; i < 8; ++i) {
for (j = 0; j < 8; ++j) {
if (id_equal(entri[j]->public_key, closest[i].id))
++good;
}
}
ck_assert_msg(good == 8, "Entries found were not the closest ones. Only %u/8 were.", good);
//printf("good: %u %u %u\n", good, a, ((uint32_t)current_time() - a));
kill_Assoc(assoc);
}
END_TEST
Suite *Assoc_suite(void)
{
Suite *s = suite_create("Assoc");
DEFTESTCASE(basics);
DEFTESTCASE(fillup);
return s;
}
int main(int argc, char *argv[])
{
unix_time_update();
Suite *Assoc = Assoc_suite();
SRunner *test_runner = srunner_create(Assoc);
srunner_set_fork_status(test_runner, CK_NOFORK);
srunner_run_all(test_runner, CK_NORMAL);
int number_failed = srunner_ntests_failed(test_runner);
srunner_free(test_runner);
return number_failed;
}
================================================
FILE: auto_tests/crypto_test.c
================================================
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "../toxcore/net_crypto.h"
#include
#include
#include
#include
#include
#include
#include "helpers.h"
void rand_bytes(uint8_t *b, size_t blen)
{
size_t i;
for (i = 0; i < blen; i++) {
b[i] = rand();
}
}
// These test vectors are from libsodium's test suite
unsigned char alicesk[32] = {
0x77, 0x07, 0x6d, 0x0a, 0x73, 0x18, 0xa5, 0x7d,
0x3c, 0x16, 0xc1, 0x72, 0x51, 0xb2, 0x66, 0x45,
0xdf, 0x4c, 0x2f, 0x87, 0xeb, 0xc0, 0x99, 0x2a,
0xb1, 0x77, 0xfb, 0xa5, 0x1d, 0xb9, 0x2c, 0x2a
};
unsigned char bobpk[32] = {
0xde, 0x9e, 0xdb, 0x7d, 0x7b, 0x7d, 0xc1, 0xb4,
0xd3, 0x5b, 0x61, 0xc2, 0xec, 0xe4, 0x35, 0x37,
0x3f, 0x83, 0x43, 0xc8, 0x5b, 0x78, 0x67, 0x4d,
0xad, 0xfc, 0x7e, 0x14, 0x6f, 0x88, 0x2b, 0x4f
};
unsigned char nonce[24] = {
0x69, 0x69, 0x6e, 0xe9, 0x55, 0xb6, 0x2b, 0x73,
0xcd, 0x62, 0xbd, 0xa8, 0x75, 0xfc, 0x73, 0xd6,
0x82, 0x19, 0xe0, 0x03, 0x6b, 0x7a, 0x0b, 0x37
};
unsigned char test_m[131] = {
0xbe, 0x07, 0x5f, 0xc5, 0x3c, 0x81, 0xf2, 0xd5,
0xcf, 0x14, 0x13, 0x16, 0xeb, 0xeb, 0x0c, 0x7b,
0x52, 0x28, 0xc5, 0x2a, 0x4c, 0x62, 0xcb, 0xd4,
0x4b, 0x66, 0x84, 0x9b, 0x64, 0x24, 0x4f, 0xfc,
0xe5, 0xec, 0xba, 0xaf, 0x33, 0xbd, 0x75, 0x1a,
0x1a, 0xc7, 0x28, 0xd4, 0x5e, 0x6c, 0x61, 0x29,
0x6c, 0xdc, 0x3c, 0x01, 0x23, 0x35, 0x61, 0xf4,
0x1d, 0xb6, 0x6c, 0xce, 0x31, 0x4a, 0xdb, 0x31,
0x0e, 0x3b, 0xe8, 0x25, 0x0c, 0x46, 0xf0, 0x6d,
0xce, 0xea, 0x3a, 0x7f, 0xa1, 0x34, 0x80, 0x57,
0xe2, 0xf6, 0x55, 0x6a, 0xd6, 0xb1, 0x31, 0x8a,
0x02, 0x4a, 0x83, 0x8f, 0x21, 0xaf, 0x1f, 0xde,
0x04, 0x89, 0x77, 0xeb, 0x48, 0xf5, 0x9f, 0xfd,
0x49, 0x24, 0xca, 0x1c, 0x60, 0x90, 0x2e, 0x52,
0xf0, 0xa0, 0x89, 0xbc, 0x76, 0x89, 0x70, 0x40,
0xe0, 0x82, 0xf9, 0x37, 0x76, 0x38, 0x48, 0x64,
0x5e, 0x07, 0x05
};
unsigned char test_c[147] = {
0xf3, 0xff, 0xc7, 0x70, 0x3f, 0x94, 0x00, 0xe5,
0x2a, 0x7d, 0xfb, 0x4b, 0x3d, 0x33, 0x05, 0xd9,
0x8e, 0x99, 0x3b, 0x9f, 0x48, 0x68, 0x12, 0x73,
0xc2, 0x96, 0x50, 0xba, 0x32, 0xfc, 0x76, 0xce,
0x48, 0x33, 0x2e, 0xa7, 0x16, 0x4d, 0x96, 0xa4,
0x47, 0x6f, 0xb8, 0xc5, 0x31, 0xa1, 0x18, 0x6a,
0xc0, 0xdf, 0xc1, 0x7c, 0x98, 0xdc, 0xe8, 0x7b,
0x4d, 0xa7, 0xf0, 0x11, 0xec, 0x48, 0xc9, 0x72,
0x71, 0xd2, 0xc2, 0x0f, 0x9b, 0x92, 0x8f, 0xe2,
0x27, 0x0d, 0x6f, 0xb8, 0x63, 0xd5, 0x17, 0x38,
0xb4, 0x8e, 0xee, 0xe3, 0x14, 0xa7, 0xcc, 0x8a,
0xb9, 0x32, 0x16, 0x45, 0x48, 0xe5, 0x26, 0xae,
0x90, 0x22, 0x43, 0x68, 0x51, 0x7a, 0xcf, 0xea,
0xbd, 0x6b, 0xb3, 0x73, 0x2b, 0xc0, 0xe9, 0xda,
0x99, 0x83, 0x2b, 0x61, 0xca, 0x01, 0xb6, 0xde,
0x56, 0x24, 0x4a, 0x9e, 0x88, 0xd5, 0xf9, 0xb3,
0x79, 0x73, 0xf6, 0x22, 0xa4, 0x3d, 0x14, 0xa6,
0x59, 0x9b, 0x1f, 0x65, 0x4c, 0xb4, 0x5a, 0x74,
0xe3, 0x55, 0xa5
};
START_TEST(test_known)
{
unsigned char c[147];
unsigned char m[131];
int clen, mlen;
ck_assert_msg(sizeof(c) == sizeof(m) + crypto_box_MACBYTES * sizeof(unsigned char),
"cyphertext should be crypto_box_MACBYTES bytes longer than plaintext");
ck_assert_msg(sizeof(test_c) == sizeof(c), "sanity check failed");
ck_assert_msg(sizeof(test_m) == sizeof(m), "sanity check failed");
clen = encrypt_data(bobpk, alicesk, nonce, test_m, sizeof(test_m) / sizeof(unsigned char), c);
ck_assert_msg(memcmp(test_c, c, sizeof(c)) == 0, "cyphertext doesn't match test vector");
ck_assert_msg(clen == sizeof(c) / sizeof(unsigned char), "wrong ciphertext length");
mlen = decrypt_data(bobpk, alicesk, nonce, test_c, sizeof(test_c) / sizeof(unsigned char), m);
ck_assert_msg(memcmp(test_m, m, sizeof(m)) == 0, "decrypted text doesn't match test vector");
ck_assert_msg(mlen == sizeof(m) / sizeof(unsigned char), "wrong plaintext length");
}
END_TEST
START_TEST(test_fast_known)
{
unsigned char k[crypto_box_BEFORENMBYTES];
unsigned char c[147];
unsigned char m[131];
int clen, mlen;
encrypt_precompute(bobpk, alicesk, k);
ck_assert_msg(sizeof(c) == sizeof(m) + crypto_box_MACBYTES * sizeof(unsigned char),
"cyphertext should be crypto_box_MACBYTES bytes longer than plaintext");
ck_assert_msg(sizeof(test_c) == sizeof(c), "sanity check failed");
ck_assert_msg(sizeof(test_m) == sizeof(m), "sanity check failed");
clen = encrypt_data_symmetric(k, nonce, test_m, sizeof(test_m) / sizeof(unsigned char), c);
ck_assert_msg(memcmp(test_c, c, sizeof(c)) == 0, "cyphertext doesn't match test vector");
ck_assert_msg(clen == sizeof(c) / sizeof(unsigned char), "wrong ciphertext length");
mlen = decrypt_data_symmetric(k, nonce, test_c, sizeof(test_c) / sizeof(unsigned char), m);
ck_assert_msg(memcmp(test_m, m, sizeof(m)) == 0, "decrypted text doesn't match test vector");
ck_assert_msg(mlen == sizeof(m) / sizeof(unsigned char), "wrong plaintext length");
}
END_TEST
START_TEST(test_endtoend)
{
unsigned char pk1[crypto_box_PUBLICKEYBYTES];
unsigned char sk1[crypto_box_SECRETKEYBYTES];
unsigned char pk2[crypto_box_PUBLICKEYBYTES];
unsigned char sk2[crypto_box_SECRETKEYBYTES];
unsigned char k1[crypto_box_BEFORENMBYTES];
unsigned char k2[crypto_box_BEFORENMBYTES];
unsigned char n[crypto_box_NONCEBYTES];
unsigned char m[500];
unsigned char c1[sizeof(m) + crypto_box_MACBYTES];
unsigned char c2[sizeof(m) + crypto_box_MACBYTES];
unsigned char c3[sizeof(m) + crypto_box_MACBYTES];
unsigned char c4[sizeof(m) + crypto_box_MACBYTES];
unsigned char m1[sizeof(m)];
unsigned char m2[sizeof(m)];
unsigned char m3[sizeof(m)];
unsigned char m4[sizeof(m)];
int mlen;
int c1len, c2len, c3len, c4len;
int m1len, m2len, m3len, m4len;
int testno;
// Test 100 random messages and keypairs
for (testno = 0; testno < 100; testno++) {
//Generate random message (random length from 100 to 500)
mlen = (rand() % 400) + 100;
rand_bytes(m, mlen);
rand_bytes(n, crypto_box_NONCEBYTES);
//Generate keypairs
crypto_box_keypair(pk1, sk1);
crypto_box_keypair(pk2, sk2);
//Precompute shared keys
encrypt_precompute(pk2, sk1, k1);
encrypt_precompute(pk1, sk2, k2);
ck_assert_msg(memcmp(k1, k2, crypto_box_BEFORENMBYTES) == 0, "encrypt_precompute: bad");
//Encrypt all four ways
c1len = encrypt_data(pk2, sk1, n, m, mlen, c1);
c2len = encrypt_data(pk1, sk2, n, m, mlen, c2);
c3len = encrypt_data_symmetric(k1, n, m, mlen, c3);
c4len = encrypt_data_symmetric(k2, n, m, mlen, c4);
ck_assert_msg(c1len == c2len && c1len == c3len && c1len == c4len, "cyphertext lengths differ");
ck_assert_msg(c1len == mlen + (int)crypto_box_MACBYTES, "wrong cyphertext length");
ck_assert_msg(memcmp(c1, c2, c1len) == 0 && memcmp(c1, c3, c1len) == 0
&& memcmp(c1, c4, c1len) == 0, "crypertexts differ");
//Decrypt all four ways
m1len = decrypt_data(pk2, sk1, n, c1, c1len, m1);
m2len = decrypt_data(pk1, sk2, n, c1, c1len, m2);
m3len = decrypt_data_symmetric(k1, n, c1, c1len, m3);
m4len = decrypt_data_symmetric(k2, n, c1, c1len, m4);
ck_assert_msg(m1len == m2len && m1len == m3len && m1len == m4len, "decrypted text lengths differ");
ck_assert_msg(m1len == mlen, "wrong decrypted text length");
ck_assert_msg(memcmp(m1, m2, mlen) == 0 && memcmp(m1, m3, mlen) == 0
&& memcmp(m1, m4, mlen) == 0, "decrypted texts differ");
ck_assert_msg(memcmp(m1, m, mlen) == 0, "wrong decrypted text");
}
}
END_TEST
START_TEST(test_large_data)
{
unsigned char k[crypto_box_BEFORENMBYTES];
unsigned char n[crypto_box_NONCEBYTES];
unsigned char m1[MAX_CRYPTO_PACKET_SIZE - crypto_box_MACBYTES];
unsigned char c1[sizeof(m1) + crypto_box_MACBYTES];
unsigned char m1prime[sizeof(m1)];
unsigned char m2[MAX_CRYPTO_PACKET_SIZE];
unsigned char c2[sizeof(m2) + crypto_box_MACBYTES];
int c1len, c2len;
int m1plen;
//Generate random messages
rand_bytes(m1, sizeof(m1));
rand_bytes(m2, sizeof(m2));
rand_bytes(n, crypto_box_NONCEBYTES);
//Generate key
rand_bytes(k, crypto_box_BEFORENMBYTES);
c1len = encrypt_data_symmetric(k, n, m1, sizeof(m1), c1);
c2len = encrypt_data_symmetric(k, n, m2, sizeof(m2), c2);
ck_assert_msg(c1len == sizeof(m1) + crypto_box_MACBYTES, "could not encrypt");
ck_assert_msg(c2len == sizeof(m2) + crypto_box_MACBYTES, "could not encrypt");
m1plen = decrypt_data_symmetric(k, n, c1, c1len, m1prime);
ck_assert_msg(m1plen == sizeof(m1), "decrypted text lengths differ");
ck_assert_msg(memcmp(m1prime, m1, sizeof(m1)) == 0, "decrypted texts differ");
}
END_TEST
START_TEST(test_large_data_symmetric)
{
unsigned char k[crypto_box_KEYBYTES];
unsigned char n[crypto_box_NONCEBYTES];
unsigned char m1[16 * 16 * 16];
unsigned char c1[sizeof(m1) + crypto_box_MACBYTES];
unsigned char m1prime[sizeof(m1)];
int c1len;
int m1plen;
//Generate random messages
rand_bytes(m1, sizeof(m1));
rand_bytes(n, crypto_box_NONCEBYTES);
//Generate key
new_symmetric_key(k);
c1len = encrypt_data_symmetric(k, n, m1, sizeof(m1), c1);
ck_assert_msg(c1len == sizeof(m1) + crypto_box_MACBYTES, "could not encrypt data");
m1plen = decrypt_data_symmetric(k, n, c1, c1len, m1prime);
ck_assert_msg(m1plen == sizeof(m1), "decrypted text lengths differ");
ck_assert_msg(memcmp(m1prime, m1, sizeof(m1)) == 0, "decrypted texts differ");
}
END_TEST
void increment_nonce_number_cmp(uint8_t *nonce, uint32_t num)
{
uint32_t num1, num2;
memcpy(&num1, nonce + (crypto_box_NONCEBYTES - sizeof(num1)), sizeof(num1));
num1 = ntohl(num1);
num2 = num + num1;
if (num2 < num1) {
uint32_t i;
for (i = crypto_box_NONCEBYTES - sizeof(num1); i != 0; --i) {
++nonce[i - 1];
if (nonce[i - 1] != 0)
break;
}
}
num2 = htonl(num2);
memcpy(nonce + (crypto_box_NONCEBYTES - sizeof(num2)), &num2, sizeof(num2));
}
START_TEST(test_increment_nonce)
{
long long unsigned int i;
uint8_t n[crypto_box_NONCEBYTES];
for (i = 0; i < crypto_box_NONCEBYTES; ++i)
n[i] = rand();
uint8_t n1[crypto_box_NONCEBYTES];
memcpy(n1, n, crypto_box_NONCEBYTES);
for (i = 0; i < (1 << 18); ++i) {
increment_nonce_number_cmp(n, 1);
increment_nonce(n1);
ck_assert_msg(memcmp(n, n1, crypto_box_NONCEBYTES) == 0, "Bad increment_nonce function");
}
for (i = 0; i < (1 << 18); ++i) {
uint32_t r = rand();
increment_nonce_number_cmp(n, r);
increment_nonce_number(n1, r);
ck_assert_msg(memcmp(n, n1, crypto_box_NONCEBYTES) == 0, "Bad increment_nonce_number function");
}
}
END_TEST
Suite *crypto_suite(void)
{
Suite *s = suite_create("Crypto");
DEFTESTCASE(known);
DEFTESTCASE(fast_known);
DEFTESTCASE_SLOW(endtoend, 15); /* waiting up to 15 seconds */
DEFTESTCASE(large_data);
DEFTESTCASE(large_data_symmetric);
DEFTESTCASE_SLOW(increment_nonce, 20);
return s;
}
int main(int argc, char *argv[])
{
srand((unsigned int) time(NULL));
Suite *crypto = crypto_suite();
SRunner *test_runner = srunner_create(crypto);
int number_failed = 0;
srunner_run_all(test_runner, CK_NORMAL);
number_failed = srunner_ntests_failed(test_runner);
srunner_free(test_runner);
return number_failed;
}
================================================
FILE: auto_tests/dht_test.c
================================================
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include
#include "../toxcore/tox.h"
#include "../toxcore/DHT.c"
#include "helpers.h"
#define swap(x,y) do \
{ unsigned char swap_temp[sizeof(x) == sizeof(y) ? (signed)sizeof(x) : -1]; \
memcpy(swap_temp,&y,sizeof(x)); \
memcpy(&y,&x, sizeof(x)); \
memcpy(&x,swap_temp,sizeof(x)); \
} while(0)
void mark_bad(IPPTsPng *ipptp)
{
ipptp->timestamp = unix_time() - 2 * BAD_NODE_TIMEOUT;
ipptp->hardening.routes_requests_ok = 0;
ipptp->hardening.send_nodes_ok = 0;
ipptp->hardening.testing_requests = 0;
}
void mark_possible_bad(IPPTsPng *ipptp)
{
ipptp->timestamp = unix_time();
ipptp->hardening.routes_requests_ok = 0;
ipptp->hardening.send_nodes_ok = 0;
ipptp->hardening.testing_requests = 0;
}
void mark_good(IPPTsPng *ipptp)
{
ipptp->timestamp = unix_time();
ipptp->hardening.routes_requests_ok = (HARDENING_ALL_OK >> 0) & 1;
ipptp->hardening.send_nodes_ok = (HARDENING_ALL_OK >> 1) & 1;
ipptp->hardening.testing_requests = (HARDENING_ALL_OK >> 2) & 1;
}
void mark_all_good(Client_data *list, uint32_t length, uint8_t ipv6)
{
uint32_t i;
for (i = 0; i < length; ++i) {
if (ipv6)
mark_good(&list[i].assoc6);
else
mark_good(&list[i].assoc4);
}
}
/* Returns 1 if public_key has a furthest distance to comp_client_id
than all public_key's in the list */
uint8_t is_furthest(const uint8_t *comp_client_id, Client_data *list, uint32_t length, const uint8_t *public_key)
{
uint32_t i;
for (i = 0; i < length; ++i)
if (id_closest(comp_client_id, public_key, list[i].public_key) == 1)
return 0;
return 1;
}
int client_in_list(Client_data *list, uint32_t length, const uint8_t *public_key)
{
int i;
for (i = 0; i < (int)length; ++i)
if (id_equal(public_key, list[i].public_key))
return i;
return -1;
}
void test_addto_lists_update(DHT *dht,
Client_data *list,
uint32_t length,
IP_Port *ip_port)
{
int used, test, test1, test2, found;
IP_Port test_ipp;
uint8_t test_id[crypto_box_PUBLICKEYBYTES];
uint8_t ipv6 = ip_port->ip.family == AF_INET6 ? 1 : 0;
// check id update for existing ip_port
test = rand() % length;
ipport_copy(&test_ipp, ipv6 ? &list[test].assoc6.ip_port : &list[test].assoc4.ip_port);
randombytes(test_id, sizeof(test_id));
used = addto_lists(dht, test_ipp, test_id);
ck_assert_msg(used >= 1, "Wrong number of added clients");
// it is possible to have ip_port duplicates in the list, so ip_port @ found not always equal to ip_port @ test
found = client_in_list(list, length, test_id);
ck_assert_msg(found >= 0, "Client id is not in the list");
ck_assert_msg(ipport_equal(&test_ipp, ipv6 ? &list[found].assoc6.ip_port : &list[found].assoc4.ip_port),
"Client IP_Port is incorrect");
// check ip_port update for existing id
test = rand() % length;
test_ipp.port = rand() % TOX_PORT_DEFAULT;
id_copy(test_id, list[test].public_key);
used = addto_lists(dht, test_ipp, test_id);
ck_assert_msg(used >= 1, "Wrong number of added clients");
// it is not possible to have id duplicates in the list, so id @ found must be equal id @ test
ck_assert_msg(client_in_list(list, length, test_id) == test, "Client id is not in the list");
ck_assert_msg(ipport_equal(&test_ipp, ipv6 ? &list[test].assoc6.ip_port : &list[test].assoc4.ip_port),
"Client IP_Port is incorrect");
// check ip_port update for existing id and ip_port (... port ... id ...)
test1 = rand() % (length / 2);
test2 = rand() % (length / 2) + length / 2;
ipport_copy(&test_ipp, ipv6 ? &list[test1].assoc6.ip_port : &list[test1].assoc4.ip_port);
id_copy(test_id, list[test2].public_key);
if (ipv6) list[test2].assoc6.ip_port.port = -1;
else list[test2].assoc4.ip_port.port = -1;
used = addto_lists(dht, test_ipp, test_id);
ck_assert_msg(used >= 1, "Wrong number of added clients");
ck_assert_msg(client_in_list(list, length, test_id) == test2, "Client id is not in the list");
ck_assert_msg(ipport_equal(&test_ipp, ipv6 ? &list[test2].assoc6.ip_port : &list[test2].assoc4.ip_port),
"Client IP_Port is incorrect");
// check ip_port update for existing id and ip_port (... id ... port ...)
test1 = rand() % (length / 2);
test2 = rand() % (length / 2) + length / 2;
ipport_copy(&test_ipp, ipv6 ? &list[test2].assoc6.ip_port : &list[test2].assoc4.ip_port);
id_copy(test_id, list[test1].public_key);
if (ipv6) list[test1].assoc6.ip_port.port = -1;
else list[test1].assoc4.ip_port.port = -1;
used = addto_lists(dht, test_ipp, test_id);
ck_assert_msg(used >= 1, "Wrong number of added clients");
ck_assert_msg(client_in_list(list, length, test_id) == test1, "Client id is not in the list");
ck_assert_msg(ipport_equal(&test_ipp, ipv6 ? &list[test1].assoc6.ip_port : &list[test1].assoc4.ip_port),
"Client IP_Port is incorrect");
}
void test_addto_lists_bad(DHT *dht,
Client_data *list,
uint32_t length,
IP_Port *ip_port)
{
// check "bad" clients replacement
int used, test1, test2, test3;
uint8_t public_key[crypto_box_PUBLICKEYBYTES], test_id1[crypto_box_PUBLICKEYBYTES], test_id2[crypto_box_PUBLICKEYBYTES],
test_id3[crypto_box_PUBLICKEYBYTES];
uint8_t ipv6 = ip_port->ip.family == AF_INET6 ? 1 : 0;
randombytes(public_key, sizeof(public_key));
mark_all_good(list, length, ipv6);
test1 = rand() % (length / 3);
test2 = rand() % (length / 3) + length / 3;
test3 = rand() % (length / 3) + 2 * length / 3;
ck_assert_msg(!(test1 == test2 || test1 == test3 || test2 == test3), "Wrong test indices are chosen");
id_copy((uint8_t *)&test_id1, list[test1].public_key);
id_copy((uint8_t *)&test_id2, list[test2].public_key);
id_copy((uint8_t *)&test_id3, list[test3].public_key);
// mark nodes as "bad"
if (ipv6) {
mark_bad(&list[test1].assoc6);
mark_bad(&list[test2].assoc6);
mark_bad(&list[test3].assoc6);
} else {
mark_bad(&list[test1].assoc4);
mark_bad(&list[test2].assoc4);
mark_bad(&list[test3].assoc4);
}
ip_port->port += 1;
used = addto_lists(dht, *ip_port, public_key);
ck_assert_msg(used >= 1, "Wrong number of added clients");
ck_assert_msg(client_in_list(list, length, public_key) >= 0, "Client id is not in the list");
ck_assert_msg(client_in_list(list, length, test_id2) >= 0, "Wrong bad client removed");
ck_assert_msg(client_in_list(list, length, test_id3) >= 0, "Wrong bad client removed");
}
void test_addto_lists_possible_bad(DHT *dht,
Client_data *list,
uint32_t length,
IP_Port *ip_port,
const uint8_t *comp_client_id)
{
// check "possibly bad" clients replacement
int used, test1, test2, test3;
uint8_t public_key[crypto_box_PUBLICKEYBYTES], test_id1[crypto_box_PUBLICKEYBYTES], test_id2[crypto_box_PUBLICKEYBYTES],
test_id3[crypto_box_PUBLICKEYBYTES];
uint8_t ipv6 = ip_port->ip.family == AF_INET6 ? 1 : 0;
randombytes(public_key, sizeof(public_key));
mark_all_good(list, length, ipv6);
test1 = rand() % (length / 3);
test2 = rand() % (length / 3) + length / 3;
test3 = rand() % (length / 3) + 2 * length / 3;
ck_assert_msg(!(test1 == test2 || test1 == test3 || test2 == test3), "Wrong test indices are chosen");
id_copy((uint8_t *)&test_id1, list[test1].public_key);
id_copy((uint8_t *)&test_id2, list[test2].public_key);
id_copy((uint8_t *)&test_id3, list[test3].public_key);
// mark nodes as "possibly bad"
if (ipv6) {
mark_possible_bad(&list[test1].assoc6);
mark_possible_bad(&list[test2].assoc6);
mark_possible_bad(&list[test3].assoc6);
} else {
mark_possible_bad(&list[test1].assoc4);
mark_possible_bad(&list[test2].assoc4);
mark_possible_bad(&list[test3].assoc4);
}
ip_port->port += 1;
used = addto_lists(dht, *ip_port, public_key);
ck_assert_msg(used >= 1, "Wrong number of added clients");
ck_assert_msg(client_in_list(list, length, public_key) >= 0, "Client id is not in the list");
int inlist_id1 = client_in_list(list, length, test_id1) >= 0;
int inlist_id2 = client_in_list(list, length, test_id2) >= 0;
int inlist_id3 = client_in_list(list, length, test_id3) >= 0;
ck_assert_msg(inlist_id1 + inlist_id2 + inlist_id3 == 2, "Wrong client removed");
if (!inlist_id1) {
ck_assert_msg(id_closest(comp_client_id, test_id2, test_id1) == 1,
"Id has been removed but is closer to than another one");
ck_assert_msg(id_closest(comp_client_id, test_id3, test_id1) == 1,
"Id has been removed but is closer to than another one");
} else if (!inlist_id2) {
ck_assert_msg(id_closest(comp_client_id, test_id1, test_id2) == 1,
"Id has been removed but is closer to than another one");
ck_assert_msg(id_closest(comp_client_id, test_id3, test_id2) == 1,
"Id has been removed but is closer to than another one");
} else if (!inlist_id3) {
ck_assert_msg(id_closest(comp_client_id, test_id1, test_id3) == 1,
"Id has been removed but is closer to than another one");
ck_assert_msg(id_closest(comp_client_id, test_id2, test_id3) == 1,
"Id has been removed but is closer to than another one");
}
}
void test_addto_lists_good(DHT *dht,
Client_data *list,
uint32_t length,
IP_Port *ip_port,
const uint8_t *comp_client_id)
{
uint8_t public_key[crypto_box_PUBLICKEYBYTES];
uint8_t ipv6 = ip_port->ip.family == AF_INET6 ? 1 : 0;
mark_all_good(list, length, ipv6);
// check "good" client id replacement
do {
randombytes(public_key, sizeof(public_key));
} while (is_furthest(comp_client_id, list, length, public_key));
ip_port->port += 1;
addto_lists(dht, *ip_port, public_key);
ck_assert_msg(client_in_list(list, length, public_key) >= 0, "Good client id is not in the list");
// check "good" client id skip
do {
randombytes(public_key, sizeof(public_key));
} while (!is_furthest(comp_client_id, list, length, public_key));
ip_port->port += 1;
addto_lists(dht, *ip_port, public_key);
ck_assert_msg(client_in_list(list, length, public_key) == -1, "Good client id is in the list");
}
void test_addto_lists(IP ip)
{
Networking_Core *net = new_networking(ip, TOX_PORT_DEFAULT);
ck_assert_msg(net != 0, "Failed to create Networking_Core");
DHT *dht = new_DHT(net);
ck_assert_msg(dht != 0, "Failed to create DHT");
IP_Port ip_port = { .ip = ip, .port = TOX_PORT_DEFAULT };
uint8_t public_key[crypto_box_PUBLICKEYBYTES];
int i, used;
// check lists filling
for (i = 0; i < MAX(LCLIENT_LIST, MAX_FRIEND_CLIENTS); ++i) {
randombytes(public_key, sizeof(public_key));
used = addto_lists(dht, ip_port, public_key);
ck_assert_msg(used == dht->num_friends + 1, "Wrong number of added clients with existing ip_port");
}
for (i = 0; i < MAX(LCLIENT_LIST, MAX_FRIEND_CLIENTS); ++i) {
ip_port.port += 1;
used = addto_lists(dht, ip_port, public_key);
ck_assert_msg(used == dht->num_friends + 1, "Wrong number of added clients with existing public_key");
}
for (i = 0; i < MAX(LCLIENT_LIST, MAX_FRIEND_CLIENTS); ++i) {
ip_port.port += 1;
randombytes(public_key, sizeof(public_key));
used = addto_lists(dht, ip_port, public_key);
ck_assert_msg(used >= 1, "Wrong number of added clients");
}
/*check: Current behavior if there are two clients with the same id is
* to replace the first ip by the second. */
test_addto_lists_update(dht, dht->close_clientlist, LCLIENT_LIST, &ip_port);
for (i = 0; i < dht->num_friends; ++i)
test_addto_lists_update(dht, dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, &ip_port);
// check "bad" entries
test_addto_lists_bad(dht, dht->close_clientlist, LCLIENT_LIST, &ip_port);
for (i = 0; i < dht->num_friends; ++i)
test_addto_lists_bad(dht, dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, &ip_port);
// check "possibly bad" entries
/*
test_addto_lists_possible_bad(dht, dht->close_clientlist, LCLIENT_LIST, &ip_port, dht->self_public_key);
for (i = 0; i < dht->num_friends; ++i)
test_addto_lists_possible_bad(dht, dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, &ip_port,
dht->friends_list[i].public_key);
*/
// check "good" entries
test_addto_lists_good(dht, dht->close_clientlist, LCLIENT_LIST, &ip_port, dht->self_public_key);
for (i = 0; i < dht->num_friends; ++i)
test_addto_lists_good(dht, dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, &ip_port,
dht->friends_list[i].public_key);
kill_DHT(dht);
kill_networking(net);
}
START_TEST(test_addto_lists_ipv4)
{
IP ip;
ip_init(&ip, 0);
test_addto_lists(ip);
}
END_TEST
START_TEST(test_addto_lists_ipv6)
{
IP ip;
ip_init(&ip, 1);
test_addto_lists(ip);
}
END_TEST
#define DHT_DEFAULT_PORT (TOX_PORT_DEFAULT + 20)
#define DHT_LIST_LENGTH 128
void print_pk(uint8_t *public_key)
{
uint32_t j;
for (j = 0; j < crypto_box_PUBLICKEYBYTES; j++) {
printf("%02hhX", public_key[j]);
}
printf("\n");
}
void test_add_to_list(uint8_t cmp_list[][crypto_box_PUBLICKEYBYTES + 1], unsigned int length, const uint8_t *pk,
const uint8_t *cmp_pk)
{
uint8_t p_b[crypto_box_PUBLICKEYBYTES];
unsigned int i;
for (i = 0; i < length; ++i) {
if (!cmp_list[i][crypto_box_PUBLICKEYBYTES]) {
memcpy(cmp_list[i], pk, crypto_box_PUBLICKEYBYTES);
cmp_list[i][crypto_box_PUBLICKEYBYTES] = 1;
return;
} else {
if (memcmp(cmp_list[i], pk, crypto_box_PUBLICKEYBYTES) == 0) {
return;
}
}
}
for (i = 0; i < length; ++i) {
if (id_closest(cmp_pk, cmp_list[i], pk) == 2) {
memcpy(p_b, cmp_list[i], crypto_box_PUBLICKEYBYTES);
memcpy(cmp_list[i], pk, crypto_box_PUBLICKEYBYTES);
test_add_to_list(cmp_list, length, p_b, cmp_pk);
break;
}
}
}
#define NUM_DHT 100
void test_list_main()
{
DHT *dhts[NUM_DHT];
uint8_t cmp_list1[NUM_DHT][MAX_FRIEND_CLIENTS][crypto_box_PUBLICKEYBYTES + 1];
memset(cmp_list1, 0, sizeof(cmp_list1));
IP ip;
ip_init(&ip, 1);
unsigned int i, j, k, l;
for (i = 0; i < NUM_DHT; ++i) {
IP ip;
ip_init(&ip, 1);
dhts[i] = new_DHT(new_networking(ip, DHT_DEFAULT_PORT + i));
ck_assert_msg(dhts[i] != 0, "Failed to create dht instances %u", i);
ck_assert_msg(dhts[i]->net->port != DHT_DEFAULT_PORT + i, "Bound to wrong port");
}
for (j = 0; j < NUM_DHT; ++j) {
for (i = 1; i < NUM_DHT; ++i) {
test_add_to_list(cmp_list1[j], MAX_FRIEND_CLIENTS, dhts[(i + j) % NUM_DHT]->self_public_key, dhts[j]->self_public_key);
}
}
for (j = 0; j < NUM_DHT; ++j) {
for (i = 0; i < NUM_DHT; ++i) {
if (i == j)
continue;
IP_Port ip_port;
ip_init(&ip_port.ip, 0);
ip_port.ip.ip4.uint32 = rand();
ip_port.port = rand() % (UINT16_MAX - 1);
++ip_port.port;
addto_lists(dhts[j], ip_port, dhts[i]->self_public_key);
}
}
/*
print_pk(dhts[0]->self_public_key);
for (i = 0; i < MAX_FRIEND_CLIENTS; ++i) {
printf("----Entry %u----\n", i);
print_pk(cmp_list1[i]);
}
*/
unsigned int m_count = 0;
for (l = 0; l < NUM_DHT; ++l) {
for (i = 0; i < MAX_FRIEND_CLIENTS; ++i) {
for (j = 1; j < NUM_DHT; ++j) {
if (memcmp(cmp_list1[l][i], dhts[(l + j) % NUM_DHT]->self_public_key, crypto_box_PUBLICKEYBYTES) != 0)
continue;
unsigned int count = 0;
for (k = 0; k < LCLIENT_LIST; ++k) {
if (memcmp(dhts[l]->self_public_key, dhts[(l + j) % NUM_DHT]->close_clientlist[k].public_key,
crypto_box_PUBLICKEYBYTES) == 0)
++count;
}
if (count != 1) {
print_pk(dhts[l]->self_public_key);
for (k = 0; k < MAX_FRIEND_CLIENTS; ++k) {
printf("----Entry %u----\n", k);
print_pk(cmp_list1[l][k]);
}
for (k = 0; k < LCLIENT_LIST; ++k) {
printf("----Closel %u----\n", k);
print_pk(dhts[(l + j) % NUM_DHT]->close_clientlist[k].public_key);
}
print_pk(dhts[(l + j) % NUM_DHT]->self_public_key);
}
ck_assert_msg(count == 1, "Nodes in search don't know ip of friend. %u %u %u", i, j, count);
Node_format ln[MAX_SENT_NODES];
int n = get_close_nodes(dhts[(l + j) % NUM_DHT], dhts[l]->self_public_key, ln, 0, 1, 0);
ck_assert_msg(n == MAX_SENT_NODES, "bad num close %u | %u %u", n, i, j);
count = 0;
for (k = 0; k < MAX_SENT_NODES; ++k) {
if (memcmp(dhts[l]->self_public_key, ln[k].public_key, crypto_box_PUBLICKEYBYTES) == 0)
++count;
}
ck_assert_msg(count == 1, "Nodes in search don't know ip of friend. %u %u %u", i, j, count);
/*
for (k = 0; k < MAX_SENT_NODES; ++k) {
printf("----gn %u----\n", k);
print_pk(ln[k].public_key);
}*/
++m_count;
}
}
}
ck_assert_msg(m_count == (NUM_DHT) * (MAX_FRIEND_CLIENTS), "Bad count. %u != %u", m_count,
(NUM_DHT) * (MAX_FRIEND_CLIENTS));
for (i = 0; i < NUM_DHT; ++i) {
void *n = dhts[i]->net;
kill_DHT(dhts[i]);
kill_networking(n);
}
}
START_TEST(test_list)
{
unsigned int i;
for (i = 0; i < 10; ++i)
test_list_main();
}
END_TEST
void ip_callback(void *data, int32_t number, IP_Port ip_port)
{
}
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
#define c_sleep(x) Sleep(1*x)
#else
#include
#define c_sleep(x) usleep(1000*x)
#endif
#define NUM_DHT_FRIENDS 20
START_TEST(test_DHT_test)
{
uint32_t to_comp = 8394782;
DHT *dhts[NUM_DHT];
unsigned int i, j;
for (i = 0; i < NUM_DHT; ++i) {
IP ip;
ip_init(&ip, 1);
dhts[i] = new_DHT(new_networking(ip, DHT_DEFAULT_PORT + i));
ck_assert_msg(dhts[i] != 0, "Failed to create dht instances %u", i);
ck_assert_msg(dhts[i]->net->port != DHT_DEFAULT_PORT + i, "Bound to wrong port");
}
struct {
uint16_t tox1;
uint16_t tox2;
} pairs[NUM_DHT_FRIENDS];
uint8_t address[TOX_ADDRESS_SIZE];
unsigned int num_f = 0;
for (i = 0; i < NUM_DHT_FRIENDS; ++i) {
loop_top:
pairs[i].tox1 = rand() % NUM_DHT;
pairs[i].tox2 = (pairs[i].tox1 + (rand() % (NUM_DHT - 1)) + 1) % NUM_DHT;
for (j = 0; j < i; ++j) {
if (pairs[j].tox2 == pairs[i].tox2 && pairs[j].tox1 == pairs[i].tox1)
goto loop_top;
}
uint16_t lock_count = 0;
ck_assert_msg(DHT_addfriend(dhts[pairs[i].tox2], dhts[pairs[i].tox1]->self_public_key, &ip_callback, &to_comp, 1337,
&lock_count) == 0, "Failed to add friend");
ck_assert_msg(lock_count == 1, "bad lock count: %u %u", lock_count, i);
}
for (i = 0; i < NUM_DHT; ++i) {
IP_Port ip_port;
ip_init(&ip_port.ip, 1);
ip_port.ip.ip6.uint8[15] = 1;
ip_port.port = htons(DHT_DEFAULT_PORT + i);
DHT_bootstrap(dhts[(i - 1) % NUM_DHT], ip_port, dhts[i]->self_public_key);
}
while (1) {
uint16_t counter = 0;
for (i = 0; i < NUM_DHT_FRIENDS; ++i) {
IP_Port a;
if (DHT_getfriendip(dhts[pairs[i].tox2], dhts[pairs[i].tox1]->self_public_key, &a) == 1)
++counter;
}
if (counter == NUM_DHT_FRIENDS) {
break;
}
for (i = 0; i < NUM_DHT; ++i) {
networking_poll(dhts[i]->net);
do_DHT(dhts[i]);
}
c_sleep(500);
}
for (i = 0; i < NUM_DHT; ++i) {
void *n = dhts[i]->net;
kill_DHT(dhts[i]);
kill_networking(n);
}
}
END_TEST
Suite *dht_suite(void)
{
Suite *s = suite_create("DHT");
//DEFTESTCASE(addto_lists_ipv4);
//DEFTESTCASE(addto_lists_ipv6);
DEFTESTCASE_SLOW(list, 20);
DEFTESTCASE_SLOW(DHT_test, 50);
return s;
}
int main(int argc, char *argv[])
{
srand((unsigned int) time(NULL));
Suite *dht = dht_suite();
SRunner *test_runner = srunner_create(dht);
int number_failed = 0;
//srunner_set_fork_status(test_runner, CK_NOFORK);
srunner_run_all(test_runner, CK_NORMAL);
number_failed = srunner_ntests_failed(test_runner);
srunner_free(test_runner);
return number_failed;
}
================================================
FILE: auto_tests/encryptsave_test.c
================================================
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include
#include
#include
#include
#include
#include "helpers.h"
#include "../toxcore/tox.h"
#include "../toxencryptsave/toxencryptsave.h"
#include "../toxcore/crypto_core.h"
#ifdef VANILLA_NACL
#include "../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/crypto_pwhash_scryptsalsa208sha256.h"
#endif
unsigned char salt[32] = {0xB1, 0xC2, 0x09, 0xEE, 0x50, 0x6C, 0xF0, 0x20, 0xC4, 0xD6, 0xEB, 0xC0, 0x44, 0x51, 0x3B, 0x60, 0x4B, 0x39, 0x4A, 0xCF, 0x09, 0x53, 0x4F, 0xEA, 0x08, 0x41, 0xFA, 0xCA, 0x66, 0xD2, 0x68, 0x7F};
unsigned char known_key[crypto_box_BEFORENMBYTES] = {0x29, 0x36, 0x1c, 0x9e, 0x65, 0xbb, 0x46, 0x8b, 0xde, 0xa1, 0xac, 0xf, 0xd5, 0x11, 0x81, 0xc8, 0x29, 0x28, 0x17, 0x23, 0xa6, 0xc3, 0x6b, 0x77, 0x2e, 0xd7, 0xd3, 0x10, 0xeb, 0xd2, 0xf7, 0xc8};
char *pw = "hunter2";
unsigned int pwlen = 7;
unsigned char known_key2[crypto_box_BEFORENMBYTES] = {0x7a, 0xfa, 0x95, 0x45, 0x36, 0x8a, 0xa2, 0x5c, 0x40, 0xfd, 0xc0, 0xe2, 0x35, 0x8, 0x7, 0x88, 0xfa, 0xf9, 0x37, 0x86, 0xeb, 0xff, 0x50, 0x4f, 0x3, 0xe2, 0xf6, 0xd9, 0xef, 0x9, 0x17, 0x1};
// same as above, except standard opslimit instead of extra ops limit for test_known_kdf, and hash pw before kdf for compat
/* cause I'm shameless */
void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata)
{
if (*((uint32_t *)userdata) != 974536)
return;
if (length == 7 && memcmp("Gentoo", data, 7) == 0) {
tox_friend_add_norequest(m, public_key, 0);
}
}
START_TEST(test_known_kdf)
{
unsigned char out[crypto_box_BEFORENMBYTES];
crypto_pwhash_scryptsalsa208sha256(out,
crypto_box_BEFORENMBYTES,
pw,
pwlen,
salt,
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE * 8,
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE);
ck_assert_msg(memcmp(out, known_key, crypto_box_BEFORENMBYTES) == 0, "derived key is wrong");
}
END_TEST
START_TEST(test_save_friend)
{
Tox *tox1 = tox_new(0, 0);
Tox *tox2 = tox_new(0, 0);
ck_assert_msg(tox1 || tox2, "Failed to create 2 tox instances");
uint32_t to_compare = 974536;
tox_callback_friend_request(tox2, accept_friend_request, &to_compare);
uint8_t address[TOX_ADDRESS_SIZE];
tox_self_get_address(tox2, address);
uint32_t test = tox_friend_add(tox1, address, (uint8_t *)"Gentoo", 7, 0);
ck_assert_msg(test != UINT32_MAX, "Failed to add friend");
size_t size = tox_get_savedata_size(tox1);
uint8_t data[size];
tox_get_savedata(tox1, data);
size_t size2 = size + TOX_PASS_ENCRYPTION_EXTRA_LENGTH;
uint8_t enc_data[size2];
TOX_ERR_ENCRYPTION error1;
bool ret = tox_pass_encrypt(data, size, "correcthorsebatterystaple", 25, enc_data, &error1);
ck_assert_msg(ret, "failed to encrypted save: %u", error1);
ck_assert_msg(tox_is_data_encrypted(enc_data), "magic number missing");
struct Tox_Options options;
tox_options_default(&options);
options.savedata_type = TOX_SAVEDATA_TYPE_TOX_SAVE;
options.savedata_data = enc_data;
options.savedata_length = size2;
TOX_ERR_NEW err2;
Tox *tox3 = tox_new(&options, &err2);
ck_assert_msg(err2 == TOX_ERR_NEW_LOAD_ENCRYPTED, "wrong error! %u. should fail with %u", err2,
TOX_ERR_NEW_LOAD_ENCRYPTED);
uint8_t dec_data[size];
TOX_ERR_DECRYPTION err3;
ret = tox_pass_decrypt(enc_data, size2, "correcthorsebatterystaple", 25, dec_data, &err3);
ck_assert_msg(ret, "failed to decrypt save: %u", err3);
options.savedata_data = dec_data;
options.savedata_length = size;
tox3 = tox_new(&options, &err2);
ck_assert_msg(err2 == TOX_ERR_NEW_OK, "failed to load from decrypted data: %u", err2);
uint8_t address2[TOX_PUBLIC_KEY_SIZE];
ret = tox_friend_get_public_key(tox3, 0, address2, 0);
ck_assert_msg(ret, "no friends!");
ck_assert_msg(memcmp(address, address2, TOX_PUBLIC_KEY_SIZE) == 0, "addresses don't match!");
size = tox_get_savedata_size(tox3);
uint8_t data2[size];
tox_get_savedata(tox3, data2);
TOX_PASS_KEY key;
memcpy(key.salt, salt, 32);
memcpy(key.key, known_key2, crypto_box_BEFORENMBYTES);
size2 = size + TOX_PASS_ENCRYPTION_EXTRA_LENGTH;
uint8_t encdata2[size2];
ret = tox_pass_key_encrypt(data2, size, &key, encdata2, &error1);
ck_assert_msg(ret, "failed to key encrypt %u", error1);
ck_assert_msg(tox_is_data_encrypted(encdata2), "magic number the second missing");
uint8_t out1[size], out2[size];
ret = tox_pass_decrypt(encdata2, size2, pw, pwlen, out1, &err3);
ck_assert_msg(ret, "failed to pw decrypt %u", err3);
ret = tox_pass_key_decrypt(encdata2, size2, &key, out2, &err3);
ck_assert_msg(ret, "failed to key decrypt %u", err3);
ck_assert_msg(memcmp(out1, out2, size) == 0, "differing output data");
// and now with the code in use (I only bothered with manually to debug this, and it seems a waste
// to remove the manual check now that it's there)
options.savedata_data = out1;
options.savedata_length = size;
Tox *tox4 = tox_new(&options, &err2);
ck_assert_msg(err2 == TOX_ERR_NEW_OK, "failed to new the third");
uint8_t address5[TOX_PUBLIC_KEY_SIZE];
ret = tox_friend_get_public_key(tox4, 0, address5, 0);
ck_assert_msg(ret, "no friends! the third");
ck_assert_msg(memcmp(address, address2, TOX_PUBLIC_KEY_SIZE) == 0, "addresses don't match! the third");
tox_kill(tox1);
tox_kill(tox2);
tox_kill(tox3);
tox_kill(tox4);
}
END_TEST
START_TEST(test_keys)
{
TOX_ERR_ENCRYPTION encerr;
TOX_ERR_DECRYPTION decerr;
TOX_ERR_KEY_DERIVATION keyerr;
TOX_PASS_KEY key;
bool ret = tox_derive_key_from_pass("123qweasdzxc", 12, &key, &keyerr);
ck_assert_msg(ret, "generic failure 1: %u", keyerr);
uint8_t *string = "No Patrick, mayonnaise is not an instrument."; // 44
uint8_t encrypted[44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH];
ret = tox_pass_key_encrypt(string, 44, &key, encrypted, &encerr);
ck_assert_msg(ret, "generic failure 2: %u", encerr);
uint8_t encrypted2[44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH];
ret = tox_pass_encrypt(string, 44, "123qweasdzxc", 12, encrypted2, &encerr);
ck_assert_msg(ret, "generic failure 3: %u", encerr);
uint8_t out1[44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH];
uint8_t out2[44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH];
ret = tox_pass_key_decrypt(encrypted, 44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH, &key, out1, &decerr);
ck_assert_msg(ret, "generic failure 4: %u", decerr);
ck_assert_msg(memcmp(out1, string, 44) == 0, "decryption 1 failed");
ret = tox_pass_decrypt(encrypted2, 44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH, "123qweasdzxc", 12, out2, &decerr);
ck_assert_msg(ret, "generic failure 5: %u", decerr);
ck_assert_msg(memcmp(out2, string, 44) == 0, "decryption 2 failed");
ret = tox_pass_decrypt(encrypted2, 44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH, NULL, 0, out2, &decerr);
ck_assert_msg(!ret, "Decrypt succeeded with wrong pass");
ck_assert_msg(decerr != TOX_ERR_DECRYPTION_FAILED, "Bad error code %u", decerr);
// test that pass_decrypt can decrypt things from pass_key_encrypt
ret = tox_pass_decrypt(encrypted, 44 + TOX_PASS_ENCRYPTION_EXTRA_LENGTH, "123qweasdzxc", 12, out1, &decerr);
ck_assert_msg(ret, "generic failure 6: %u", decerr);
ck_assert_msg(memcmp(out1, string, 44) == 0, "decryption 3 failed");
uint8_t salt[TOX_PASS_SALT_LENGTH];
ck_assert_msg(tox_get_salt(encrypted, salt), "couldn't get salt");
TOX_PASS_KEY key2;
ret = tox_derive_key_with_salt("123qweasdzxc", 12, salt, &key2, &keyerr);
ck_assert_msg(ret, "generic failure 7: %u", keyerr);
ck_assert_msg(0 == memcmp(&key, &key2, sizeof(TOX_PASS_KEY)), "salt comparison failed");
}
END_TEST
Suite *encryptsave_suite(void)
{
Suite *s = suite_create("encryptsave");
DEFTESTCASE_SLOW(known_kdf, 60);
DEFTESTCASE_SLOW(save_friend, 20);
DEFTESTCASE_SLOW(keys, 30);
return s;
}
int main(int argc, char *argv[])
{
srand((unsigned int) time(NULL));
Suite *encryptsave = encryptsave_suite();
SRunner *test_runner = srunner_create(encryptsave);
int number_failed = 0;
srunner_run_all(test_runner, CK_NORMAL);
number_failed = srunner_ntests_failed(test_runner);
srunner_free(test_runner);
return number_failed;
}
================================================
FILE: auto_tests/friends_test.c
================================================
/* Unit testing for friend requests, statuses, and messages.
* Purpose: Check that messaging functions actually do what
* they're supposed to by setting up two local clients.
*
* Design: (Subject to change.)
* 1. Parent sends a friend request, and waits for a response.
* It it doesn't get one, it kills the child.
* 2. Child gets friend request, accepts, then waits for a status change.
* 3. The parent waits on a status change, killing the child if it takes
* too long.
* 4. The child gets the status change, then sends a message. After that,
* it returns. If if doesn't get the status change, it just loops forever.
* 5. After getting the status change, the parent waits for a message, on getting
* one, it waits on the child to return, then returns 0.
*
* Note about "waiting":
* Wait time is decided by WAIT_COUNT and WAIT_TIME. c_sleep(WAIT_TIME) WAIT_COUNT
* times. This is used both to ensure that we don't loop forever on a broken build,
* and that we don't get too slow with messaging. The current time is 15 seconds. */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "../toxcore/friend_requests.h"
#include "../toxcore/Messenger.h"
#include
#include
#include
#include
#include
#define WAIT_COUNT 30
#define WAIT_TIME 500
#ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS MAP_ANON
#endif
/* first step, second step */
#define FIRST_FLAG 0x1
#define SECOND_FLAG 0x2
/* ensure that we sleep in milliseconds */
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
#define c_sleep(x) Sleep(x)
#else
#define c_sleep(x) usleep(1000*x)
#endif
#define PORT 33445
static Messenger *m;
uint8_t *parent_id = NULL;
uint8_t *child_id = NULL;
pid_t child_pid = 0;
int request_flags = 0;
void do_tox(DHT *dht)
{
static int dht_on = 0;
if (!dht_on && DHT_isconnected(dht)) {
dht_on = 1;
} else if (dht_on && !DHT_isconnected(dht)) {
dht_on = 0;
}
doMessenger(m);
}
void parent_confirm_message(Messenger *m, int num, uint8_t *data, uint16_t length, void *userdata)
{
puts("OK");
request_flags |= SECOND_FLAG;
}
void parent_confirm_status(Messenger *m, int num, uint8_t *data, uint16_t length, void *userdata)
{
puts("OK");
request_flags |= FIRST_FLAG;
}
int parent_friend_request(DHT *dht)
{
char *message = "Watson, come here, I need you.";
int len = strlen(message);
int i = 0;
fputs("Sending child request.", stdout);
fflush(stdout);
m_addfriend(m, child_id, (uint8_t *)message, len);
/* wait on the status change */
for (i = 0; i < WAIT_COUNT; i++) {
do_tox(dht);
if (request_flags & FIRST_FLAG)
break;
fputs(".", stdout);
fflush(stdout);
c_sleep(WAIT_TIME);
}
if (!(request_flags & FIRST_FLAG)) {
fputs("\nfriends_test: The child took to long to respond!\n"
"Friend requests may be broken, failing build!\n", stderr);
kill(child_pid, SIGKILL);
return -1;
}
return 0;
}
void child_got_request(Messenger *m, uint8_t *public_key, uint8_t *data, uint16_t length, void *userdata)
{
fputs("OK\nsending status to parent", stdout);
fflush(stdout);
m_addfriend_norequest(m, public_key);
request_flags |= FIRST_FLAG;
}
void child_got_statuschange(Messenger *m, int friend_num, uint8_t *string, uint16_t length, void *userdata)
{
request_flags |= SECOND_FLAG;
}
int parent_wait_for_message(DHT *dht)
{
int i = 0;
fputs("Parent waiting for message.", stdout);
fflush(stdout);
for (i = 0; i < WAIT_COUNT; i++) {
do_tox(dht);
if (request_flags & SECOND_FLAG)
break;
fputs(".", stdout);
fflush(stdout);
c_sleep(WAIT_TIME);
}
if (!(request_flags & SECOND_FLAG)) {
fputs("\nParent hasn't received the message yet!\n"
"Messaging may be broken, failing the build!\n", stderr);
kill(child_pid, SIGKILL);
return -1;
}
return 0;
}
void cleanup(void)
{
munmap(parent_id, crypto_box_PUBLICKEYBYTES);
munmap(child_id, crypto_box_PUBLICKEYBYTES);
puts("============= END TEST =============");
}
int main(int argc, char *argv[])
{
puts("=========== FRIENDS_TEST ===========");
/* set up the global memory */
parent_id = mmap(NULL, crypto_box_PUBLICKEYBYTES, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
child_id = mmap(NULL, crypto_box_PUBLICKEYBYTES, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
fputs("friends_test: Starting test...\n", stdout);
if ((child_pid = fork()) == 0) {
/* child */
int i = 0;
char *message = "Y-yes Mr. Watson?";
m = initMessenger();
Messenger_save(m, child_id);
msync(child_id, crypto_box_PUBLICKEYBYTES, MS_SYNC);
m_callback_friendrequest(m, child_got_request, NULL);
m_callback_statusmessage(m, child_got_statuschange, NULL);
/* wait on the friend request */
while (!(request_flags & FIRST_FLAG))
do_tox(m->dht);
/* wait for the status change */
while (!(request_flags & SECOND_FLAG))
do_tox(m->dht);
for (i = 0; i < 6; i++) {
/* send the message six times, just to be sure */
m_sendmessage(m, 0, (uint8_t *)message, strlen(message));
do_tox(m->dht);
}
cleanupMessenger(m);
return 0;
}
/* parent */
if (atexit(cleanup) != 0) {
fputs("friends_test: atexit() failed!\nFailing build...\n", stderr);
kill(child_pid, SIGKILL);
return -1;
}
m = initMessenger();
msync(parent_id, crypto_box_PUBLICKEYBYTES, MS_SYNC);
m_callback_statusmessage(m, parent_confirm_status, NULL);
m_callback_friendmessage(m, parent_confirm_message, NULL);
/* hacky way to give the child time to set up */
c_sleep(50);
Messenger_save(m, parent_id);
if (parent_friend_request(m->dht) == -1)
return -1;
if (parent_wait_for_message(m->dht) == -1)
return -1;
wait(NULL);
fputs("friends_test: Build passed!\n", stdout);
return 0;
}
================================================
FILE: auto_tests/helpers.h
================================================
#ifndef TOXCORE_TEST_HELPERS_H
#define TOXCORE_TEST_HELPERS_H
#include
#define DEFTESTCASE(NAME) \
TCase *tc_##NAME = tcase_create(#NAME); \
tcase_add_test(tc_##NAME, test_##NAME); \
suite_add_tcase(s, tc_##NAME);
#define DEFTESTCASE_SLOW(NAME, TIMEOUT) \
DEFTESTCASE(NAME) \
tcase_set_timeout(tc_##NAME, TIMEOUT);
#endif // TOXCORE_TEST_HELPERS_H
================================================
FILE: auto_tests/messenger_test.c
================================================
/* unit tests for /core/Messenger.c
* Design:
* Just call every non-static function in Messenger.c, checking that
* they return as they should with check calls. "Bad" calls of the type
* function(bad_data, good_length) are _not_ checked for, this type
* of call is the fault of the client code.
*
* Note:
* None of the functions here test things that rely on the network, i.e.
* checking that status changes are received, messages can be sent, etc.
* All of that is done in a separate test, with two local clients running. */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "../testing/misc_tools.c" // hex_string_to_bin
#include "../toxcore/Messenger.h"
#include
#include
#include
#include
#include "helpers.h"
#define REALLY_BIG_NUMBER ((1) << (sizeof(uint16_t) * 7))
#define STRINGS_EQUAL(X, Y) (strcmp(X, Y) == 0)
char *friend_id_str = "e4b3d5030bc99494605aecc33ceec8875640c1d74aa32790e821b17e98771c4a00000000f1db";
/* in case we need more than one ID for a test */
char *good_id_a_str = "DB9B569D14850ED8364C3744CAC2C8FF78985D213E980C7C508D0E91E8E45441";
char *good_id_b_str = "d3f14b6d384d8f5f2a66cff637e69f28f539c5de61bc29744785291fa4ef4d64";
char *bad_id_str = "9B569D14ff637e69f2";
unsigned char *friend_id = NULL;
unsigned char *good_id_a = NULL;
unsigned char *good_id_b = NULL;
unsigned char *bad_id = NULL;
int friend_id_num = 0;
Messenger *m;
START_TEST(test_m_sendmesage)
{
char *message = "h-hi :3";
int good_len = strlen(message);
int bad_len = MAX_CRYPTO_PACKET_SIZE;
ck_assert(m_send_message_generic(m, -1, MESSAGE_NORMAL, (uint8_t *)message, good_len, 0) == -1);
ck_assert(m_send_message_generic(m, REALLY_BIG_NUMBER, MESSAGE_NORMAL, (uint8_t *)message, good_len, 0) == -1);
ck_assert(m_send_message_generic(m, 17, MESSAGE_NORMAL, (uint8_t *)message, good_len, 0) == -1);
ck_assert(m_send_message_generic(m, friend_id_num, MESSAGE_NORMAL, (uint8_t *)message, bad_len, 0) == -2);
}
END_TEST
START_TEST(test_m_get_userstatus_size)
{
int rc = 0;
ck_assert_msg((m_get_statusmessage_size(m, -1) == -1),
"m_get_statusmessage_size did NOT catch an argument of -1");
ck_assert_msg((m_get_statusmessage_size(m, REALLY_BIG_NUMBER) == -1),
"m_get_statusmessage_size did NOT catch the following argument: %d\n",
REALLY_BIG_NUMBER);
rc = m_get_statusmessage_size(m, friend_id_num);
/* this WILL error if the original m_addfriend_norequest() failed */
ck_assert_msg((rc >= 0 && rc <= MAX_STATUSMESSAGE_LENGTH),
"m_get_statusmessage_size is returning out of range values! (%i)\n"
"(this can be caused by the error of m_addfriend_norequest"
" in the beginning of the suite)\n", rc);
}
END_TEST
START_TEST(test_m_set_userstatus)
{
char *status = "online!";
uint16_t good_length = strlen(status);
uint16_t bad_length = REALLY_BIG_NUMBER;
ck_assert_msg((m_set_statusmessage(m, (uint8_t *)status, bad_length) == -1),
"m_set_userstatus did NOT catch the following length: %d\n",
REALLY_BIG_NUMBER);
ck_assert_msg((m_set_statusmessage(m, (uint8_t *)status, good_length) == 0),
"m_set_userstatus did NOT return 0 on the following length: %d\n"
"MAX_STATUSMESSAGE_LENGTH: %d\n", good_length, MAX_STATUSMESSAGE_LENGTH);
}
END_TEST
START_TEST(test_m_get_friend_connectionstatus)
{
ck_assert_msg((m_get_friend_connectionstatus(m, -1) == -1),
"m_get_friend_connectionstatus did NOT catch an argument of -1.\n");
ck_assert_msg((m_get_friend_connectionstatus(m, REALLY_BIG_NUMBER) == -1),
"m_get_friend_connectionstatus did NOT catch an argument of %d.\n",
REALLY_BIG_NUMBER);
}
END_TEST
START_TEST(test_m_friend_exists)
{
ck_assert_msg((m_friend_exists(m, -1) == 0),
"m_friend_exists did NOT catch an argument of -1.\n");
ck_assert_msg((m_friend_exists(m, REALLY_BIG_NUMBER) == 0),
"m_friend_exists did NOT catch an argument of %d.\n",
REALLY_BIG_NUMBER);
}
END_TEST
START_TEST(test_m_delfriend)
{
ck_assert_msg((m_delfriend(m, -1) == -1),
"m_delfriend did NOT catch an argument of -1\n");
ck_assert_msg((m_delfriend(m, REALLY_BIG_NUMBER) == -1),
"m_delfriend did NOT catch the following number: %d\n",
REALLY_BIG_NUMBER);
}
END_TEST
/*
START_TEST(test_m_addfriend)
{
char *good_data = "test";
char *bad_data = "";
int good_len = strlen(good_data);
int bad_len = strlen(bad_data);
int really_bad_len = (MAX_CRYPTO_PACKET_SIZE - crypto_box_PUBLICKEYBYTES
- crypto_box_NONCEBYTES - crypto_box_BOXZEROBYTES
+ crypto_box_ZEROBYTES + 100); */
/* TODO: Update this properly to latest master
if(m_addfriend(m, (uint8_t *)friend_id, (uint8_t *)good_data, really_bad_len) != FAERR_TOOLONG)
ck_abort_msg("m_addfriend did NOT catch the following length: %d\n", really_bad_len);
*/
/* this will return an error if the original m_addfriend_norequest() failed */
/* if(m_addfriend(m, (uint8_t *)friend_id, (uint8_t *)good_data, good_len) != FAERR_ALREADYSENT)
ck_abort_msg("m_addfriend did NOT catch adding a friend we already have.\n"
"(this can be caused by the error of m_addfriend_norequest in"
" the beginning of the suite)\n");
if(m_addfriend(m, (uint8_t *)good_id_b, (uint8_t *)bad_data, bad_len) != FAERR_NOMESSAGE)
ck_abort_msg("m_addfriend did NOT catch the following length: %d\n", bad_len);
*/
/* this should REALLY return an error */
/*
* TODO: validate client_id in m_addfriend?
if(m_addfriend((uint8_t *)bad_id, (uint8_t *)good_data, good_len) >= 0)
ck_abort_msg("The following ID passed through "
"m_addfriend without an error:\n'%s'\n", bad_id_str);
}
END_TEST */
START_TEST(test_setname)
{
char *good_name = "consensualCorn";
int good_length = strlen(good_name);
int bad_length = REALLY_BIG_NUMBER;
ck_assert_msg((setname(m, (uint8_t *)good_name, bad_length) == -1),
"setname() did NOT error on %d as a length argument!\n", bad_length);
ck_assert_msg((setname(m, (uint8_t *)good_name, good_length) == 0),
"setname() did NOT return 0 on good arguments!\n");
}
END_TEST
START_TEST(test_getself_name)
{
char *nickname = "testGallop";
int len = strlen(nickname);
char nick_check[len];
setname(m, (uint8_t *)nickname, len);
getself_name(m, (uint8_t *)nick_check);
ck_assert_msg((memcmp(nickname, nick_check, len) == 0),
"getself_name failed to return the known name!\n"
"known name: %s\nreturned: %s\n", nickname, nick_check);
}
END_TEST
/* this test is excluded for now, due to lack of a way
* to set a friend's status for now.
* ideas:
* if we have access to the friends list, we could
* just add a status manually ourselves. */
/*
START_TEST(test_m_copy_userstatus)
{
assert(m_copy_userstatus(-1, buf, MAX_USERSTATUS_LENGTH) == -1);
assert(m_copy_userstatus(REALLY_BIG_NUMBER, buf, MAX_USERSTATUS_LENGTH) == -1);
m_copy_userstatus(friend_id_num, buf, MAX_USERSTATUS_LENGTH + 6);
assert(STRINGS_EQUAL(name_buf, friend_id_status));
}
END_TEST
*/
START_TEST(test_getname)
{
uint8_t name_buf[MAX_NAME_LENGTH];
uint8_t test_name[] = {'f', 'o', 'o'};
ck_assert(getname(m, -1, name_buf) == -1);
ck_assert(getname(m, REALLY_BIG_NUMBER, name_buf) == -1);
memcpy(m->friendlist[0].name, &test_name[0], 3);
m->friendlist[0].name_length = 4;
ck_assert(getname(m, 0, &name_buf[0]) == 4);
ck_assert(strcmp((char *)&name_buf[0], "foo") == 0);
}
END_TEST
START_TEST(test_dht_state_saveloadsave)
{
/* validate that:
* a) saving stays within the confined space
* b) a save()d state can be load()ed back successfully
* c) a second save() is of equal size
* d) the second save() is of equal content */
size_t i, extra = 64;
size_t size = DHT_size(m->dht);
uint8_t buffer[size + 2 * extra];
memset(buffer, 0xCD, extra);
memset(buffer + extra + size, 0xCD, extra);
DHT_save(m->dht, buffer + extra);
for (i = 0; i < extra; i++) {
ck_assert_msg(buffer[i] == 0xCD, "Buffer underwritten from DHT_save() @%u", i);
ck_assert_msg(buffer[extra + size + i] == 0xCD, "Buffer overwritten from DHT_save() @%u", i);
}
int res = DHT_load(m->dht, buffer + extra, size);
if (res == -1)
ck_assert_msg(res == 0, "Failed to load back stored buffer: res == -1");
else {
char msg[128];
size_t offset = res >> 4;
uint8_t *ptr = buffer + extra + offset;
sprintf(msg, "Failed to load back stored buffer: 0x%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx @%zu/%zu, code %d",
ptr[-2], ptr[-1], ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], offset, size, res & 0x0F);
ck_assert_msg(res == 0, msg);
}
size_t size2 = DHT_size(m->dht);
ck_assert_msg(size == size2, "Messenger \"grew\" in size from a store/load cycle: %u -> %u", size, size2);
uint8_t buffer2[size2];
DHT_save(m->dht, buffer2);
ck_assert_msg(!memcmp(buffer + extra, buffer2, size), "DHT state changed by store/load/store cycle");
}
END_TEST
START_TEST(test_messenger_state_saveloadsave)
{
/* validate that:
* a) saving stays within the confined space
* b) a save()d state can be load()ed back successfully
* c) a second save() is of equal size
* d) the second save() is of equal content */
size_t i, extra = 64;
size_t size = messenger_size(m);
uint8_t buffer[size + 2 * extra];
memset(buffer, 0xCD, extra);
memset(buffer + extra + size, 0xCD, extra);
messenger_save(m, buffer + extra);
for (i = 0; i < extra; i++) {
ck_assert_msg(buffer[i] == 0xCD, "Buffer underwritten from messenger_save() @%u", i);
ck_assert_msg(buffer[extra + size + i] == 0xCD, "Buffer overwritten from messenger_save() @%u", i);
}
int res = messenger_load(m, buffer + extra, size);
if (res == -1)
ck_assert_msg(res == 0, "Failed to load back stored buffer: res == -1");
else {
char msg[128];
size_t offset = res >> 4;
uint8_t *ptr = buffer + extra + offset;
sprintf(msg, "Failed to load back stored buffer: 0x%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx @%zu/%zu, code %d",
ptr[-2], ptr[-1], ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], offset, size, res & 0x0F);
ck_assert_msg(res == 0, msg);
}
size_t size2 = messenger_size(m);
ck_assert_msg(size == size2, "Messenger \"grew\" in size from a store/load cycle: %u -> %u", size, size2);
uint8_t buffer2[size2];
messenger_save(m, buffer2);
ck_assert_msg(!memcmp(buffer + extra, buffer2, size), "Messenger state changed by store/load/store cycle");
}
END_TEST
Suite *messenger_suite(void)
{
Suite *s = suite_create("Messenger");
DEFTESTCASE(dht_state_saveloadsave);
DEFTESTCASE(messenger_state_saveloadsave);
DEFTESTCASE(getself_name);
DEFTESTCASE(m_get_userstatus_size);
DEFTESTCASE(m_set_userstatus);
/* DEFTESTCASE(m_addfriend); */
DEFTESTCASE(m_friend_exists);
DEFTESTCASE(m_get_friend_connectionstatus);
DEFTESTCASE(m_delfriend);
DEFTESTCASE(setname);
DEFTESTCASE(getname);
DEFTESTCASE(m_sendmesage);
return s;
}
int main(int argc, char *argv[])
{
Suite *messenger = messenger_suite();
SRunner *test_runner = srunner_create(messenger);
int number_failed = 0;
friend_id = hex_string_to_bin(friend_id_str);
good_id_a = hex_string_to_bin(good_id_a_str);
good_id_b = hex_string_to_bin(good_id_b_str);
bad_id = hex_string_to_bin(bad_id_str);
/* IPv6 status from global define */
Messenger_Options options = {0};
options.ipv6enabled = TOX_ENABLE_IPV6_DEFAULT;
m = new_messenger(&options, 0);
/* setup a default friend and friendnum */
if (m_addfriend_norequest(m, (uint8_t *)friend_id) < 0)
fputs("m_addfriend_norequest() failed on a valid ID!\n"
"this was CRITICAL to the test, and the build WILL fail.\n"
"the tests will continue now...\n\n", stderr);
if ((friend_id_num = getfriend_id(m, (uint8_t *)friend_id)) < 0)
fputs("getfriend_id() failed on a valid ID!\n"
"this was CRITICAL to the test, and the build WILL fail.\n"
"the tests will continue now...\n\n", stderr);
srunner_run_all(test_runner, CK_NORMAL);
number_failed = srunner_ntests_failed(test_runner);
srunner_free(test_runner);
free(friend_id);
free(good_id_a);
free(good_id_b);
free(bad_id);
kill_messenger(m);
return number_failed;
}
================================================
FILE: auto_tests/network_test.c
================================================
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include
#include
#include
#include
#include
#include "../toxcore/network.h"
#include "helpers.h"
START_TEST(test_addr_resolv_localhost)
{
#ifdef __CYGWIN__
/* force initialization of network stack
* normally this should happen automatically
* cygwin doesn't do it for every network related function though
* e.g. not for getaddrinfo... */
socket(0, 0, 0);
errno = 0;
#endif
const char localhost[] = "localhost";
int localhost_split = 0;
IP ip;
ip_init(&ip, 0); // ipv6enabled = 0
int res = addr_resolve(localhost, &ip, NULL);
ck_assert_msg(res > 0, "Resolver failed: %u, %s (%x, %x)", errno, strerror(errno));
if (res > 0) {
ck_assert_msg(ip.family == AF_INET, "Expected family AF_INET, got %u.", ip.family);
ck_assert_msg(ip.ip4.uint32 == htonl(0x7F000001), "Expected 127.0.0.1, got %s.", inet_ntoa(ip.ip4.in_addr));
}
ip_init(&ip, 1); // ipv6enabled = 1
res = addr_resolve(localhost, &ip, NULL);
if (!(res & TOX_ADDR_RESOLVE_INET6)) {
res = addr_resolve("ip6-localhost", &ip, NULL);
localhost_split = 1;
}
ck_assert_msg(res > 0, "Resolver failed: %u, %s (%x, %x)", errno, strerror(errno));
if (res > 0) {
ck_assert_msg(ip.family == AF_INET6, "Expected family AF_INET6 (%u), got %u.", AF_INET6, ip.family);
ck_assert_msg(!memcmp(&ip.ip6, &in6addr_loopback, sizeof(IP6)), "Expected ::1, got %s.", ip_ntoa(&ip));
}
if (!localhost_split) {
ip_init(&ip, 1); // ipv6enabled = 1
ip.family = AF_UNSPEC;
IP extra;
ip_reset(&extra);
res = addr_resolve(localhost, &ip, &extra);
ck_assert_msg(res > 0, "Resolver failed: %u, %s (%x, %x)", errno, strerror(errno));
if (res > 0) {
ck_assert_msg(ip.family == AF_INET6, "Expected family AF_INET6 (%u), got %u.", AF_INET6, ip.family);
ck_assert_msg(!memcmp(&ip.ip6, &in6addr_loopback, sizeof(IP6)), "Expected ::1, got %s.", ip_ntoa(&ip));
ck_assert_msg(extra.family == AF_INET, "Expected family AF_INET (%u), got %u.", AF_INET, extra.family);
ck_assert_msg(extra.ip4.uint32 == htonl(0x7F000001), "Expected 127.0.0.1, got %s.", inet_ntoa(extra.ip4.in_addr));
}
} else {
printf("Localhost seems to be split in two.\n");
}
}
END_TEST
START_TEST(test_ip_equal)
{
int res;
IP ip1, ip2;
ip_reset(&ip1);
ip_reset(&ip2);
res = ip_equal(NULL, NULL);
ck_assert_msg(res == 0, "ip_equal(NULL, NULL): expected result 0, got %u.", res);
res = ip_equal(&ip1, NULL);
ck_assert_msg(res == 0, "ip_equal(PTR, NULL): expected result 0, got %u.", res);
res = ip_equal(NULL, &ip1);
ck_assert_msg(res == 0, "ip_equal(NULL, PTR): expected result 0, got %u.", res);
ip1.family = AF_INET;
ip1.ip4.uint32 = htonl(0x7F000001);
res = ip_equal(&ip1, &ip2);
ck_assert_msg(res == 0, "ip_equal( {AF_INET, 127.0.0.1}, {AF_UNSPEC, 0} ): expected result 0, got %u.", res);
ip2.family = AF_INET;
ip2.ip4.uint32 = htonl(0x7F000001);
res = ip_equal(&ip1, &ip2);
ck_assert_msg(res != 0, "ip_equal( {AF_INET, 127.0.0.1}, {AF_INET, 127.0.0.1} ): expected result != 0, got 0.");
ip2.ip4.uint32 = htonl(0x7F000002);
res = ip_equal(&ip1, &ip2);
ck_assert_msg(res == 0, "ip_equal( {AF_INET, 127.0.0.1}, {AF_INET, 127.0.0.2} ): expected result 0, got %u.", res);
ip2.family = AF_INET6;
ip2.ip6.uint32[0] = 0;
ip2.ip6.uint32[1] = 0;
ip2.ip6.uint32[2] = htonl(0xFFFF);
ip2.ip6.uint32[3] = htonl(0x7F000001);
ck_assert_msg(IN6_IS_ADDR_V4MAPPED(&ip2.ip6.in6_addr) != 0,
"IN6_IS_ADDR_V4MAPPED(::ffff:127.0.0.1): expected != 0, got 0.");
res = ip_equal(&ip1, &ip2);
ck_assert_msg(res != 0, "ip_equal( {AF_INET, 127.0.0.1}, {AF_INET6, ::ffff:127.0.0.1} ): expected result != 0, got 0.");
memcpy(&ip2.ip6, &in6addr_loopback, sizeof(IP6));
res = ip_equal(&ip1, &ip2);
ck_assert_msg(res == 0, "ip_equal( {AF_INET, 127.0.0.1}, {AF_INET6, ::1} ): expected result 0, got %u.", res);
memcpy(&ip1, &ip2, sizeof(IP));
res = ip_equal(&ip1, &ip2);
ck_assert_msg(res != 0, "ip_equal( {AF_INET6, ::1}, {AF_INET6, ::1} ): expected result != 0, got 0.");
ip2.ip6.uint8[15]++;
res = ip_equal(&ip1, &ip2);
ck_assert_msg(res == 0, "ip_equal( {AF_INET6, ::1}, {AF_INET6, ::2} ): expected result 0, got %res.", res);
}
END_TEST
Suite *network_suite(void)
{
Suite *s = suite_create("Network");
DEFTESTCASE(addr_resolv_localhost);
DEFTESTCASE(ip_equal);
return s;
}
int main()
{
srand((unsigned int) time(NULL));
Suite *network = network_suite();
SRunner *test_runner = srunner_create(network);
int number_failed = 0;
srunner_run_all(test_runner, CK_NORMAL);
number_failed = srunner_ntests_failed(test_runner);
srunner_free(test_runner);
return number_failed;
}
================================================
FILE: auto_tests/onion_test.c
================================================
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include
#include
#include
#include
#include
#include "../toxcore/onion.h"
#include "../toxcore/onion_announce.h"
#include "../toxcore/onion_client.h"
#include "../toxcore/util.h"
#include "helpers.h"
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
#define c_sleep(x) Sleep(1*x)
#else
#include
#define c_sleep(x) usleep(1000*x)
#endif
void do_onion(Onion *onion)
{
networking_poll(onion->net);
do_DHT(onion->dht);
}
static int handled_test_1;
static int handle_test_1(void *object, IP_Port source, const uint8_t *packet, uint16_t length)
{
Onion *onion = object;
if (memcmp(packet, "\x83 Install Gentoo", sizeof("\x83 Install Gentoo")) != 0)
return 1;
if (send_onion_response(onion->net, source, (uint8_t *)"\x84 install gentoo", sizeof("\x84 install gentoo"),
packet + sizeof("\x83 Install Gentoo")) == -1)
return 1;
handled_test_1 = 1;
return 0;
}
static int handled_test_2;
static int handle_test_2(void *object, IP_Port source, const uint8_t *packet, uint16_t length)
{
if (length != sizeof("\x84 install Gentoo"))
return 1;
if (memcmp(packet, (uint8_t *)"\x84 install gentoo", sizeof("\x84 install gentoo")) != 0)
return 1;
handled_test_2 = 1;
return 0;
}
/*
void print_client_id(uint8_t *client_id, uint32_t length)
{
uint32_t j;
for (j = 0; j < length; j++) {
printf("%02hhX", client_id[j]);
}
printf("\n");
}
*/
uint8_t sb_data[ONION_ANNOUNCE_SENDBACK_DATA_LENGTH];
static int handled_test_3;
uint8_t test_3_pub_key[crypto_box_PUBLICKEYBYTES];
uint8_t test_3_ping_id[crypto_hash_sha256_BYTES];
static int handle_test_3(void *object, IP_Port source, const uint8_t *packet, uint16_t length)
{
Onion *onion = object;
if (length != (1 + crypto_box_NONCEBYTES + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + 1 + crypto_hash_sha256_BYTES +
crypto_box_MACBYTES))
return 1;
uint8_t plain[1 + crypto_hash_sha256_BYTES];
//print_client_id(packet, length);
int len = decrypt_data(test_3_pub_key, onion->dht->self_secret_key, packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH,
packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + crypto_box_NONCEBYTES,
1 + crypto_hash_sha256_BYTES + crypto_box_MACBYTES, plain);
if (len == -1)
return 1;
if (memcmp(packet + 1, sb_data, ONION_ANNOUNCE_SENDBACK_DATA_LENGTH) != 0)
return 1;
memcpy(test_3_ping_id, plain + 1, crypto_hash_sha256_BYTES);
//print_client_id(test_3_ping_id, sizeof(test_3_ping_id));
handled_test_3 = 1;
return 0;
}
uint8_t nonce[crypto_box_NONCEBYTES];
static int handled_test_4;
static int handle_test_4(void *object, IP_Port source, const uint8_t *packet, uint16_t length)
{
Onion *onion = object;
if (length != (1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + sizeof("Install gentoo") + crypto_box_MACBYTES))
return 1;
uint8_t plain[sizeof("Install gentoo")] = {0};
if (memcmp(nonce, packet + 1, crypto_box_NONCEBYTES) != 0)
return 1;
int len = decrypt_data(packet + 1 + crypto_box_NONCEBYTES, onion->dht->self_secret_key, packet + 1,
packet + 1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES, sizeof("Install gentoo") + crypto_box_MACBYTES, plain);
if (len == -1)
return 1;
if (memcmp(plain, "Install gentoo", sizeof("Install gentoo")) != 0)
return 1;
handled_test_4 = 1;
return 0;
}
START_TEST(test_basic)
{
IP ip;
ip_init(&ip, 1);
ip.ip6.uint8[15] = 1;
Onion *onion1 = new_onion(new_DHT(new_networking(ip, 34567)));
Onion *onion2 = new_onion(new_DHT(new_networking(ip, 34568)));
ck_assert_msg((onion1 != NULL) && (onion2 != NULL), "Onion failed initializing.");
networking_registerhandler(onion2->net, NET_PACKET_ANNOUNCE_REQUEST, &handle_test_1, onion2);
IP_Port on1 = {ip, onion1->net->port};
Node_format n1;
memcpy(n1.public_key, onion1->dht->self_public_key, crypto_box_PUBLICKEYBYTES);
n1.ip_port = on1;
IP_Port on2 = {ip, onion2->net->port};
Node_format n2;
memcpy(n2.public_key, onion2->dht->self_public_key, crypto_box_PUBLICKEYBYTES);
n2.ip_port = on2;
Node_format nodes[4];
nodes[0] = n1;
nodes[1] = n2;
nodes[2] = n1;
nodes[3] = n2;
Onion_Path path;
create_onion_path(onion1->dht, &path, nodes);
int ret = send_onion_packet(onion1->net, &path, nodes[3].ip_port, (uint8_t *)"\x83 Install Gentoo",
sizeof("\x83 Install Gentoo"));
ck_assert_msg(ret == 0, "Failed to create/send onion packet.");
handled_test_1 = 0;
while (handled_test_1 == 0) {
do_onion(onion1);
do_onion(onion2);
}
networking_registerhandler(onion1->net, NET_PACKET_ANNOUNCE_RESPONSE, &handle_test_2, onion1);
handled_test_2 = 0;
while (handled_test_2 == 0) {
do_onion(onion1);
do_onion(onion2);
}
Onion_Announce *onion1_a = new_onion_announce(onion1->dht);
Onion_Announce *onion2_a = new_onion_announce(onion2->dht);
networking_registerhandler(onion1->net, NET_PACKET_ANNOUNCE_RESPONSE, &handle_test_3, onion1);
ck_assert_msg((onion1_a != NULL) && (onion2_a != NULL), "Onion_Announce failed initializing.");
uint8_t zeroes[64] = {0};
randombytes(sb_data, sizeof(sb_data));
uint64_t s;
memcpy(&s, sb_data, sizeof(uint64_t));
memcpy(test_3_pub_key, nodes[3].public_key, crypto_box_PUBLICKEYBYTES);
ret = send_announce_request(onion1->net, &path, nodes[3], onion1->dht->self_public_key,
onion1->dht->self_secret_key,
zeroes, onion1->dht->self_public_key, onion1->dht->self_public_key, s);
ck_assert_msg(ret == 0, "Failed to create/send onion announce_request packet.");
handled_test_3 = 0;
while (handled_test_3 == 0) {
do_onion(onion1);
do_onion(onion2);
c_sleep(50);
}
randombytes(sb_data, sizeof(sb_data));
memcpy(&s, sb_data, sizeof(uint64_t));
memcpy(onion2_a->entries[1].public_key, onion2->dht->self_public_key, crypto_box_PUBLICKEYBYTES);
onion2_a->entries[1].time = unix_time();
networking_registerhandler(onion1->net, NET_PACKET_ONION_DATA_RESPONSE, &handle_test_4, onion1);
send_announce_request(onion1->net, &path, nodes[3], onion1->dht->self_public_key, onion1->dht->self_secret_key,
test_3_ping_id, onion1->dht->self_public_key, onion1->dht->self_public_key, s);
while (memcmp(onion2_a->entries[ONION_ANNOUNCE_MAX_ENTRIES - 2].public_key, onion1->dht->self_public_key,
crypto_box_PUBLICKEYBYTES) != 0) {
do_onion(onion1);
do_onion(onion2);
c_sleep(50);
}
c_sleep(1000);
Onion *onion3 = new_onion(new_DHT(new_networking(ip, 34569)));
ck_assert_msg((onion3 != NULL), "Onion failed initializing.");
new_nonce(nonce);
ret = send_data_request(onion3->net, &path, nodes[3].ip_port, onion1->dht->self_public_key,
onion1->dht->self_public_key,
nonce, (uint8_t *)"Install gentoo", sizeof("Install gentoo"));
ck_assert_msg(ret == 0, "Failed to create/send onion data_request packet.");
handled_test_4 = 0;
while (handled_test_4 == 0) {
do_onion(onion1);
do_onion(onion2);
c_sleep(50);
}
kill_onion_announce(onion1_a);
kill_onion_announce(onion2_a);
{
Onion *onion = onion1;
Networking_Core *net = onion->dht->net;
DHT *dht = onion->dht;
kill_onion(onion);
kill_DHT(dht);
kill_networking(net);
}
{
Onion *onion = onion2;
Networking_Core *net = onion->dht->net;
DHT *dht = onion->dht;
kill_onion(onion);
kill_DHT(dht);
kill_networking(net);
}
{
Onion *onion = onion3;
Networking_Core *net = onion->dht->net;
DHT *dht = onion->dht;
kill_onion(onion);
kill_DHT(dht);
kill_networking(net);
}
}
END_TEST
typedef struct {
Onion *onion;
Onion_Announce *onion_a;
Onion_Client *onion_c;
} Onions;
Onions *new_onions(uint16_t port)
{
IP ip;
ip_init(&ip, 1);
ip.ip6.uint8[15] = 1;
Onions *on = malloc(sizeof(Onions));
DHT *dht = new_DHT(new_networking(ip, port));
on->onion = new_onion(dht);
on->onion_a = new_onion_announce(dht);
TCP_Proxy_Info inf = {0};
on->onion_c = new_onion_client(new_net_crypto(dht, &inf));
if (on->onion && on->onion_a && on->onion_c)
return on;
return NULL;
}
void do_onions(Onions *on)
{
networking_poll(on->onion->net);
do_DHT(on->onion->dht);
do_onion_client(on->onion_c);
}
void kill_onions(Onions *on)
{
Networking_Core *net = on->onion->dht->net;
DHT *dht = on->onion->dht;
Net_Crypto *c = on->onion_c->c;
kill_onion_client(on->onion_c);
kill_onion_announce(on->onion_a);
kill_onion(on->onion);
kill_net_crypto(c);
kill_DHT(dht);
kill_networking(net);
free(on);
}
#define NUM_ONIONS 50
#define NUM_FIRST 7
#define NUM_LAST 37
_Bool first_ip, last_ip;
void dht_ip_callback(void *object, int32_t number, IP_Port ip_port)
{
if (NUM_FIRST == number) {
first_ip = 1;
return;
}
if (NUM_LAST == number) {
last_ip = 1;
return;
}
ck_abort_msg("Error.");
}
_Bool first, last;
uint8_t first_dht_pk[crypto_box_PUBLICKEYBYTES];
uint8_t last_dht_pk[crypto_box_PUBLICKEYBYTES];
static void dht_pk_callback(void *object, int32_t number, const uint8_t *dht_public_key)
{
if ((NUM_FIRST == number && !first) || (NUM_LAST == number && !last)) {
Onions *on = object;
uint16_t count = 0;
int ret = DHT_addfriend(on->onion->dht, dht_public_key, &dht_ip_callback, object, number, &count);
ck_assert_msg(ret == 0, "DHT_addfriend() did not return 0");
ck_assert_msg(count == 1, "Count not 1, count is %u", count);
if (NUM_FIRST == number && !first) {
first = 1;
if (memcmp(dht_public_key, last_dht_pk, crypto_box_PUBLICKEYBYTES) != 0) {
ck_abort_msg("Error wrong dht key.");
}
return;
}
if (NUM_LAST == number && !last) {
last = 1;
if (memcmp(dht_public_key, first_dht_pk, crypto_box_PUBLICKEYBYTES) != 0) {
ck_abort_msg("Error wrong dht key.");
}
return;
}
ck_abort_msg("Error.");
}
}
START_TEST(test_announce)
{
uint32_t i, j;
Onions *onions[NUM_ONIONS];
for (i = 0; i < NUM_ONIONS; ++i) {
onions[i] = new_onions(i + 34655);
ck_assert_msg(onions[i] != 0, "Failed to create onions. %u");
}
IP ip;
ip_init(&ip, 1);
ip.ip6.uint8[15] = 1;
for (i = 3; i < NUM_ONIONS; ++i) {
IP_Port ip_port = {ip, onions[i - 1]->onion->net->port};
DHT_bootstrap(onions[i]->onion->dht, ip_port, onions[i - 1]->onion->dht->self_public_key);
IP_Port ip_port1 = {ip, onions[i - 2]->onion->net->port};
DHT_bootstrap(onions[i]->onion->dht, ip_port1, onions[i - 2]->onion->dht->self_public_key);
IP_Port ip_port2 = {ip, onions[i - 3]->onion->net->port};
DHT_bootstrap(onions[i]->onion->dht, ip_port2, onions[i - 3]->onion->dht->self_public_key);
}
uint32_t connected = 0;
while (connected != NUM_ONIONS) {
connected = 0;
for (i = 0; i < NUM_ONIONS; ++i) {
do_onions(onions[i]);
connected += DHT_isconnected(onions[i]->onion->dht);
}
c_sleep(50);
}
printf("connected\n");
for (i = 0; i < 25 * 2; ++i) {
for (j = 0; j < NUM_ONIONS; ++j) {
do_onions(onions[j]);
}
c_sleep(50);
}
memcpy(first_dht_pk, onions[NUM_FIRST]->onion->dht->self_public_key, crypto_box_PUBLICKEYBYTES);
memcpy(last_dht_pk, onions[NUM_LAST]->onion->dht->self_public_key, crypto_box_PUBLICKEYBYTES);
printf("adding friend\n");
int frnum_f = onion_addfriend(onions[NUM_FIRST]->onion_c, onions[NUM_LAST]->onion_c->c->self_public_key);
int frnum = onion_addfriend(onions[NUM_LAST]->onion_c, onions[NUM_FIRST]->onion_c->c->self_public_key);
onion_dht_pk_callback(onions[NUM_FIRST]->onion_c, frnum_f, &dht_pk_callback, onions[NUM_FIRST], NUM_FIRST);
onion_dht_pk_callback(onions[NUM_LAST]->onion_c, frnum, &dht_pk_callback, onions[NUM_LAST], NUM_LAST);
int ok = -1;
IP_Port ip_port;
while (!first || !last) {
for (i = 0; i < NUM_ONIONS; ++i) {
do_onions(onions[i]);
}
c_sleep(50);
}
printf("Waiting for ips\n");
while (!first_ip || !last_ip) {
for (i = 0; i < NUM_ONIONS; ++i) {
do_onions(onions[i]);
}
c_sleep(50);
}
onion_getfriendip(onions[NUM_LAST]->onion_c, frnum, &ip_port);
ck_assert_msg(ip_port.port == onions[NUM_FIRST]->onion->net->port, "Port in returned ip not correct.");
for (i = 0; i < NUM_ONIONS; ++i) {
kill_onions(onions[i]);
}
}
END_TEST
Suite *onion_suite(void)
{
Suite *s = suite_create("Onion");
DEFTESTCASE_SLOW(basic, 5);
DEFTESTCASE_SLOW(announce, 70);
return s;
}
int main(int argc, char *argv[])
{
srand((unsigned int) time(NULL));
Suite *onion = onion_suite();
SRunner *test_runner = srunner_create(onion);
int number_failed = 0;
srunner_run_all(test_runner, CK_NORMAL);
number_failed = srunner_ntests_failed(test_runner);
srunner_free(test_runner);
return number_failed;
}
================================================
FILE: auto_tests/skeleton_test.c
================================================
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include
#include
#include
#include
#include
#include "helpers.h"
/*
#include "../"
*/
START_TEST(test_creativetestnamegoeshere)
{
uint8_t test = 0;
ck_assert_msg(test == 0, "test: expected result 0, got %u.", test);
}
END_TEST
Suite *creativesuitenamegoeshere_suite(void)
{
Suite *s = suite_create("creativesuitedescritptiongoeshere");
DEFTESTCASE(/* remove test_ from test function names */ creativetestnamegoeshere);
return s;
}
int main(int argc, char *argv[])
{
srand((unsigned int) time(NULL));
Suite *creativesuitenamegoeshere = creativesuitenamegoeshere_suite();
SRunner *test_runner = srunner_create(creativesuitenamegoeshere);
int number_failed = 0;
srunner_run_all(test_runner, CK_NORMAL);
number_failed = srunner_ntests_failed(test_runner);
srunner_free(test_runner);
return number_failed;
}
================================================
FILE: auto_tests/tox_test.c
================================================
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include
#include
#include
#include
#include
#include
#include "../toxcore/tox.h"
#include "helpers.h"
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
#define c_sleep(x) Sleep(1*x)
#else
#include
#define c_sleep(x) usleep(1000*x)
#endif
void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata)
{
if (*((uint32_t *)userdata) != 974536)
return;
if (length == 7 && memcmp("Gentoo", data, 7) == 0) {
tox_friend_add_norequest(m, public_key, 0);
}
}
uint32_t messages_received;
void print_message(Tox *m, uint32_t friendnumber, TOX_MESSAGE_TYPE type, const uint8_t *string, size_t length,
void *userdata)
{
if (*((uint32_t *)userdata) != 974536)
return;
if (type != TOX_MESSAGE_TYPE_NORMAL) {
ck_abort_msg("Bad type");
}
uint8_t cmp_msg[TOX_MAX_MESSAGE_LENGTH];
memset(cmp_msg, 'G', sizeof(cmp_msg));
if (length == TOX_MAX_MESSAGE_LENGTH && memcmp(string, cmp_msg, sizeof(cmp_msg)) == 0)
++messages_received;
}
uint32_t name_changes;
void print_nickchange(Tox *m, uint32_t friendnumber, const uint8_t *string, size_t length, void *userdata)
{
if (*((uint32_t *)userdata) != 974536)
return;
if (length == sizeof("Gentoo") && memcmp(string, "Gentoo", sizeof("Gentoo")) == 0)
++name_changes;
}
uint32_t status_m_changes;
void print_status_m_change(Tox *tox, uint32_t friend_number, const uint8_t *message, size_t length, void *user_data)
{
if (*((uint32_t *)user_data) != 974536)
return;
if (length == sizeof("Installing Gentoo") && memcmp(message, "Installing Gentoo", sizeof("Installing Gentoo")) == 0)
++status_m_changes;
}
uint32_t typing_changes;
void print_typingchange(Tox *m, uint32_t friendnumber, bool typing, void *userdata)
{
if (*((uint32_t *)userdata) != 974536)
return;
if (!typing)
typing_changes = 1;
else
typing_changes = 2;
}
uint32_t custom_packet;
void handle_custom_packet(Tox *m, uint32_t friend_num, const uint8_t *data, size_t len, void *object)
{
uint8_t number = *((uint32_t *)object);
if (len != TOX_MAX_CUSTOM_PACKET_SIZE)
return;
uint8_t f_data[len];
memset(f_data, number, len);
if (memcmp(f_data, data, len) == 0) {
++custom_packet;
} else {
ck_abort_msg("Custom packet fail. %u", number);
}
return;
}
uint64_t size_recv;
uint64_t sending_pos;
uint8_t file_cmp_id[TOX_FILE_ID_LENGTH];
uint8_t filenum;
uint32_t file_accepted;
uint64_t file_size;
void tox_file_receive(Tox *tox, uint32_t friend_number, uint32_t file_number, uint32_t kind, uint64_t filesize,
const uint8_t *filename, size_t filename_length, void *userdata)
{
if (*((uint32_t *)userdata) != 974536)
return;
if (kind != TOX_FILE_KIND_DATA) {
ck_abort_msg("Bad kind");
return;
}
if (!(filename_length == sizeof("Gentoo.exe") && memcmp(filename, "Gentoo.exe", sizeof("Gentoo.exe")) == 0)) {
ck_abort_msg("Bad filename");
return;
}
uint8_t file_id[TOX_FILE_ID_LENGTH];
if (!tox_file_get_file_id(tox, friend_number, file_number, file_id, 0)) {
ck_abort_msg("tox_file_get_file_id error");
}
if (memcmp(file_id, file_cmp_id, TOX_FILE_ID_LENGTH) != 0) {
ck_abort_msg("bad file_id");
}
uint8_t empty[TOX_FILE_ID_LENGTH] = {0};
if (memcmp(empty, file_cmp_id, TOX_FILE_ID_LENGTH) == 0) {
ck_abort_msg("empty file_id");
}
file_size = filesize;
if (filesize) {
sending_pos = size_recv = 1337;
TOX_ERR_FILE_SEEK err_s;
if (!tox_file_seek(tox, friend_number, file_number, 1337, &err_s)) {
ck_abort_msg("tox_file_seek error");
}
ck_assert_msg(err_s == TOX_ERR_FILE_SEEK_OK, "tox_file_seek wrong error");
} else {
sending_pos = size_recv = 0;
}
TOX_ERR_FILE_CONTROL error;
if (tox_file_control(tox, friend_number, file_number, TOX_FILE_CONTROL_RESUME, &error)) {
++file_accepted;
} else {
ck_abort_msg("tox_file_control failed. %i", error);
}
TOX_ERR_FILE_SEEK err_s;
if (tox_file_seek(tox, friend_number, file_number, 1234, &err_s)) {
ck_abort_msg("tox_file_seek no error");
}
ck_assert_msg(err_s == TOX_ERR_FILE_SEEK_DENIED, "tox_file_seek wrong error");
}
uint32_t sendf_ok;
void file_print_control(Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control,
void *userdata)
{
if (*((uint32_t *)userdata) != 974536)
return;
/* First send file num is 0.*/
if (file_number == 0 && control == TOX_FILE_CONTROL_RESUME)
sendf_ok = 1;
}
uint64_t max_sending;
_Bool m_send_reached;
uint8_t sending_num;
_Bool file_sending_done;
void tox_file_chunk_request(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, size_t length,
void *user_data)
{
if (*((uint32_t *)user_data) != 974536)
return;
if (!sendf_ok) {
ck_abort_msg("Didn't get resume control");
}
if (sending_pos != position) {
ck_abort_msg("Bad position %llu", position);
return;
}
if (length == 0) {
if (file_sending_done) {
ck_abort_msg("File sending already done.");
}
file_sending_done = 1;
return;
}
if (position + length > max_sending) {
if (m_send_reached) {
ck_abort_msg("Requested done file tranfer.");
}
length = max_sending - position;
m_send_reached = 1;
}
TOX_ERR_FILE_SEND_CHUNK error;
uint8_t f_data[length];
memset(f_data, sending_num, length);
if (tox_file_send_chunk(tox, friend_number, file_number, position, f_data, length, &error)) {
++sending_num;
sending_pos += length;
} else {
ck_abort_msg("Could not send chunk %i", error);
}
if (error != TOX_ERR_FILE_SEND_CHUNK_OK) {
ck_abort_msg("Wrong error code");
}
}
uint8_t num;
_Bool file_recv;
void write_file(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, const uint8_t *data,
size_t length, void *user_data)
{
if (*((uint32_t *)user_data) != 974536)
return;
if (size_recv != position) {
ck_abort_msg("Bad position");
return;
}
if (length == 0) {
file_recv = 1;
return;
}
uint8_t f_data[length];
memset(f_data, num, length);
++num;
if (memcmp(f_data, data, length) == 0) {
size_recv += length;
} else {
ck_abort_msg("FILE_CORRUPTED");
}
}
unsigned int connected_t1;
void tox_connection_status(Tox *tox, TOX_CONNECTION connection_status, void *user_data)
{
if (*((uint32_t *)user_data) != 974536)
return;
if (connected_t1 && !connection_status)
ck_abort_msg("Tox went offline");
ck_assert_msg(connection_status == TOX_CONNECTION_UDP, "wrong status %u", connection_status);
connected_t1 = connection_status;
}
START_TEST(test_one)
{
{
TOX_ERR_OPTIONS_NEW o_err;
struct Tox_Options *o1 = tox_options_new(&o_err);
struct Tox_Options o2;
tox_options_default(&o2);
ck_assert_msg(o_err == TOX_ERR_OPTIONS_NEW_OK, "tox_options_new wrong error");
ck_assert_msg(memcmp(o1, &o2, sizeof(struct Tox_Options)) == 0, "tox_options_new error");
tox_options_free(o1);
}
Tox *tox1 = tox_new(0, 0);
Tox *tox2 = tox_new(0, 0);
{
TOX_ERR_GET_PORT error;
ck_assert_msg(tox_self_get_udp_port(tox1, &error) == 33445, "First Tox instance did not bind to udp port 33445.\n");
ck_assert_msg(error == TOX_ERR_GET_PORT_OK, "wrong error");
}
uint8_t address[TOX_ADDRESS_SIZE];
tox_self_get_address(tox1, address);
TOX_ERR_FRIEND_ADD error;
uint32_t ret = tox_friend_add(tox1, address, (uint8_t *)"m", 1, &error);
ck_assert_msg(ret == UINT32_MAX && error == TOX_ERR_FRIEND_ADD_OWN_KEY, "Adding own address worked.");
tox_self_get_address(tox2, address);
uint8_t message[TOX_MAX_FRIEND_REQUEST_LENGTH + 1];
ret = tox_friend_add(tox1, address, NULL, 0, &error);
ck_assert_msg(ret == UINT32_MAX && error == TOX_ERR_FRIEND_ADD_NULL, "Sending request with no message worked.");
ret = tox_friend_add(tox1, address, message, 0, &error);
ck_assert_msg(ret == UINT32_MAX && error == TOX_ERR_FRIEND_ADD_NO_MESSAGE, "Sending request with no message worked.");
ret = tox_friend_add(tox1, address, message, sizeof(message), &error);
ck_assert_msg(ret == UINT32_MAX && error == TOX_ERR_FRIEND_ADD_TOO_LONG,
"TOX_MAX_FRIEND_REQUEST_LENGTH is too big.");
address[0]++;
ret = tox_friend_add(tox1, address, (uint8_t *)"m", 1, &error);
ck_assert_msg(ret == UINT32_MAX && error == TOX_ERR_FRIEND_ADD_BAD_CHECKSUM,
"Adding address with bad checksum worked.");
tox_self_get_address(tox2, address);
ret = tox_friend_add(tox1, address, message, TOX_MAX_FRIEND_REQUEST_LENGTH, &error);
ck_assert_msg(ret == 0 && error == TOX_ERR_FRIEND_ADD_OK, "Failed to add friend.");
ret = tox_friend_add(tox1, address, message, TOX_MAX_FRIEND_REQUEST_LENGTH, &error);
ck_assert_msg(ret == UINT32_MAX && error == TOX_ERR_FRIEND_ADD_ALREADY_SENT, "Adding friend twice worked.");
uint8_t name[TOX_MAX_NAME_LENGTH];
int i;
for (i = 0; i < TOX_MAX_NAME_LENGTH; ++i) {
name[i] = rand();
}
tox_self_set_name(tox1, name, sizeof(name), 0);
ck_assert_msg(tox_self_get_name_size(tox1) == sizeof(name), "Can't set name of TOX_MAX_NAME_LENGTH");
tox_self_get_address(tox1, address);
size_t save_size = tox_get_savedata_size(tox1);
uint8_t data[save_size];
tox_get_savedata(tox1, data);
tox_kill(tox2);
TOX_ERR_NEW err_n;
struct Tox_Options options;
tox_options_default(&options);
options.savedata_type = TOX_SAVEDATA_TYPE_TOX_SAVE;
options.savedata_data = data;
options.savedata_length = save_size;
tox2 = tox_new(&options, &err_n);
ck_assert_msg(err_n == TOX_ERR_NEW_OK, "Load failed");
ck_assert_msg(tox_self_get_name_size(tox2) == sizeof name, "Wrong name size.");
uint8_t address2[TOX_ADDRESS_SIZE];
tox_self_get_address(tox2, address2);
ck_assert_msg(memcmp(address2, address, TOX_ADDRESS_SIZE) == 0, "Wrong address.");
uint8_t new_name[TOX_MAX_NAME_LENGTH] = { 0 };
tox_self_get_name(tox2, new_name);
ck_assert_msg(memcmp(name, new_name, TOX_MAX_NAME_LENGTH) == 0, "Wrong name");
uint8_t sk[TOX_SECRET_KEY_SIZE];
tox_self_get_secret_key(tox2, sk);
tox_kill(tox2);
tox_options_default(&options);
options.savedata_type = TOX_SAVEDATA_TYPE_SECRET_KEY;
options.savedata_data = sk;
options.savedata_length = sizeof(sk);
tox2 = tox_new(&options, &err_n);
ck_assert_msg(err_n == TOX_ERR_NEW_OK, "Load failed");
uint8_t address3[TOX_ADDRESS_SIZE];
tox_self_get_address(tox2, address3);
ck_assert_msg(memcmp(address3, address, TOX_PUBLIC_KEY_SIZE) == 0, "Wrong public key.");
uint8_t pk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_public_key(tox2, pk);
ck_assert_msg(memcmp(pk, address, TOX_PUBLIC_KEY_SIZE) == 0, "Wrong public key.");
tox_kill(tox1);
tox_kill(tox2);
}
END_TEST
START_TEST(test_few_clients)
{
long long unsigned int con_time, cur_time = time(NULL);
TOX_ERR_NEW t_n_error;
Tox *tox1 = tox_new(0, &t_n_error);
ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error");
Tox *tox2 = tox_new(0, &t_n_error);
ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error");
Tox *tox3 = tox_new(0, &t_n_error);
ck_assert_msg(t_n_error == TOX_ERR_NEW_OK, "wrong error");
ck_assert_msg(tox1 || tox2 || tox3, "Failed to create 3 tox instances");
{
TOX_ERR_GET_PORT error;
ck_assert_msg(tox_self_get_udp_port(tox1, &error) == 33445, "First Tox instance did not bind to udp port 33445.\n");
ck_assert_msg(error == TOX_ERR_GET_PORT_OK, "wrong error");
}
{
TOX_ERR_GET_PORT error;
ck_assert_msg(tox_self_get_udp_port(tox2, &error) == 33446, "Second Tox instance did not bind to udp port 33446.\n");
ck_assert_msg(error == TOX_ERR_GET_PORT_OK, "wrong error");
}
{
TOX_ERR_GET_PORT error;
ck_assert_msg(tox_self_get_udp_port(tox3, &error) == 33447, "Third Tox instance did not bind to udp port 33447.\n");
ck_assert_msg(error == TOX_ERR_GET_PORT_OK, "wrong error");
}
uint32_t to_compare = 974536;
connected_t1 = 0;
tox_callback_self_connection_status(tox1, tox_connection_status, &to_compare);
tox_callback_friend_request(tox2, accept_friend_request, &to_compare);
uint8_t address[TOX_ADDRESS_SIZE];
tox_self_get_address(tox2, address);
uint32_t test = tox_friend_add(tox3, address, (uint8_t *)"Gentoo", 7, 0);
ck_assert_msg(test == 0, "Failed to add friend error code: %i", test);
uint8_t off = 1;
while (1) {
tox_iterate(tox1);
tox_iterate(tox2);
tox_iterate(tox3);
if (tox_self_get_connection_status(tox1) && tox_self_get_connection_status(tox2)
&& tox_self_get_connection_status(tox3)) {
if (off) {
printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time);
con_time = time(NULL);
off = 0;
}
if (tox_friend_get_connection_status(tox2, 0, 0) == TOX_CONNECTION_UDP
&& tox_friend_get_connection_status(tox3, 0, 0) == TOX_CONNECTION_UDP)
break;
}
c_sleep(50);
}
ck_assert_msg(connected_t1, "Tox1 isn't connected. %u", connected_t1);
printf("tox clients connected took %llu seconds\n", time(NULL) - con_time);
to_compare = 974536;
tox_callback_friend_message(tox3, print_message, &to_compare);
uint8_t msgs[TOX_MAX_MESSAGE_LENGTH + 1];
memset(msgs, 'G', sizeof(msgs));
TOX_ERR_FRIEND_SEND_MESSAGE errm;
tox_friend_send_message(tox2, 0, TOX_MESSAGE_TYPE_NORMAL, msgs, TOX_MAX_MESSAGE_LENGTH + 1, &errm);
ck_assert_msg(errm == TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG, "TOX_MAX_MESSAGE_LENGTH is too small\n");
tox_friend_send_message(tox2, 0, TOX_MESSAGE_TYPE_NORMAL, msgs, TOX_MAX_MESSAGE_LENGTH, &errm);
ck_assert_msg(errm == TOX_ERR_FRIEND_SEND_MESSAGE_OK, "TOX_MAX_MESSAGE_LENGTH is too big\n");
while (1) {
messages_received = 0;
tox_iterate(tox1);
tox_iterate(tox2);
tox_iterate(tox3);
if (messages_received)
break;
c_sleep(50);
}
printf("tox clients messaging succeeded\n");
unsigned int save_size1 = tox_get_savedata_size(tox2);
ck_assert_msg(save_size1 != 0 && save_size1 < 4096, "save is invalid size %u", save_size1);
printf("%u\n", save_size1);
uint8_t save1[save_size1];
tox_get_savedata(tox2, save1);
tox_kill(tox2);
struct Tox_Options options;
tox_options_default(&options);
options.savedata_type = TOX_SAVEDATA_TYPE_TOX_SAVE;
options.savedata_data = save1;
options.savedata_length = save_size1;
tox2 = tox_new(&options, NULL);
cur_time = time(NULL);
off = 1;
while (1) {
tox_iterate(tox1);
tox_iterate(tox2);
tox_iterate(tox3);
if (tox_self_get_connection_status(tox1) && tox_self_get_connection_status(tox2)
&& tox_self_get_connection_status(tox3)) {
if (off) {
printf("Toxes are online again after reloading, took %llu seconds\n", time(NULL) - cur_time);
con_time = time(NULL);
off = 0;
}
if (tox_friend_get_connection_status(tox2, 0, 0) == TOX_CONNECTION_UDP
&& tox_friend_get_connection_status(tox3, 0, 0) == TOX_CONNECTION_UDP)
break;
}
c_sleep(50);
}
printf("tox clients connected took %llu seconds\n", time(NULL) - con_time);
tox_callback_friend_name(tox3, print_nickchange, &to_compare);
TOX_ERR_SET_INFO err_n;
bool succ = tox_self_set_name(tox2, (uint8_t *)"Gentoo", sizeof("Gentoo"), &err_n);
ck_assert_msg(succ && err_n == TOX_ERR_SET_INFO_OK, "tox_self_set_name failed because %u\n", err_n);
while (1) {
name_changes = 0;
tox_iterate(tox1);
tox_iterate(tox2);
tox_iterate(tox3);
if (name_changes)
break;
c_sleep(50);
}
ck_assert_msg(tox_friend_get_name_size(tox3, 0, 0) == sizeof("Gentoo"), "Name length not correct");
uint8_t temp_name[sizeof("Gentoo")];
tox_friend_get_name(tox3, 0, temp_name, 0);
ck_assert_msg(memcmp(temp_name, "Gentoo", sizeof("Gentoo")) == 0, "Name not correct");
tox_callback_friend_status_message(tox3, print_status_m_change, &to_compare);
succ = tox_self_set_status_message(tox2, (uint8_t *)"Installing Gentoo", sizeof("Installing Gentoo"), &err_n);
ck_assert_msg(succ && err_n == TOX_ERR_SET_INFO_OK, "tox_self_set_status_message failed because %u\n", err_n);
while (1) {
status_m_changes = 0;
tox_iterate(tox1);
tox_iterate(tox2);
tox_iterate(tox3);
if (status_m_changes)
break;
c_sleep(50);
}
ck_assert_msg(tox_friend_get_status_message_size(tox3, 0, 0) == sizeof("Installing Gentoo"),
"status message length not correct");
uint8_t temp_status_m[sizeof("Installing Gentoo")];
tox_friend_get_status_message(tox3, 0, temp_status_m, 0);
ck_assert_msg(memcmp(temp_status_m, "Installing Gentoo", sizeof("Installing Gentoo")) == 0,
"status message not correct");
tox_callback_friend_typing(tox2, &print_typingchange, &to_compare);
tox_self_set_typing(tox3, 0, 1, 0);
while (1) {
typing_changes = 0;
tox_iterate(tox1);
tox_iterate(tox2);
tox_iterate(tox3);
if (typing_changes == 2)
break;
else
ck_assert_msg(typing_changes == 0, "Typing fail");
c_sleep(50);
}
ck_assert_msg(tox_friend_get_typing(tox2, 0, 0) == 1, "Typing fail");
tox_self_set_typing(tox3, 0, 0, 0);
while (1) {
typing_changes = 0;
tox_iterate(tox1);
tox_iterate(tox2);
tox_iterate(tox3);
if (typing_changes == 1)
break;
else
ck_assert_msg(typing_changes == 0, "Typing fail");
c_sleep(50);
}
TOX_ERR_FRIEND_QUERY err_t;
ck_assert_msg(tox_friend_get_typing(tox2, 0, &err_t) == 0, "Typing fail");
ck_assert_msg(err_t == TOX_ERR_FRIEND_QUERY_OK, "Typing fail");
uint32_t packet_number = 160;
tox_callback_friend_lossless_packet(tox3, &handle_custom_packet, &packet_number);
uint8_t data_c[TOX_MAX_CUSTOM_PACKET_SIZE + 1];
memset(data_c, ((uint8_t)packet_number), sizeof(data_c));
int ret = tox_friend_send_lossless_packet(tox2, 0, data_c, sizeof(data_c), 0);
ck_assert_msg(ret == 0, "tox_friend_send_lossless_packet bigger fail %i", ret);
ret = tox_friend_send_lossless_packet(tox2, 0, data_c, TOX_MAX_CUSTOM_PACKET_SIZE, 0);
ck_assert_msg(ret == 1, "tox_friend_send_lossless_packet fail %i", ret);
while (1) {
custom_packet = 0;
tox_iterate(tox1);
tox_iterate(tox2);
tox_iterate(tox3);
if (custom_packet == 1)
break;
else
ck_assert_msg(custom_packet == 0, "Lossless packet fail");
c_sleep(50);
}
packet_number = 200;
tox_callback_friend_lossy_packet(tox3, &handle_custom_packet, &packet_number);
memset(data_c, ((uint8_t)packet_number), sizeof(data_c));
ret = tox_friend_send_lossy_packet(tox2, 0, data_c, sizeof(data_c), 0);
ck_assert_msg(ret == 0, "tox_friend_send_lossy_packet bigger fail %i", ret);
ret = tox_friend_send_lossy_packet(tox2, 0, data_c, TOX_MAX_CUSTOM_PACKET_SIZE, 0);
ck_assert_msg(ret == 1, "tox_friend_send_lossy_packet fail %i", ret);
while (1) {
custom_packet = 0;
tox_iterate(tox1);
tox_iterate(tox2);
tox_iterate(tox3);
if (custom_packet == 1)
break;
else
ck_assert_msg(custom_packet == 0, "lossy packet fail");
c_sleep(50);
}
printf("Starting file transfer test.\n");
file_accepted = file_size = file_recv = sendf_ok = size_recv = 0;
max_sending = UINT64_MAX;
long long unsigned int f_time = time(NULL);
tox_callback_file_recv_chunk(tox3, write_file, &to_compare);
tox_callback_file_recv_control(tox2, file_print_control, &to_compare);
tox_callback_file_chunk_request(tox2, tox_file_chunk_request, &to_compare);
tox_callback_file_recv_control(tox3, file_print_control, &to_compare);
tox_callback_file_recv(tox3, tox_file_receive, &to_compare);
uint64_t totalf_size = 100 * 1024 * 1024;
uint32_t fnum = tox_file_send(tox2, 0, TOX_FILE_KIND_DATA, totalf_size, 0, (uint8_t *)"Gentoo.exe",
sizeof("Gentoo.exe"), 0);
ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail");
TOX_ERR_FILE_GET gfierr;
ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error");
ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error");
ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed");
ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error");
while (1) {
tox_iterate(tox1);
tox_iterate(tox2);
tox_iterate(tox3);
if (file_sending_done) {
if (sendf_ok && file_recv && totalf_size == file_size && size_recv == file_size && sending_pos == size_recv
&& file_accepted == 1) {
break;
} else {
ck_abort_msg("Something went wrong in file transfer %u %u %u %u %u %u %llu %llu %llu", sendf_ok, file_recv,
totalf_size == file_size, size_recv == file_size, sending_pos == size_recv, file_accepted == 1, totalf_size, size_recv,
sending_pos);
}
}
uint32_t tox1_interval = tox_iteration_interval(tox1);
uint32_t tox2_interval = tox_iteration_interval(tox2);
uint32_t tox3_interval = tox_iteration_interval(tox3);
if (tox2_interval > tox3_interval) {
c_sleep(tox3_interval);
} else {
c_sleep(tox2_interval);
}
}
printf("100MB file sent in %llu seconds\n", time(NULL) - f_time);
printf("Starting file streaming transfer test.\n");
file_sending_done = file_accepted = file_size = file_recv = sendf_ok = size_recv = 0;
f_time = time(NULL);
tox_callback_file_recv_chunk(tox3, write_file, &to_compare);
tox_callback_file_recv_control(tox2, file_print_control, &to_compare);
tox_callback_file_chunk_request(tox2, tox_file_chunk_request, &to_compare);
tox_callback_file_recv_control(tox3, file_print_control, &to_compare);
tox_callback_file_recv(tox3, tox_file_receive, &to_compare);
totalf_size = UINT64_MAX;
fnum = tox_file_send(tox2, 0, TOX_FILE_KIND_DATA, totalf_size, 0, (uint8_t *)"Gentoo.exe", sizeof("Gentoo.exe"), 0);
ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail");
ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error");
ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error");
ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed");
ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error");
max_sending = 100 * 1024;
m_send_reached = 0;
while (1) {
tox_iterate(tox1);
tox_iterate(tox2);
tox_iterate(tox3);
if (file_sending_done) {
if (sendf_ok && file_recv && m_send_reached && totalf_size == file_size && size_recv == max_sending
&& sending_pos == size_recv && file_accepted == 1) {
break;
} else {
ck_abort_msg("Something went wrong in file transfer %u %u %u %u %u %u %u %llu %llu %llu %llu", sendf_ok, file_recv,
m_send_reached, totalf_size == file_size, size_recv == max_sending, sending_pos == size_recv, file_accepted == 1,
totalf_size, file_size,
size_recv, sending_pos);
}
}
uint32_t tox1_interval = tox_iteration_interval(tox1);
uint32_t tox2_interval = tox_iteration_interval(tox2);
uint32_t tox3_interval = tox_iteration_interval(tox3);
if (tox2_interval > tox3_interval) {
c_sleep(tox3_interval);
} else {
c_sleep(tox2_interval);
}
}
printf("Starting file 0 transfer test.\n");
file_sending_done = file_accepted = file_size = file_recv = sendf_ok = size_recv = 0;
f_time = time(NULL);
tox_callback_file_recv_chunk(tox3, write_file, &to_compare);
tox_callback_file_recv_control(tox2, file_print_control, &to_compare);
tox_callback_file_chunk_request(tox2, tox_file_chunk_request, &to_compare);
tox_callback_file_recv_control(tox3, file_print_control, &to_compare);
tox_callback_file_recv(tox3, tox_file_receive, &to_compare);
totalf_size = 0;
fnum = tox_file_send(tox2, 0, TOX_FILE_KIND_DATA, totalf_size, 0, (uint8_t *)"Gentoo.exe", sizeof("Gentoo.exe"), 0);
ck_assert_msg(fnum != UINT32_MAX, "tox_new_file_sender fail");
ck_assert_msg(!tox_file_get_file_id(tox2, 1, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
ck_assert_msg(gfierr == TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, "wrong error");
ck_assert_msg(!tox_file_get_file_id(tox2, 0, fnum + 1, file_cmp_id, &gfierr), "tox_file_get_file_id didn't fail");
ck_assert_msg(gfierr == TOX_ERR_FILE_GET_NOT_FOUND, "wrong error");
ck_assert_msg(tox_file_get_file_id(tox2, 0, fnum, file_cmp_id, &gfierr), "tox_file_get_file_id failed");
ck_assert_msg(gfierr == TOX_ERR_FILE_GET_OK, "wrong error");
while (1) {
tox_iterate(tox1);
tox_iterate(tox2);
tox_iterate(tox3);
if (file_sending_done) {
if (sendf_ok && file_recv && totalf_size == file_size && size_recv == file_size && sending_pos == size_recv
&& file_accepted == 1) {
break;
} else {
ck_abort_msg("Something went wrong in file transfer %u %u %u %u %u %u %llu %llu %llu", sendf_ok, file_recv,
totalf_size == file_size, size_recv == file_size, sending_pos == size_recv, file_accepted == 1, totalf_size, size_recv,
sending_pos);
}
}
uint32_t tox1_interval = tox_iteration_interval(tox1);
uint32_t tox2_interval = tox_iteration_interval(tox2);
uint32_t tox3_interval = tox_iteration_interval(tox3);
if (tox2_interval > tox3_interval) {
c_sleep(tox3_interval);
} else {
c_sleep(tox2_interval);
}
}
printf("test_few_clients succeeded, took %llu seconds\n", time(NULL) - cur_time);
tox_kill(tox1);
tox_kill(tox2);
tox_kill(tox3);
}
END_TEST
#define NUM_TOXES 90
#define NUM_FRIENDS 50
START_TEST(test_many_clients)
{
long long unsigned int cur_time = time(NULL);
Tox *toxes[NUM_TOXES];
uint32_t i, j;
uint32_t to_comp = 974536;
for (i = 0; i < NUM_TOXES; ++i) {
toxes[i] = tox_new(0, 0);
ck_assert_msg(toxes[i] != 0, "Failed to create tox instances %u", i);
tox_callback_friend_request(toxes[i], accept_friend_request, &to_comp);
}
{
TOX_ERR_GET_PORT error;
ck_assert_msg(tox_self_get_udp_port(toxes[0], &error) == 33445, "First Tox instance did not bind to udp port 33445.\n");
ck_assert_msg(error == TOX_ERR_GET_PORT_OK, "wrong error");
}
struct {
uint16_t tox1;
uint16_t tox2;
} pairs[NUM_FRIENDS];
uint8_t address[TOX_ADDRESS_SIZE];
unsigned int num_f = 0;
for (i = 0; i < NUM_TOXES; ++i) {
num_f += tox_self_get_friend_list_size(toxes[i]);
}
ck_assert_msg(num_f == 0, "bad num friends: %u", num_f);
for (i = 0; i < NUM_FRIENDS; ++i) {
loop_top:
pairs[i].tox1 = rand() % NUM_TOXES;
pairs[i].tox2 = (pairs[i].tox1 + rand() % (NUM_TOXES - 1) + 1) % NUM_TOXES;
for (j = 0; j < i; ++j) {
if (pairs[j].tox2 == pairs[i].tox1 && pairs[j].tox1 == pairs[i].tox2)
goto loop_top;
}
tox_self_get_address(toxes[pairs[i].tox1], address);
TOX_ERR_FRIEND_ADD test;
uint32_t num = tox_friend_add(toxes[pairs[i].tox2], address, (uint8_t *)"Gentoo", 7, &test);
if (test == TOX_ERR_FRIEND_ADD_ALREADY_SENT) {
goto loop_top;
}
ck_assert_msg(num != UINT32_MAX && test == TOX_ERR_FRIEND_ADD_OK, "Failed to add friend error code: %i", test);
}
for (i = 0; i < NUM_TOXES; ++i) {
num_f += tox_self_get_friend_list_size(toxes[i]);
}
ck_assert_msg(num_f == NUM_FRIENDS, "bad num friends: %u", num_f);
while (1) {
uint16_t counter = 0;
for (i = 0; i < NUM_TOXES; ++i) {
for (j = 0; j < tox_self_get_friend_list_size(toxes[i]); ++j)
if (tox_friend_get_connection_status(toxes[i], j, 0) == TOX_CONNECTION_UDP)
++counter;
}
if (counter == NUM_FRIENDS * 2) {
break;
}
for (i = 0; i < NUM_TOXES; ++i) {
tox_iterate(toxes[i]);
}
c_sleep(50);
}
for (i = 0; i < NUM_TOXES; ++i) {
tox_kill(toxes[i]);
}
printf("test_many_clients succeeded, took %llu seconds\n", time(NULL) - cur_time);
}
END_TEST
#define NUM_TOXES_TCP 40
#define TCP_RELAY_PORT 33448
START_TEST(test_many_clients_tcp)
{
long long unsigned int cur_time = time(NULL);
Tox *toxes[NUM_TOXES_TCP];
uint32_t i, j;
uint32_t to_comp = 974536;
for (i = 0; i < NUM_TOXES_TCP; ++i) {
struct Tox_Options opts;
tox_options_default(&opts);
if (i == 0) {
opts.tcp_port = TCP_RELAY_PORT;
} else {
opts.udp_enabled = 0;
}
toxes[i] = tox_new(&opts, 0);
ck_assert_msg(toxes[i] != 0, "Failed to create tox instances %u", i);
tox_callback_friend_request(toxes[i], accept_friend_request, &to_comp);
uint8_t dpk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_dht_id(toxes[0], dpk);
ck_assert_msg(tox_add_tcp_relay(toxes[i], "::1", TCP_RELAY_PORT, dpk, 0), "add relay error");
ck_assert_msg(tox_bootstrap(toxes[i], "::1", 33445, dpk, 0), "Bootstrap error");
}
{
TOX_ERR_GET_PORT error;
ck_assert_msg(tox_self_get_udp_port(toxes[0], &error) == 33445, "First Tox instance did not bind to udp port 33445.\n");
ck_assert_msg(error == TOX_ERR_GET_PORT_OK, "wrong error");
ck_assert_msg(tox_self_get_tcp_port(toxes[0], &error) == TCP_RELAY_PORT,
"First Tox instance did not bind to tcp port %u.\n", TCP_RELAY_PORT);
ck_assert_msg(error == TOX_ERR_GET_PORT_OK, "wrong error");
}
struct {
uint16_t tox1;
uint16_t tox2;
} pairs[NUM_FRIENDS];
uint8_t address[TOX_ADDRESS_SIZE];
for (i = 0; i < NUM_FRIENDS; ++i) {
loop_top:
pairs[i].tox1 = rand() % NUM_TOXES_TCP;
pairs[i].tox2 = (pairs[i].tox1 + rand() % (NUM_TOXES_TCP - 1) + 1) % NUM_TOXES_TCP;
for (j = 0; j < i; ++j) {
if (pairs[j].tox2 == pairs[i].tox1 && pairs[j].tox1 == pairs[i].tox2)
goto loop_top;
}
tox_self_get_address(toxes[pairs[i].tox1], address);
TOX_ERR_FRIEND_ADD test;
uint32_t num = tox_friend_add(toxes[pairs[i].tox2], address, (uint8_t *)"Gentoo", 7, &test);
if (test == TOX_ERR_FRIEND_ADD_ALREADY_SENT) {
goto loop_top;
}
ck_assert_msg(num != UINT32_MAX && test == TOX_ERR_FRIEND_ADD_OK, "Failed to add friend error code: %i", test);
}
while (1) {
uint16_t counter = 0, cc = 0;
for (i = 0; i < NUM_TOXES_TCP; ++i) {
for (j = 0; j < tox_self_get_friend_list_size(toxes[i]); ++j)
if (tox_friend_get_connection_status(toxes[i], j, 0) == TOX_CONNECTION_TCP)
++counter;
}
if (counter == NUM_FRIENDS * 2) {
break;
}
for (i = 0; i < NUM_TOXES_TCP; ++i) {
tox_iterate(toxes[i]);
}
c_sleep(50);
}
for (i = 0; i < NUM_TOXES_TCP; ++i) {
tox_kill(toxes[i]);
}
printf("test_many_clients_tcp succeeded, took %llu seconds\n", time(NULL) - cur_time);
}
END_TEST
#define NUM_TCP_RELAYS 3
START_TEST(test_many_clients_tcp_b)
{
long long unsigned int cur_time = time(NULL);
Tox *toxes[NUM_TOXES_TCP];
uint32_t i, j;
uint32_t to_comp = 974536;
for (i = 0; i < NUM_TOXES_TCP; ++i) {
struct Tox_Options opts;
tox_options_default(&opts);
if (i < NUM_TCP_RELAYS) {
opts.tcp_port = TCP_RELAY_PORT + i;
} else {
opts.udp_enabled = 0;
}
toxes[i] = tox_new(&opts, 0);
ck_assert_msg(toxes[i] != 0, "Failed to create tox instances %u", i);
tox_callback_friend_request(toxes[i], accept_friend_request, &to_comp);
uint8_t dpk[TOX_PUBLIC_KEY_SIZE];
tox_self_get_dht_id(toxes[(i % NUM_TCP_RELAYS)], dpk);
ck_assert_msg(tox_add_tcp_relay(toxes[i], "::1", TCP_RELAY_PORT + (i % NUM_TCP_RELAYS), dpk, 0), "add relay error");
tox_self_get_dht_id(toxes[0], dpk);
ck_assert_msg(tox_bootstrap(toxes[i], "::1", 33445, dpk, 0), "Bootstrap error");
}
{
TOX_ERR_GET_PORT error;
ck_assert_msg(tox_self_get_udp_port(toxes[0], &error) == 33445, "First Tox instance did not bind to udp port 33445.\n");
ck_assert_msg(error == TOX_ERR_GET_PORT_OK, "wrong error");
ck_assert_msg(tox_self_get_tcp_port(toxes[0], &error) == TCP_RELAY_PORT,
"First Tox instance did not bind to tcp port %u.\n", TCP_RELAY_PORT);
ck_assert_msg(error == TOX_ERR_GET_PORT_OK, "wrong error");
}
struct {
uint16_t tox1;
uint16_t tox2;
} pairs[NUM_FRIENDS];
uint8_t address[TOX_ADDRESS_SIZE];
for (i = 0; i < NUM_FRIENDS; ++i) {
loop_top:
pairs[i].tox1 = rand() % NUM_TOXES_TCP;
pairs[i].tox2 = (pairs[i].tox1 + rand() % (NUM_TOXES_TCP - 1) + 1) % NUM_TOXES_TCP;
for (j = 0; j < i; ++j) {
if (pairs[j].tox2 == pairs[i].tox1 && pairs[j].tox1 == pairs[i].tox2)
goto loop_top;
}
tox_self_get_address(toxes[pairs[i].tox1], address);
TOX_ERR_FRIEND_ADD test;
uint32_t num = tox_friend_add(toxes[pairs[i].tox2], address, (uint8_t *)"Gentoo", 7, &test);
if (test == TOX_ERR_FRIEND_ADD_ALREADY_SENT) {
goto loop_top;
}
ck_assert_msg(num != UINT32_MAX && test == TOX_ERR_FRIEND_ADD_OK, "Failed to add friend error code: %i", test);
}
while (1) {
uint16_t counter = 0;
for (i = 0; i < NUM_TOXES_TCP; ++i) {
for (j = 0; j < tox_self_get_friend_list_size(toxes[i]); ++j)
if (tox_friend_get_connection_status(toxes[i], j, 0) == TOX_CONNECTION_TCP)
++counter;
}
if (counter == NUM_FRIENDS * 2) {
break;
}
for (i = 0; i < NUM_TOXES_TCP; ++i) {
tox_iterate(toxes[i]);
}
c_sleep(50);
}
for (i = 0; i < NUM_TOXES_TCP; ++i) {
tox_kill(toxes[i]);
}
printf("test_many_clients_tcp_b succeeded, took %llu seconds\n", time(NULL) - cur_time);
}
END_TEST
#define NUM_GROUP_TOX 32
void g_accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata)
{
if (*((uint32_t *)userdata) != 234212)
return;
if (length == 7 && memcmp("Gentoo", data, 7) == 0) {
tox_friend_add_norequest(m, public_key, 0);
}
}
static Tox *invite_tox;
static unsigned int invite_counter;
void print_group_invite_callback(Tox *tox, int32_t friendnumber, uint8_t type, const uint8_t *data, uint16_t length,
void *userdata)
{
if (*((uint32_t *)userdata) != 234212)
return;
if (type != TOX_GROUPCHAT_TYPE_TEXT)
return;
int g_num;
if ((g_num = tox_join_groupchat(tox, friendnumber, data, length)) == -1)
return;
ck_assert_msg(g_num == 0, "Group number was not 0");
ck_assert_msg(tox_join_groupchat(tox, friendnumber, data, length) == -1,
"Joining groupchat twice should be impossible.");
invite_tox = tox;
invite_counter = 4;
}
static unsigned int num_recv;
void print_group_message(Tox *tox, int groupnumber, int peernumber, const uint8_t *message, uint16_t length,
void *userdata)
{
if (*((uint32_t *)userdata) != 234212)
return;
if (length == (sizeof("Install Gentoo") - 1) && memcmp(message, "Install Gentoo", sizeof("Install Gentoo") - 1) == 0) {
++num_recv;
}
}
START_TEST(test_many_group)
{
long long unsigned int cur_time = time(NULL);
Tox *toxes[NUM_GROUP_TOX];
unsigned int i, j, k;
uint32_t to_comp = 234212;
for (i = 0; i < NUM_GROUP_TOX; ++i) {
toxes[i] = tox_new(0, 0);
ck_assert_msg(toxes[i] != 0, "Failed to create tox instances %u", i);
tox_callback_friend_request(toxes[i], &g_accept_friend_request, &to_comp);
tox_callback_group_invite(toxes[i], &print_group_invite_callback, &to_comp);
}
{
TOX_ERR_GET_PORT error;
ck_assert_msg(tox_self_get_udp_port(toxes[0], &error) == 33445, "First Tox instance did not bind to udp port 33445.\n");
ck_assert_msg(error == TOX_ERR_GET_PORT_OK, "wrong error");
}
uint8_t address[TOX_ADDRESS_SIZE];
tox_self_get_address(toxes[NUM_GROUP_TOX - 1], address);
for (i = 0; i < NUM_GROUP_TOX; ++i) {
ck_assert_msg(tox_friend_add(toxes[i], address, (uint8_t *)"Gentoo", 7, 0) == 0, "Failed to add friend");
tox_self_get_address(toxes[i], address);
}
while (1) {
for (i = 0; i < NUM_GROUP_TOX; ++i) {
if (tox_friend_get_connection_status(toxes[i], 0, 0) != TOX_CONNECTION_UDP) {
break;
}
}
if (i == NUM_GROUP_TOX)
break;
for (i = 0; i < NUM_GROUP_TOX; ++i) {
tox_iterate(toxes[i]);
}
c_sleep(50);
}
printf("friends connected, took %llu seconds\n", time(NULL) - cur_time);
ck_assert_msg(tox_add_groupchat(toxes[0]) != -1, "Failed to create group");
ck_assert_msg(tox_invite_friend(toxes[0], 0, 0) == 0, "Failed to invite friend");
ck_assert_msg(tox_group_set_title(toxes[0], 0, "Gentoo", sizeof("Gentoo") - 1) == 0, "Failed to set group title");
invite_counter = ~0;
unsigned int done = ~0;
done -= 5;
while (1) {
for (i = 0; i < NUM_GROUP_TOX; ++i) {
tox_iterate(toxes[i]);
}
if (!invite_counter) {
ck_assert_msg(tox_invite_friend(invite_tox, 0, 0) == 0, "Failed to invite friend");
}
if (done == invite_counter) {
break;
}
--invite_counter;
c_sleep(50);
}
for (i = 0; i < NUM_GROUP_TOX; ++i) {
int num_peers = tox_group_number_peers(toxes[i], 0);
ck_assert_msg(num_peers == NUM_GROUP_TOX, "Bad number of group peers. expected: %u got: %i, tox %u", NUM_GROUP_TOX,
num_peers, i);
uint8_t title[2048];
int ret = tox_group_get_title(toxes[i], 0, title, sizeof(title));
ck_assert_msg(ret == sizeof("Gentoo") - 1, "Wrong title length");
ck_assert_msg(memcmp("Gentoo", title, ret) == 0, "Wrong title");
}
printf("group connected\n");
for (i = 0; i < NUM_GROUP_TOX; ++i) {
tox_callback_group_message(toxes[i], &print_group_message, &to_comp);
}
ck_assert_msg(tox_group_message_send(toxes[rand() % NUM_GROUP_TOX], 0, (uint8_t *)"Install Gentoo",
sizeof("Install Gentoo") - 1) == 0, "Failed to send group message.");
num_recv = 0;
for (j = 0; j < 20; ++j) {
for (i = 0; i < NUM_GROUP_TOX; ++i) {
tox_iterate(toxes[i]);
}
c_sleep(50);
}
ck_assert_msg(num_recv == NUM_GROUP_TOX, "Failed to recv group messages.");
for (k = NUM_GROUP_TOX; k != 0 ; --k) {
tox_del_groupchat(toxes[k - 1], 0);
for (j = 0; j < 10; ++j) {
for (i = 0; i < NUM_GROUP_TOX; ++i) {
tox_iterate(toxes[i]);
}
c_sleep(50);
}
for (i = 0; i < (k - 1); ++i) {
int num_peers = tox_group_number_peers(toxes[i], 0);
ck_assert_msg(num_peers == (k - 1), "Bad number of group peers. expected: %u got: %i, tox %u", (k - 1), num_peers, i);
}
}
for (i = 0; i < NUM_GROUP_TOX; ++i) {
tox_kill(toxes[i]);
}
printf("test_many_group succeeded, took %llu seconds\n", time(NULL) - cur_time);
}
END_TEST
Suite *tox_suite(void)
{
Suite *s = suite_create("Tox");
DEFTESTCASE(one);
DEFTESTCASE_SLOW(few_clients, 80);
DEFTESTCASE_SLOW(many_clients, 80);
DEFTESTCASE_SLOW(many_clients_tcp, 20);
DEFTESTCASE_SLOW(many_clients_tcp_b, 20);
DEFTESTCASE_SLOW(many_group, 100);
return s;
}
int main(int argc, char *argv[])
{
srand((unsigned int) time(NULL));
Suite *tox = tox_suite();
SRunner *test_runner = srunner_create(tox);
int number_failed = 0;
srunner_run_all(test_runner, CK_NORMAL);
number_failed = srunner_ntests_failed(test_runner);
srunner_free(test_runner);
return number_failed;
}
================================================
FILE: auto_tests/toxav_basic_test.c
================================================
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifndef HAVE_LIBCHECK
# include
# define ck_assert(X) assert(X);
# define START_TEST(NAME) void NAME ()
# define END_TEST
#else
# include "helpers.h"
#endif
#include
#include
#include
#include
#include
#include
#include
#include "../toxcore/tox.h"
#include "../toxcore/util.h"
#include "../toxcore/logger.h"
#include "../toxcore/crypto_core.h"
#include "../toxav/toxav.h"
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
#define c_sleep(x) Sleep(1*x)
#else
#include
#define c_sleep(x) usleep(1000*x)
#endif
#define TEST_REGULAR_AV 1
#define TEST_REGULAR_A 1
#define TEST_REGULAR_V 1
#define TEST_REJECT 1
#define TEST_CANCEL 1
#define TEST_MUTE_UNMUTE 1
#define TEST_STOP_RESUME_PAYLOAD 1
#define TEST_PAUSE_RESUME_SEND 1
typedef struct {
bool incoming;
uint32_t state;
} CallControl;
/**
* Callbacks
*/
void t_toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data)
{
(void) av;
(void) friend_number;
(void) audio_enabled;
(void) video_enabled;
printf("Handling CALL callback\n");
((CallControl *)user_data)->incoming = true;
}
void t_toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data)
{
(void) av;
(void) friend_number;
printf("Handling CALL STATE callback: %d\n", state);
((CallControl *)user_data)->state = state;
}
void t_toxav_receive_video_frame_cb(ToxAV *av, uint32_t friend_number,
uint16_t width, uint16_t height,
uint8_t const *y, uint8_t const *u, uint8_t const *v,
int32_t ystride, int32_t ustride, int32_t vstride,
void *user_data)
{
(void) av;
(void) friend_number;
(void) width;
(void) height;
(void) y;
(void) u;
(void) v;
(void) ystride;
(void) ustride;
(void) vstride;
(void) user_data;
printf("Received video payload\n");
}
void t_toxav_receive_audio_frame_cb(ToxAV *av, uint32_t friend_number,
int16_t const *pcm,
size_t sample_count,
uint8_t channels,
uint32_t sampling_rate,
void *user_data)
{
(void) av;
(void) friend_number;
(void) pcm;
(void) sample_count;
(void) channels;
(void) sampling_rate;
(void) user_data;
printf("Received audio payload\n");
}
void t_accept_friend_request_cb(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata)
{
(void) userdata;
if (length == 7 && memcmp("gentoo", data, 7) == 0) {
ck_assert(tox_friend_add_norequest(m, public_key, NULL) != (uint32_t) ~0);
}
}
/**
* Iterate helper
*/
int iterate_tox(Tox *bootstrap, Tox *Alice, Tox *Bob)
{
tox_iterate(bootstrap);
tox_iterate(Alice);
tox_iterate(Bob);
return MIN(tox_iteration_interval(Alice), tox_iteration_interval(Bob));
}
START_TEST(test_AV_flows)
{
Tox *Alice, *Bob, *bootstrap;
ToxAV *AliceAV, *BobAV;
CallControl AliceCC, BobCC;
{
TOX_ERR_NEW error;
bootstrap = tox_new(NULL, &error);
ck_assert(error == TOX_ERR_NEW_OK);
Alice = tox_new(NULL, &error);
ck_assert(error == TOX_ERR_NEW_OK);
Bob = tox_new(NULL, &error);
ck_assert(error == TOX_ERR_NEW_OK);
}
printf("Created 3 instances of Tox\n");
printf("Preparing network...\n");
long long unsigned int cur_time = time(NULL);
uint32_t to_compare = 974536;
uint8_t address[TOX_ADDRESS_SIZE];
tox_callback_friend_request(Alice, t_accept_friend_request_cb, &to_compare);
tox_self_get_address(Alice, address);
ck_assert(tox_friend_add(Bob, address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0);
uint8_t off = 1;
while (1) {
iterate_tox(bootstrap, Alice, Bob);
if (tox_self_get_connection_status(bootstrap) &&
tox_self_get_connection_status(Alice) &&
tox_self_get_connection_status(Bob) && off) {
printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time);
off = 0;
}
if (tox_friend_get_connection_status(Alice, 0, NULL) == TOX_CONNECTION_UDP &&
tox_friend_get_connection_status(Bob, 0, NULL) == TOX_CONNECTION_UDP)
break;
c_sleep(20);
}
{
TOXAV_ERR_NEW error;
AliceAV = toxav_new(Alice, &error);
ck_assert(error == TOXAV_ERR_NEW_OK);
BobAV = toxav_new(Bob, &error);
ck_assert(error == TOXAV_ERR_NEW_OK);
}
toxav_callback_call(AliceAV, t_toxav_call_cb, &AliceCC);
toxav_callback_call_state(AliceAV, t_toxav_call_state_cb, &AliceCC);
toxav_callback_video_receive_frame(AliceAV, t_toxav_receive_video_frame_cb, &AliceCC);
toxav_callback_audio_receive_frame(AliceAV, t_toxav_receive_audio_frame_cb, &AliceCC);
toxav_callback_call(BobAV, t_toxav_call_cb, &BobCC);
toxav_callback_call_state(BobAV, t_toxav_call_state_cb, &BobCC);
toxav_callback_video_receive_frame(BobAV, t_toxav_receive_video_frame_cb, &BobCC);
toxav_callback_audio_receive_frame(BobAV, t_toxav_receive_audio_frame_cb, &BobCC);
printf("Created 2 instances of ToxAV\n");
printf("All set after %llu seconds!\n", time(NULL) - cur_time);
#define REGULAR_CALL_FLOW(A_BR, V_BR) \
do { \
memset(&AliceCC, 0, sizeof(CallControl)); \
memset(&BobCC, 0, sizeof(CallControl)); \
\
TOXAV_ERR_CALL rc; \
toxav_call(AliceAV, 0, A_BR, V_BR, &rc); \
\
if (rc != TOXAV_ERR_CALL_OK) { \
printf("toxav_call failed: %d\n", rc); \
ck_assert(0); \
} \
\
\
long long unsigned int start_time = time(NULL); \
\
\
while (BobCC.state != TOXAV_FRIEND_CALL_STATE_FINISHED) { \
\
if (BobCC.incoming) { \
TOXAV_ERR_ANSWER rc; \
toxav_answer(BobAV, 0, A_BR, V_BR, &rc); \
\
if (rc != TOXAV_ERR_ANSWER_OK) { \
printf("toxav_answer failed: %d\n", rc); \
ck_assert(0); \
} \
BobCC.incoming = false; \
} else { \
/* TODO rtp */ \
\
if (time(NULL) - start_time >= 1) { \
\
TOXAV_ERR_CALL_CONTROL rc; \
toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc); \
\
if (rc != TOXAV_ERR_CALL_CONTROL_OK) { \
printf("toxav_call_control failed: %d\n", rc); \
ck_assert(0); \
} \
} \
} \
\
iterate_tox(bootstrap, Alice, Bob); \
} \
printf("Success!\n");\
} while(0)
if (TEST_REGULAR_AV) {
printf("\nTrying regular call (Audio and Video)...\n");
REGULAR_CALL_FLOW(48, 4000);
}
if (TEST_REGULAR_A) {
printf("\nTrying regular call (Audio only)...\n");
REGULAR_CALL_FLOW(48, 0);
}
if (TEST_REGULAR_V) {
printf("\nTrying regular call (Video only)...\n");
REGULAR_CALL_FLOW(0, 4000);
}
#undef REGULAR_CALL_FLOW
if (TEST_REJECT) { /* Alice calls; Bob rejects */
printf("\nTrying reject flow...\n");
memset(&AliceCC, 0, sizeof(CallControl));
memset(&BobCC, 0, sizeof(CallControl));
{
TOXAV_ERR_CALL rc;
toxav_call(AliceAV, 0, 48, 0, &rc);
if (rc != TOXAV_ERR_CALL_OK) {
printf("toxav_call failed: %d\n", rc);
ck_assert(0);
}
}
while (!BobCC.incoming)
iterate_tox(bootstrap, Alice, Bob);
/* Reject */
{
TOXAV_ERR_CALL_CONTROL rc;
toxav_call_control(BobAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc);
if (rc != TOXAV_ERR_CALL_CONTROL_OK) {
printf("toxav_call_control failed: %d\n", rc);
ck_assert(0);
}
}
while (AliceCC.state != TOXAV_FRIEND_CALL_STATE_FINISHED)
iterate_tox(bootstrap, Alice, Bob);
printf("Success!\n");
}
if (TEST_CANCEL) { /* Alice calls; Alice cancels while ringing */
printf("\nTrying cancel (while ringing) flow...\n");
memset(&AliceCC, 0, sizeof(CallControl));
memset(&BobCC, 0, sizeof(CallControl));
{
TOXAV_ERR_CALL rc;
toxav_call(AliceAV, 0, 48, 0, &rc);
if (rc != TOXAV_ERR_CALL_OK) {
printf("toxav_call failed: %d\n", rc);
ck_assert(0);
}
}
while (!BobCC.incoming)
iterate_tox(bootstrap, Alice, Bob);
/* Cancel */
{
TOXAV_ERR_CALL_CONTROL rc;
toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc);
if (rc != TOXAV_ERR_CALL_CONTROL_OK) {
printf("toxav_call_control failed: %d\n", rc);
ck_assert(0);
}
}
/* Alice will not receive end state */
while (BobCC.state != TOXAV_FRIEND_CALL_STATE_FINISHED)
iterate_tox(bootstrap, Alice, Bob);
printf("Success!\n");
}
if (TEST_MUTE_UNMUTE) { /* Check Mute-Unmute etc */
printf("\nTrying mute functionality...\n");
memset(&AliceCC, 0, sizeof(CallControl));
memset(&BobCC, 0, sizeof(CallControl));
/* Assume sending audio and video */
{
TOXAV_ERR_CALL rc;
toxav_call(AliceAV, 0, 48, 1000, &rc);
if (rc != TOXAV_ERR_CALL_OK) {
printf("toxav_call failed: %d\n", rc);
ck_assert(0);
}
}
while (!BobCC.incoming)
iterate_tox(bootstrap, Alice, Bob);
/* At first try all stuff while in invalid state */
ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_PAUSE, NULL));
ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_RESUME, NULL));
ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO, NULL));
ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO, NULL));
ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_HIDE_VIDEO, NULL));
ck_assert(!toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_SHOW_VIDEO, NULL));
{
TOXAV_ERR_ANSWER rc;
toxav_answer(BobAV, 0, 48, 4000, &rc);
if (rc != TOXAV_ERR_ANSWER_OK) {
printf("toxav_answer failed: %d\n", rc);
ck_assert(0);
}
}
iterate_tox(bootstrap, Alice, Bob);
/* Pause and Resume */
printf("Pause and Resume\n");
ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_PAUSE, NULL));
iterate_tox(bootstrap, Alice, Bob);
ck_assert(BobCC.state == 0);
ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_RESUME, NULL));
iterate_tox(bootstrap, Alice, Bob);
ck_assert(BobCC.state & (TOXAV_FRIEND_CALL_STATE_SENDING_A | TOXAV_FRIEND_CALL_STATE_SENDING_V));
/* Mute/Unmute single */
printf("Mute/Unmute single\n");
ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO, NULL));
iterate_tox(bootstrap, Alice, Bob);
ck_assert(BobCC.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_A);
ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO, NULL));
iterate_tox(bootstrap, Alice, Bob);
ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A);
/* Mute/Unmute both */
printf("Mute/Unmute both\n");
ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_MUTE_AUDIO, NULL));
iterate_tox(bootstrap, Alice, Bob);
ck_assert(BobCC.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_A);
ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_HIDE_VIDEO, NULL));
iterate_tox(bootstrap, Alice, Bob);
ck_assert(BobCC.state ^ TOXAV_FRIEND_CALL_STATE_ACCEPTING_V);
ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_UNMUTE_AUDIO, NULL));
iterate_tox(bootstrap, Alice, Bob);
ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_A);
ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_SHOW_VIDEO, NULL));
iterate_tox(bootstrap, Alice, Bob);
ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V);
{
TOXAV_ERR_CALL_CONTROL rc;
toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc);
if (rc != TOXAV_ERR_CALL_CONTROL_OK) {
printf("toxav_call_control failed: %d\n", rc);
ck_assert(0);
}
}
iterate_tox(bootstrap, Alice, Bob);
ck_assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED);
printf("Success!\n");
}
if (TEST_STOP_RESUME_PAYLOAD) { /* Stop and resume audio/video payload */
printf("\nTrying stop/resume functionality...\n");
memset(&AliceCC, 0, sizeof(CallControl));
memset(&BobCC, 0, sizeof(CallControl));
/* Assume sending audio and video */
{
TOXAV_ERR_CALL rc;
toxav_call(AliceAV, 0, 48, 0, &rc);
if (rc != TOXAV_ERR_CALL_OK) {
printf("toxav_call failed: %d\n", rc);
ck_assert(0);
}
}
while (!BobCC.incoming)
iterate_tox(bootstrap, Alice, Bob);
{
TOXAV_ERR_ANSWER rc;
toxav_answer(BobAV, 0, 48, 0, &rc);
if (rc != TOXAV_ERR_ANSWER_OK) {
printf("toxav_answer failed: %d\n", rc);
ck_assert(0);
}
}
iterate_tox(bootstrap, Alice, Bob);
printf("Call started as audio only\n");
printf("Turning on video for Alice...\n");
ck_assert(toxav_bit_rate_set(AliceAV, 0, -1, 1000, NULL));
iterate_tox(bootstrap, Alice, Bob);
ck_assert(BobCC.state & TOXAV_FRIEND_CALL_STATE_SENDING_V);
printf("Turning off video for Alice...\n");
ck_assert(toxav_bit_rate_set(AliceAV, 0, -1, 0, NULL));
iterate_tox(bootstrap, Alice, Bob);
ck_assert(!(BobCC.state & TOXAV_FRIEND_CALL_STATE_SENDING_V));
printf("Turning off audio for Alice...\n");
ck_assert(toxav_bit_rate_set(AliceAV, 0, 0, -1, NULL));
iterate_tox(bootstrap, Alice, Bob);
ck_assert(!(BobCC.state & TOXAV_FRIEND_CALL_STATE_SENDING_A));
{
TOXAV_ERR_CALL_CONTROL rc;
toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc);
if (rc != TOXAV_ERR_CALL_CONTROL_OK) {
printf("toxav_call_control failed: %d\n", rc);
ck_assert(0);
}
}
iterate_tox(bootstrap, Alice, Bob);
ck_assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED);
printf("Success!\n");
}
if (TEST_PAUSE_RESUME_SEND) { /* Stop and resume audio/video payload and test send options */
printf("\nTrying stop/resume functionality...\n");
memset(&AliceCC, 0, sizeof(CallControl));
memset(&BobCC, 0, sizeof(CallControl));
/* Assume sending audio and video */
{
TOXAV_ERR_CALL rc;
toxav_call(AliceAV, 0, 48, 0, &rc);
if (rc != TOXAV_ERR_CALL_OK) {
printf("toxav_call failed: %d\n", rc);
ck_assert(0);
}
}
while (!BobCC.incoming)
iterate_tox(bootstrap, Alice, Bob);
{
TOXAV_ERR_ANSWER rc;
toxav_answer(BobAV, 0, 48, 0, &rc);
if (rc != TOXAV_ERR_ANSWER_OK) {
printf("toxav_answer failed: %d\n", rc);
ck_assert(0);
}
}
int16_t PCM[5670];
iterate_tox(bootstrap, Alice, Bob);
ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_PAUSE, NULL));
iterate_tox(bootstrap, Alice, Bob);
ck_assert(!toxav_audio_send_frame(AliceAV, 0, PCM, 960, 1, 48000, NULL));
ck_assert(!toxav_audio_send_frame(BobAV, 0, PCM, 960, 1, 48000, NULL));
ck_assert(toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_RESUME, NULL));
iterate_tox(bootstrap, Alice, Bob);
ck_assert(toxav_audio_send_frame(AliceAV, 0, PCM, 960, 1, 48000, NULL));
ck_assert(toxav_audio_send_frame(BobAV, 0, PCM, 960, 1, 48000, NULL));
iterate_tox(bootstrap, Alice, Bob);
{
TOXAV_ERR_CALL_CONTROL rc;
toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc);
if (rc != TOXAV_ERR_CALL_CONTROL_OK) {
printf("toxav_call_control failed: %d\n", rc);
ck_assert(0);
}
}
iterate_tox(bootstrap, Alice, Bob);
ck_assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED);
printf("Success!\n");
}
toxav_kill(BobAV);
toxav_kill(AliceAV);
tox_kill(Bob);
tox_kill(Alice);
tox_kill(bootstrap);
printf("\nTest successful!\n");
}
END_TEST
#ifndef HAVE_LIBCHECK
int main(int argc, char *argv[])
{
(void) argc;
(void) argv;
test_AV_flows();
return 0;
}
#else
Suite *tox_suite(void)
{
Suite *s = suite_create("ToxAV");
DEFTESTCASE_SLOW(AV_flows, 200);
return s;
}
int main(int argc, char *argv[])
{
(void) argc;
(void) argv;
Suite *tox = tox_suite();
SRunner *test_runner = srunner_create(tox);
setbuf(stdout, NULL);
srunner_run_all(test_runner, CK_NORMAL);
int number_failed = srunner_ntests_failed(test_runner);
srunner_free(test_runner);
return number_failed;
}
#endif
================================================
FILE: auto_tests/toxav_many_test.c
================================================
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifndef HAVE_LIBCHECK
# include
# define ck_assert(X) assert(X);
# define START_TEST(NAME) void NAME ()
# define END_TEST
#else
# include "helpers.h"
#endif
#include
#include
#include
#include
#include
#include
#include
#include "../toxcore/tox.h"
#include "../toxcore/util.h"
#include "../toxcore/logger.h"
#include "../toxcore/crypto_core.h"
#include "../toxav/toxav.h"
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
#define c_sleep(x) Sleep(1*x)
#else
#include
#include
#define c_sleep(x) usleep(1000*x)
#endif
typedef struct {
bool incoming;
uint32_t state;
} CallControl;
typedef struct {
ToxAV *AliceAV;
ToxAV *BobAV;
CallControl *AliceCC;
CallControl *BobCC;
uint32_t friend_number;
} thread_data;
/**
* Callbacks
*/
void t_toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data)
{
(void) av;
(void) audio_enabled;
(void) video_enabled;
printf("Handling CALL callback\n");
((CallControl *)user_data)[friend_number].incoming = true;
}
void t_toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data)
{
printf("Handling CALL STATE callback: %d %p\n", state, av);
((CallControl *)user_data)[friend_number].state = state;
}
void t_toxav_receive_video_frame_cb(ToxAV *av, uint32_t friend_number,
uint16_t width, uint16_t height,
uint8_t const *y, uint8_t const *u, uint8_t const *v,
int32_t ystride, int32_t ustride, int32_t vstride,
void *user_data)
{
(void) av;
(void) friend_number;
(void) width;
(void) height;
(void) y;
(void) u;
(void) v;
(void) ystride;
(void) ustride;
(void) vstride;
(void) user_data;
}
void t_toxav_receive_audio_frame_cb(ToxAV *av, uint32_t friend_number,
int16_t const *pcm,
size_t sample_count,
uint8_t channels,
uint32_t sampling_rate,
void *user_data)
{
(void) av;
(void) friend_number;
(void) pcm;
(void) sample_count;
(void) channels;
(void) sampling_rate;
(void) user_data;
}
void t_accept_friend_request_cb(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata)
{
(void) userdata;
if (length == 7 && memcmp("gentoo", data, 7) == 0) {
ck_assert(tox_friend_add_norequest(m, public_key, NULL) != (uint32_t) ~0);
}
}
/**
* Iterate helper
*/
ToxAV *setup_av_instance(Tox *tox, CallControl *CC)
{
TOXAV_ERR_NEW error;
ToxAV *av = toxav_new(tox, &error);
ck_assert(error == TOXAV_ERR_NEW_OK);
toxav_callback_call(av, t_toxav_call_cb, CC);
toxav_callback_call_state(av, t_toxav_call_state_cb, CC);
toxav_callback_video_receive_frame(av, t_toxav_receive_video_frame_cb, CC);
toxav_callback_audio_receive_frame(av, t_toxav_receive_audio_frame_cb, CC);
return av;
}
void *call_thread(void *pd)
{
ToxAV *AliceAV = ((thread_data *) pd)->AliceAV;
ToxAV *BobAV = ((thread_data *) pd)->BobAV;
CallControl *AliceCC = ((thread_data *) pd)->AliceCC;
CallControl *BobCC = ((thread_data *) pd)->BobCC;
uint32_t friend_number = ((thread_data *) pd)->friend_number;
memset(AliceCC, 0, sizeof(CallControl));
memset(BobCC, 0, sizeof(CallControl));
{ /* Call */
TOXAV_ERR_CALL rc;
toxav_call(AliceAV, friend_number, 48, 3000, &rc);
if (rc != TOXAV_ERR_CALL_OK) {
printf("toxav_call failed: %d\n", rc);
ck_assert(0);
}
}
while (!BobCC->incoming)
c_sleep(10);
{ /* Answer */
TOXAV_ERR_ANSWER rc;
toxav_answer(BobAV, 0, 8, 500, &rc);
if (rc != TOXAV_ERR_ANSWER_OK) {
printf("toxav_answer failed: %d\n", rc);
ck_assert(0);
}
}
c_sleep(30);
int16_t PCM[960];
uint8_t video_y[800 * 600];
uint8_t video_u[800 * 600 / 2];
uint8_t video_v[800 * 600 / 2];
memset(PCM, 0, sizeof(PCM));
memset(video_y, 0, sizeof(video_y));
memset(video_u, 0, sizeof(video_u));
memset(video_v, 0, sizeof(video_v));
time_t start_time = time(NULL);
while (time(NULL) - start_time < 4) {
toxav_iterate(AliceAV);
toxav_iterate(BobAV);
toxav_audio_send_frame(AliceAV, friend_number, PCM, 960, 1, 48000, NULL);
toxav_audio_send_frame(BobAV, 0, PCM, 960, 1, 48000, NULL);
toxav_video_send_frame(AliceAV, friend_number, 800, 600, video_y, video_u, video_v, NULL);
toxav_video_send_frame(BobAV, 0, 800, 600, video_y, video_u, video_v, NULL);
c_sleep(10);
}
{ /* Hangup */
TOXAV_ERR_CALL_CONTROL rc;
toxav_call_control(AliceAV, friend_number, TOXAV_CALL_CONTROL_CANCEL, &rc);
if (rc != TOXAV_ERR_CALL_CONTROL_OK) {
printf("toxav_call_control failed: %d %p %p\n", rc, AliceAV, BobAV);
}
}
c_sleep(30);
printf ("Closing thread\n");
pthread_exit(NULL);
}
START_TEST(test_AV_three_calls)
{
Tox *Alice, *bootstrap, *Bobs[3];
ToxAV *AliceAV, *BobsAV[3];
CallControl AliceCC[3], BobsCC[3];
{
TOX_ERR_NEW error;
bootstrap = tox_new(NULL, &error);
ck_assert(error == TOX_ERR_NEW_OK);
Alice = tox_new(NULL, &error);
ck_assert(error == TOX_ERR_NEW_OK);
Bobs[0] = tox_new(NULL, &error);
ck_assert(error == TOX_ERR_NEW_OK);
Bobs[1] = tox_new(NULL, &error);
ck_assert(error == TOX_ERR_NEW_OK);
Bobs[2] = tox_new(NULL, &error);
ck_assert(error == TOX_ERR_NEW_OK);
}
printf("Created 5 instances of Tox\n");
printf("Preparing network...\n");
long long unsigned int cur_time = time(NULL);
uint32_t to_compare = 974536;
uint8_t address[TOX_ADDRESS_SIZE];
tox_callback_friend_request(Alice, t_accept_friend_request_cb, &to_compare);
tox_self_get_address(Alice, address);
ck_assert(tox_friend_add(Bobs[0], address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0);
ck_assert(tox_friend_add(Bobs[1], address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0);
ck_assert(tox_friend_add(Bobs[2], address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0);
uint8_t off = 1;
while (1) {
tox_iterate(bootstrap);
tox_iterate(Alice);
tox_iterate(Bobs[0]);
tox_iterate(Bobs[1]);
tox_iterate(Bobs[2]);
if (tox_self_get_connection_status(bootstrap) &&
tox_self_get_connection_status(Alice) &&
tox_self_get_connection_status(Bobs[0]) &&
tox_self_get_connection_status(Bobs[1]) &&
tox_self_get_connection_status(Bobs[2]) && off) {
printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time);
off = 0;
}
if (tox_friend_get_connection_status(Alice, 0, NULL) == TOX_CONNECTION_UDP &&
tox_friend_get_connection_status(Alice, 1, NULL) == TOX_CONNECTION_UDP &&
tox_friend_get_connection_status(Alice, 2, NULL) == TOX_CONNECTION_UDP &&
tox_friend_get_connection_status(Bobs[0], 0, NULL) == TOX_CONNECTION_UDP &&
tox_friend_get_connection_status(Bobs[1], 0, NULL) == TOX_CONNECTION_UDP &&
tox_friend_get_connection_status(Bobs[2], 0, NULL) == TOX_CONNECTION_UDP)
break;
c_sleep(20);
}
AliceAV = setup_av_instance(Alice, AliceCC);
BobsAV[0] = setup_av_instance(Bobs[0], BobsCC + 0);
BobsAV[1] = setup_av_instance(Bobs[1], BobsCC + 1);
BobsAV[2] = setup_av_instance(Bobs[2], BobsCC + 2);
printf("Created 4 instances of ToxAV\n");
printf("All set after %llu seconds!\n", time(NULL) - cur_time);
thread_data tds[3];
tds[0].AliceAV = AliceAV;
tds[0].BobAV = BobsAV[0];
tds[0].AliceCC = AliceCC + 0;
tds[0].BobCC = BobsCC + 0;
tds[0].friend_number = 0;
tds[1].AliceAV = AliceAV;
tds[1].BobAV = BobsAV[1];
tds[1].AliceCC = AliceCC + 1;
tds[1].BobCC = BobsCC + 1;
tds[1].friend_number = 1;
tds[2].AliceAV = AliceAV;
tds[2].BobAV = BobsAV[2];
tds[2].AliceCC = AliceCC + 2;
tds[2].BobCC = BobsCC + 2;
tds[2].friend_number = 2;
pthread_t tids[3];
(void) pthread_create(tids + 0, NULL, call_thread, tds + 0);
(void) pthread_create(tids + 1, NULL, call_thread, tds + 1);
(void) pthread_create(tids + 2, NULL, call_thread, tds + 2);
(void) pthread_detach(tids[0]);
(void) pthread_detach(tids[1]);
(void) pthread_detach(tids[2]);
time_t start_time = time(NULL);
while (time(NULL) - start_time < 5) {
tox_iterate(Alice);
tox_iterate(Bobs[0]);
tox_iterate(Bobs[1]);
tox_iterate(Bobs[2]);
c_sleep(20);
}
(void) pthread_join(tids[0], NULL);
(void) pthread_join(tids[1], NULL);
(void) pthread_join(tids[2], NULL);
printf ("Killing all instances\n");
toxav_kill(BobsAV[0]);
toxav_kill(BobsAV[1]);
toxav_kill(BobsAV[2]);
toxav_kill(AliceAV);
tox_kill(Bobs[0]);
tox_kill(Bobs[1]);
tox_kill(Bobs[2]);
tox_kill(Alice);
tox_kill(bootstrap);
printf("\nTest successful!\n");
}
END_TEST
#ifndef HAVE_LIBCHECK
int main(int argc, char *argv[])
{
(void) argc;
(void) argv;
test_AV_three_calls();
return 0;
}
#else
Suite *tox_suite(void)
{
Suite *s = suite_create("ToxAV");
TCase *tc_av_three_calls = tcase_create("AV_three_calls");
tcase_add_test(tc_av_three_calls, test_AV_three_calls);
tcase_set_timeout(tc_av_three_calls, 150);
suite_add_tcase(s, tc_av_three_calls);
return s;
}
int main(int argc, char *argv[])
{
(void) argc;
(void) argv;
Suite *tox = tox_suite();
SRunner *test_runner = srunner_create(tox);
setbuf(stdout, NULL);
srunner_run_all(test_runner, CK_NORMAL);
int number_failed = srunner_ntests_failed(test_runner);
srunner_free(test_runner);
return number_failed;
}
#endif
================================================
FILE: autogen.sh
================================================
#!/bin/sh -e
echo 'Running autoreconf -if...'
(
autoreconf -if
)
================================================
FILE: configure.ac
================================================
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.65])
AC_INIT([tox], [0.0.0])
AC_CONFIG_AUX_DIR(configure_aux)
AC_CONFIG_SRCDIR([toxcore/net_crypto.c])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE([foreign 1.10 -Wall subdir-objects tar-ustar])
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
AC_CONFIG_MACRO_DIR([m4])
EXTRA_LT_LDFLAGS=
LIBTOXCORE_LT_VERSION=0:0:0
LIBTOXAV_LT_VERSION=0:0:0
dnl
dnl current:revision:age
dnl
dnl current: increment if interfaces have been added, removed or changed
dnl revision: increment if source code has changed, set to zero if current is
dnl incremented
dnl age: increment if interfaces have been added, set to zero if
dnl interfaces have been removed or changed
if test "x${prefix}" = "xNONE"; then
prefix="${ac_default_prefix}"
fi
BUILD_DHT_BOOTSTRAP_DAEMON="no"
BUILD_NTOX="no"
BUILD_TESTS="yes"
BUILD_AV="yes"
BUILD_TESTING="yes"
TOX_LOGGER="no"
LOGGING_OUTNAM="libtoxcore.log"
NCURSES_FOUND="no"
LIBCONFIG_FOUND="no"
LIBCHECK_FOUND="no"
WANT_NACL="no"
ADD_NACL_OBJECTS_TO_PKGCONFIG="yes"
TOXCORE_LT_LDFLAGS="-version-info $LIBTOXCORE_LT_VERSION"
TOXAV_LT_LDFLAGS="-version-info $LIBTOXAV_LT_VERSION"
AC_ARG_ENABLE([soname-versions],
[AC_HELP_STRING([--enable-soname-versions], [enable soname versions (must be disabled for android) (default: enabled)]) ],
[
if test "x$enableval" = "xno"; then
TOXCORE_LT_LDFLAGS="-avoid-version"
TOXAV_LT_LDFLAGS="-avoid-version"
fi
]
)
AC_SUBST(TOXCORE_LT_LDFLAGS)
AC_SUBST(TOXAV_LT_LDFLAGS)
AC_ARG_ENABLE([nacl],
[AC_HELP_STRING([--enable-nacl], [use nacl instead of libsodium (default: disabled)]) ],
[
if test "x$enableval" = "xno"; then
WANT_NACL="no"
elif test "x$enableval" = "xyes"; then
WANT_NACL="yes"
fi
]
)
AC_ARG_ENABLE([randombytes-stir],
[AC_HELP_STRING([--enable-randombytes-stir], [use randombytes_stir() instead of sodium_init() for faster startup on android (default: disabled)]) ],
[
if test "x$enableval" = "xyes"; then
if test "x$WANT_NACL" = "xyes"; then
AC_MSG_WARN([randombytes_stir() is not available with NaCl library])
else
AC_DEFINE([USE_RANDOMBYTES_STIR], [1], [randombytes_stir() instead of sodium_init()])
fi
fi
]
)
AC_ARG_ENABLE([logging],
[AC_HELP_STRING([--enable-logging], [enable logging (default: auto)]) ],
[
if test "x$enableval" = "xyes"; then
TOX_LOGGER="yes"
AC_DEFINE([TOX_LOGGER], [], [If logging enabled])
AC_DEFINE([LOGGER_LEVEL], [LOG_DEBUG], [LOG_LEVEL value])
AC_DEFINE_UNQUOTED([LOGGER_OUTPUT_FILE], ["$LOGGING_OUTNAM"], [Output of logger])
fi
]
)
AC_ARG_WITH(log-level,
AC_HELP_STRING([--with-log-level=LEVEL],
[Logger levels: TRACE; DEBUG; INFO; WARNING; ERROR ]),
[
if test "x$TOX_LOGGER" = "xno"; then
AC_MSG_WARN([Logging disabled!])
else
if test "x$withval" = "xTRACE"; then
AC_DEFINE([LOGGER_LEVEL], [LOG_TRACE], [LOG_LEVEL value])
elif test "x$withval" = "xDEBUG"; then
AC_DEFINE([LOGGER_LEVEL], [LOG_DEBUG], [LOG_LEVEL value])
elif test "x$withval" = "xINFO"; then
AC_DEFINE([LOGGER_LEVEL], [LOG_INFO], [LOG_LEVEL value])
elif test "x$withval" = "xWARNING"; then
AC_DEFINE([LOGGER_LEVEL], [LOG_WARNING], [LOG_LEVEL value])
elif test "x$withval" = "xERROR"; then
AC_DEFINE([LOGGER_LEVEL], [LOG_ERROR], [LOG_LEVEL value])
else
AC_MSG_WARN([Invalid logger level: $withval. Using default 'DEBUG'])
fi
fi
]
)
AC_ARG_WITH(log-path,
AC_HELP_STRING([--with-log-path=DIR],
[Path of logger output]),
[
if test "x$TOX_LOGGER" = "xno"; then
AC_MSG_WARN([Logging disabled!])
else
AC_DEFINE_UNQUOTED([LOGGER_OUTPUT_FILE], ["$withval""/""$LOGGING_OUTNAM"], [Output of logger])
fi
]
)
PKG_PROG_PKG_CONFIG
AC_ARG_ENABLE([av],
[AC_HELP_STRING([--disable-av], [build AV support libraries (default: auto)]) ],
[
if test "x$enableval" = "xno"; then
BUILD_AV="no"
elif test "x$enableval" = "xyes"; then
BUILD_AV="yes"
fi
]
)
AC_ARG_ENABLE([tests],
[AC_HELP_STRING([--disable-tests], [build unit tests (default: auto)]) ],
[
if test "x$enableval" = "xno"; then
BUILD_TESTS="no"
elif test "x$enableval" = "xyes"; then
BUILD_TESTS="yes"
fi
]
)
AC_ARG_ENABLE([ntox],
[AC_HELP_STRING([--enable-ntox], [build nTox client (default: auto)]) ],
[
if test "x$enableval" = "xno"; then
BUILD_NTOX="no"
elif test "x$enableval" = "xyes"; then
BUILD_NTOX="yes"
fi
]
)
AC_ARG_ENABLE([daemon],
[AC_HELP_STRING([--enable-daemon], [build DHT bootstrap daemon (default: auto)]) ],
[
if test "x$enableval" = "xno"; then
BUILD_DHT_BOOTSTRAP_DAEMON="no"
elif test "x$enableval" = "xyes"; then
BUILD_DHT_BOOTSTRAP_DAEMON="yes"
fi
]
)
AC_ARG_ENABLE([rt],
[AC_HELP_STRING([--disable-rt], [Disables the librt check (default: auto)]) ],
[
if test "x$enableval" = "xno"; then
DISABLE_RT="yes"
elif test "x$enableval" = "xyes"; then
DISABLE_RT="no"
fi
]
)
AC_ARG_ENABLE([testing],
[AC_HELP_STRING([--disable-testing], [build various testing tools (default: auto)]) ],
[
if test "x$enableval" = "xno"; then
BUILD_TESTING="no"
elif test "x$enableval" = "xyes"; then
BUILD_TESTING="yes"
fi
]
)
AC_ARG_ENABLE([[epoll]],
[AS_HELP_STRING([[--enable-epoll[=ARG]]], [enable epoll support (yes, no, auto) [auto]])],
[enable_epoll=${enableval}],
[enable_epoll='auto']
)
AX_HAVE_EPOLL
if test "$enable_epoll" != "no"; then
if test "${ax_cv_have_epoll}" = "yes"; then
AC_DEFINE([TCP_SERVER_USE_EPOLL],[1],[define to 1 to enable epoll support])
enable_epoll='yes'
else
if test "$enable_epoll" = "yes"; then
AC_MSG_ERROR([[Support for epoll was explicitly requested but cannot be enabled on this platform.]])
fi
enable_epoll='no'
fi
fi
DEPSEARCH=
LIBSODIUM_SEARCH_HEADERS=
LIBSODIUM_SEARCH_LIBS=
NACL_SEARCH_HEADERS=
NACL_SEARCH_LIBS=
AC_ARG_WITH(dependency-search,
AC_HELP_STRING([--with-dependency-search=DIR],
[search for dependencies in DIR, i.e., look for libraries in
DIR/lib and for headers in DIR/include]),
[
DEPSEARCH="$withval"
]
)
if test -n "$DEPSEARCH"; then
CFLAGS="$CFLAGS -I$DEPSEARCH/include"
CPPFLAGS="$CPPFLAGS -I$DEPSEARCH/include"
LDFLAGS="$LDFLAGS -L$DEPSEARCH/lib"
export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$DEPSEARCH/lib/pkgconfig
fi
AC_ARG_WITH(nacl-headers,
AC_HELP_STRING([--with-nacl-headers=DIR],
[search for nacl header files in DIR]),
[
NACL_SEARCH_HEADERS="$withval"
AC_MSG_NOTICE([will search for nacl header files in $withval])
]
)
AC_ARG_WITH(nacl-libs,
AC_HELP_STRING([--with-nacl-libs=DIR],
[search for nacl libraries in DIR]),
[
NACL_SEARCH_LIBS="$withval"
AC_MSG_NOTICE([will search for nacl libraries in $withval])
]
)
AC_ARG_WITH(libsodium-headers,
AC_HELP_STRING([--with-libsodium-headers=DIR],
[search for libsodium header files in DIR]),
[
LIBSODIUM_SEARCH_HEADERS="$withval"
AC_MSG_NOTICE([will search for libsodium header files in $withval])
]
)
AC_ARG_WITH(libsodium-libs,
AC_HELP_STRING([--with-libsodium-libs=DIR],
[search for libsodium libraries in DIR]),
[
LIBSODIUM_SEARCH_LIBS="$withval"
AC_MSG_NOTICE([will search for libsodium libraries in $withval])
]
)
if test "x$WANT_NACL" = "xyes"; then
enable_shared=no
enable_static=yes
fi
# Checks for programs.
AC_PROG_CC
AM_PROG_CC_C_O
m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
AC_LIBTOOL_WIN32_DLL
AC_PROG_LIBTOOL
WIN32=no
MACH=no
AC_CANONICAL_HOST
case $host_os in
*mingw*)
WIN32="yes"
EXTRA_LT_LDFLAGS="$EXTRA_LT_LDFLAGS -no-undefined"
;;
*solaris*)
LIBS="$LIBS -lssp -lsocket -lnsl"
;;
*qnx*)
LIBS="$LIBS -lsocket"
;;
*freebsd*|*openbsd*)
LDFLAGS="$LDFLAGS -L/usr/local/lib"
CFLAGS="$CFLAGS -I/usr/local/include"
CPPFLAGS="$CPPFLAGS -I/usr/local/include"
ADD_NACL_OBJECTS_TO_PKGCONFIG="no"
;;
darwin*)
MACH=yes
;;
esac
AM_CONDITIONAL(WIN32, test "x$WIN32" = "xyes")
AC_SUBST(EXTRA_LT_LDFLAGS)
# Needed math flags for some compilers
MATH_LDFLAGS="-lm"
AC_SUBST(MATH_LDFLAGS)
# Checks for libraries.
if test "x$WANT_NACL" = "xyes"; then
NACL_LIBS=
NACL_LDFLAGS=
NACL_OBJECTS=
NACL_OBJECTS_PKGCONFIG=
LDFLAGS_SAVE="$LDFLAGS"
if test -n "$NACL_SEARCH_LIBS"; then
LDFLAGS="-L$NACL_SEARCH_LIBS $LDFLAGS"
AC_CHECK_LIB(nacl, random,
[
NACL_LDFLAGS="-L$NACL_SEARCH_LIBS"
NACL_LIBS="-lnacl"
],
[
AC_MSG_ERROR([library nacl was not found in requested location $NACL_SEARCH_LIBS])
]
)
else
AC_CHECK_LIB(nacl, random,
[],
[
AC_MSG_ERROR([you enabled nacl support, but library nacl was not found on your system])
]
)
fi
if (test -f "$NACL_SEARCH_LIBS/cpucycles.o") &&
(test -f "$NACL_SEARCH_LIBS/randombytes.o"); then
NACL_OBJECTS="$NACL_SEARCH_LIBS/cpucycles.o $NACL_SEARCH_LIBS/randombytes.o"
if test "x$ADD_NACL_OBJECTS_TO_PKGCONFIG" = "xyes"; then
NACL_OBJECTS_PKGCONFIG="$NACL_OBJECTS"
fi
else
AC_MSG_ERROR([required NaCl object files cpucycles.o randombytes.o not found, please specify their location using the --with-nacl-libs parameter])
fi
LDFLAGS="$LDFLAGS_SAVE"
AC_SUBST(NACL_LIBS)
AC_SUBST(NACL_LDFLAGS)
AC_SUBST(NACL_OBJECTS)
AC_SUBST(NACL_OBJECTS_PKGCONFIG)
else
LIBSODIUM_LIBS=
LIBSODIUM_LDFLAGS=
LDFLAGS_SAVE="$LDFLAGS"
if test -n "$LIBSODIUM_SEARCH_LIBS"; then
LDFLAGS="-L$LIBSODIUM_SEARCH_LIBS $LDFLAGS"
AC_CHECK_LIB(sodium, crypto_pwhash_scryptsalsa208sha256,
[
LIBSODIUM_LDFLAGS="-L$LIBSODIUM_SEARCH_LIBS"
LIBSODIUM_LIBS="-lsodium"
],
[
AC_MSG_ERROR([required library libsodium was not found in requested location $LIBSODIUM_SEARCH_LIBS or library version is too old])
]
)
else
AC_CHECK_LIB(sodium, crypto_pwhash_scryptsalsa208sha256,
[],
[
AC_MSG_ERROR([required library libsodium was not found on your system, please check http://download.libsodium.org/libsodium/releases/ or library version is too old])
]
)
fi
LDFLAGS="$LDFLAGS_SAVE"
AC_SUBST(LIBSODIUM_LIBS)
AC_SUBST(LIBSODIUM_LDFLAGS)
fi
# Checks for header files.
AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h stdint.h stdlib.h string.h sys/socket.h sys/time.h unistd.h])
if test "x$WANT_NACL" = "xyes"; then
NACL_CFLAGS=
CFLAGS_SAVE="$CFLAGS"
CPPFLAGS_SAVE="$CPPFLAGS"
if test -n "$NACL_SEARCH_HEADERS"; then
CFLAGS="-I$NACL_SEARCH_HEADERS $CFLAGS"
CPPFLAGS="-I$NACL_SEARCH_HEADERS $CPPFLAGS"
AC_CHECK_HEADER(crypto_box.h,
[
NACL_CFLAGS="-I$NACL_SEARCH_HEADERS"
],
[
AC_MSG_ERROR([header files for library nacl were not found in requested location $NACL_SEARCH_HEADERS])
]
)
else
AC_CHECK_HEADER(crypto_box.h,
[],
[
AC_MSG_ERROR([you enabled nacl support, but nacl header files were not found on your system])
]
)
fi
CFLAGS="$CFLAGS_SAVE"
CPPFLAGS="$CPPFLAGS_SAVE"
AC_SUBST(NACL_CFLAGS)
AC_DEFINE([VANILLA_NACL], [1], [use nacl instead of libsodium])
else
LIBSODIUM_CFLAGS=
CFLAGS_SAVE="$CFLAGS"
CPPFLAGS_SAVE="$CPPFLAGS"
if test -n "$LIBSODIUM_SEARCH_HEADERS"; then
CFLAGS="-I$LIBSODIUM_SEARCH_HEADERS $CFLAGS"
CPPFLAGS="-I$LIBSODIUM_SEARCH_HEADERS $CPPFLAGS"
AC_CHECK_HEADER(sodium.h,
[
LIBSODIUM_CFLAGS="-I$LIBSODIUM_SEARCH_HEADERS"
],
[
AC_MSG_ERROR([header files for required library libsodium were not found in requested location $LIBSODIUM_SEARCH_HEADERS])
]
)
else
AC_CHECK_HEADER(sodium.h,
[],
[
AC_MSG_ERROR([header files for required library libsodium was not found on your system, please check http://download.libsodium.org/libsodium/releases/])
]
)
fi
CFLAGS="$CFLAGS_SAVE"
CPPFLAGS="$CPPFLAGS_SAVE"
AC_SUBST(LIBSODIUM_CFLAGS)
fi
# Checks for typedefs, structures, and compiler characteristics.
AC_HEADER_STDBOOL
AC_TYPE_INT16_T
AC_TYPE_INT32_T
AC_TYPE_PID_T
AC_TYPE_SIZE_T
AC_TYPE_UINT16_T
AC_TYPE_UINT32_T
AC_TYPE_UINT64_T
AC_TYPE_UINT8_T
AC_C_BIGENDIAN
# Checks for library functions.
AC_FUNC_FORK
AC_CHECK_FUNCS([gettimeofday memset socket strchr malloc])
if (test "x$WIN32" != "xyes") && (test "x$MACH" != "xyes") && (test "x${host_os#*openbsd}" == "x$host_os") && (test "x$DISABLE_RT" != "xyes"); then
AC_CHECK_LIB(rt, clock_gettime,
[
RT_LIBS="-lrt"
AC_SUBST(RT_LIBS)
],
[
AC_MSG_ERROR([required library rt was not found on your system])
]
)
fi
AX_PTHREAD(
[],
[
AC_MSG_ERROR([required library pthread was not found on your system])
]
)
AC_CHECK_LIB([pthread], [pthread_self],
[
PTHREAD_LDFLAGS="-lpthread"
AC_SUBST(PTHREAD_LDFLAGS)
]
)
if test "x$BUILD_AV" = "xyes"; then
PKG_CHECK_MODULES([OPUS], [opus],
[],
[
AC_MSG_WARN([disabling AV support $OPUS_PKG_ERRORS])
BUILD_AV="no"
]
)
fi
if test "x$BUILD_AV" = "xyes"; then
PKG_CHECK_MODULES([VPX], [vpx],
[],
[
AC_MSG_WARN([disabling AV support $VPX_PKG_ERRORS])
BUILD_AV="no"
]
)
fi
if test "x$BUILD_AV" = "xyes"; then
# toxcore lib needs an global?
# So far this works okay
AV_LIBS="$OPUS_LIBS $VPX_LIBS"
AC_SUBST(AV_LIBS)
AV_CFLAGS="$OPUS_CFLAGS $VPX_CFLAGS"
AC_SUBST(AV_CFLAGS)
fi
if test -n "$PKG_CONFIG"; then
if test "x$BUILD_TESTS" = "xyes"; then
PKG_CHECK_MODULES([CHECK], [check],
[
LIBCHECK_FOUND="yes"
],
[
AC_MSG_WARN([libcheck not found, not building unit tests: $CHECK_PKG_ERRORS])
BUILD_TESTS="no"
])
fi
if test "x$BUILD_DHT_BOOTSTRAP_DAEMON" = "xyes"; then
PKG_CHECK_MODULES([LIBCONFIG], [libconfig >= 1.4.6],
[
LIBCONFIG_FOUND="yes"
],
[
AC_MSG_WARN([$LIBCONFIG_PKG_ERRORS])
AC_MSG_WARN([libconfig not available, will not build DHT bootstrap daemon])
BUILD_DHT_BOOTSTRAP_DAEMON="no"
])
fi
if test "x$BUILD_NTOX" = "xyes"; then
PKG_CHECK_MODULES([NCURSES], [ncurses],
[
NCURSES_FOUND="yes"
],
[
AC_MSG_WARN([$NCURSES_PKG_ERRORS])
])
fi
else
AC_MSG_WARN([pkg-config was not found on your system, will search for libraries manually])
fi
if (test "x$BUILD_NTOX" = "xyes") && (test "x$NCURSES_FOUND" != "xyes"); then
AC_PATH_PROG([CURSES_CONFIG], [ncurses5-config], [no])
if test "x$CURSES_CONFIG" != "xno"; then
AC_MSG_CHECKING(ncurses cflags)
NCURSES_CFLAGS=`${CURSES_CONFIG} --cflags`
AC_MSG_RESULT($NCURSES_CFLAGS)
AC_MSG_CHECKING(ncurses libraries)
NCURSES_LIBS=`${CURSES_CONFIG} --libs`
AC_MSG_RESULT($NCURSES_LIBS)
AC_SUBST(NCURSES_CFLAGS)
AC_SUBST(NCURSES_LIBS)
NCURSES_FOUND="yes"
fi
if test "x$NCURSES_FOUND" != "xyes"; then
AC_CHECK_HEADER([curses.h],
[],
[
AC_MSG_WARN([not building nTox client because headers for the curses library were not found on your system])
BUILD_NTOX="no"
]
)
if test "x$BUILD_NTOX" = "xyes"; then
if test "x$WIN32" = "xyes"; then
AC_CHECK_LIB([pdcurses], [clear],
[
NCURSES_LIBS="-lpdcurses"
AC_SUBST(NCURSES_LIBS)
],
[
AC_MSG_ERROR([required library pdcurses was not found on your system])
BUILD_NTOX="no"
]
)
else
AC_CHECK_LIB([ncurses], [clear],
[
NCURSES_LIBS="-lncurses"
AC_SUBST(NCURSES_LIBS)
],
[
unset ac_cv_lib_ncurses_clear
AC_CHECK_LIB([ncurses], [clear],
[
NCURSES_LIBS="-lncurses -ltinfo"
AC_SUBST(NCURSES_LIBS)
],
[
AC_MSG_WARN([not building nTox client because required library ncurses was not found on your system])
BUILD_NTOX="no"
],
[
-ltinfo
]
)
]
)
fi
fi
fi
fi
if (test "x$BUILD_DHT_BOOTSTRAP_DAEMON" = "xyes") && \
(test "x$LIBCONFIG_FOUND" = "xno"); then
AC_CHECK_HEADER(libconfig.h,
[],
[
AC_MSG_WARN([header files for library libconfig was not found on your system, not building DHT bootstrap daemon])
BUILD_DHT_BOOTSTRAP_DAEMON="no"
]
)
if test "x$BUILD_DHT_BOOTSTRAP_DAEMON" = "xyes"; then
AC_CHECK_LIB(config, config_read,
[],
[
AC_MSG_WARN([library libconfig was not found on the system])
BUILD_DHT_BOOTSTRAP_DAEMON="no"
]
)
fi
fi
if (test "x$BUILD_TESTS" = "xyes") && (test "x$LIBCHECK_FOUND" = "xno"); then
AC_CHECK_HEADER([check.h],
[],
[
AC_MSG_WARN([header file for check library was not found on your system, unit tests will be disabled])
BUILD_TESTS="no"
]
)
if test "x$BUILD_TESTS" = "xyes"; then
AC_CHECK_LIB([check], [suite_create],
[],
[
AC_MSG_WARN([library check was not found on the system, unit tests will be disabled])
BUILD_TESTS="no"
]
)
fi
fi
if test "x$WIN32" = "xyes"; then
AC_CHECK_LIB(ws2_32, main,
[
WINSOCK2_LIBS="-liphlpapi -lws2_32"
AC_SUBST(WINSOCK2_LIBS)
],
[
AC_MSG_ERROR([required library was not found on the system, please check your MinGW installation])
]
)
fi
AM_CONDITIONAL(BUILD_DHT_BOOTSTRAP_DAEMON, test "x$BUILD_DHT_BOOTSTRAP_DAEMON" = "xyes")
AM_CONDITIONAL(BUILD_TESTS, test "x$BUILD_TESTS" = "xyes")
AM_CONDITIONAL(BUILD_NTOX, test "x$BUILD_NTOX" = "xyes")
AM_CONDITIONAL(BUILD_AV, test "x$BUILD_AV" = "xyes")
AM_CONDITIONAL(BUILD_TESTING, test "x$BUILD_TESTING" = "xyes")
AM_CONDITIONAL(WITH_NACL, test "x$WANT_NACL" = "xyes")
AM_CONDITIONAL(WIN32, test "x$WIN32" = "xyes")
AC_CONFIG_FILES([Makefile
build/Makefile
libtoxcore.pc
tox.spec
])
AM_COND_IF(BUILD_AV,
[
AC_CONFIG_FILES([libtoxav.pc])
],)
AC_OUTPUT
================================================
FILE: dist-build/android-arm.sh
================================================
#!/bin/sh
export CFLAGS="-Ofast -mthumb -marm -march=armv6"
TARGET_ARCH=arm TOOLCHAIN_NAME=arm-linux-androideabi-4.8 HOST_COMPILER=arm-linux-androideabi "$(dirname "$0")/android-build.sh"
================================================
FILE: dist-build/android-armv7.sh
================================================
#!/bin/sh
export CFLAGS="-Ofast -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb -marm -march=armv7-a"
TARGET_ARCH=armv7 TOOLCHAIN_NAME=arm-linux-androideabi-4.8 HOST_COMPILER=arm-linux-androideabi "$(dirname "$0")/android-build.sh"
================================================
FILE: dist-build/android-build.sh
================================================
#!/bin/sh
if [ -z "$ANDROID_NDK_HOME" ]; then
echo "You should probably set ANDROID_NDK_HOME to the directory containing"
echo "the Android NDK"
exit
fi
if [ -z "$SODIUM_HOME" ]; then
echo "You should probably set SODIUM_HOME to the directory containing root sodium sources"
exit
fi
if [[ -z $TARGET_ARCH ]] || [[ -z $HOST_COMPILER ]]; then
echo "You shouldn't use android-build.sh directly, use android-[arch].sh instead"
exit 1
fi
if [ ! -f ./configure ]; then
echo "Can't find ./configure. Wrong directory or haven't run autogen.sh?"
exit 1
fi
if [ -z "$TOOLCHAIN_DIR" ]; then
export TOOLCHAIN_DIR="$(pwd)/android-toolchain-${TARGET_ARCH}"
export MAKE_TOOLCHAIN="${ANDROID_NDK_HOME}/build/tools/make-standalone-toolchain.sh"
if [ -z "$MAKE_TOOLCHAIN" ]; then
echo "Cannot find a make-standalone-toolchain.sh in ndk dir, interrupt..."
exit 1
fi
$MAKE_TOOLCHAIN --platform="${NDK_PLATFORM:-android-14}" \
--arch="${TARGET_ARCH}" \
--toolchain="${TOOLCHAIN_NAME:-arm-linux-androideabi-4.8}" \
--install-dir="${TOOLCHAIN_DIR}"
fi
export PREFIX="$(pwd)/toxcore-android-${TARGET_ARCH}"
export SYSROOT="${TOOLCHAIN_DIR}/sysroot"
export PATH="${PATH}:${TOOLCHAIN_DIR}/bin"
# Clean up before build
rm -rf "${PREFIX}"
export CFLAGS="${CFLAGS} --sysroot=${SYSROOT} -I${SYSROOT}/usr/include"
export CPPFLAGS="${CFLAGS}"
export LDFLAGS="${LDFLAGS} -L${SYSROOT}/usr/lib"
./configure --host="${HOST_COMPILER}" \
--with-sysroot="${SYSROOT}" \
--with-libsodium-headers="${SODIUM_HOME}/libsodium-android-${TARGET_ARCH}/include" \
--with-libsodium-libs="${SODIUM_HOME}/libsodium-android-${TARGET_ARCH}/lib" \
--disable-av \
--prefix="${PREFIX}" && \
make clean && \
make -j3 install && \
echo "libtoxcore has been installed into ${PREFIX}"
================================================
FILE: dist-build/android-mips.sh
================================================
#!/bin/sh
export CFLAGS="-Ofast"
TARGET_ARCH=mips TOOLCHAIN_NAME=mipsel-linux-android-4.8 HOST_COMPILER=mipsel-linux-android "$(dirname "$0")/android-build.sh"
================================================
FILE: dist-build/android-x86.sh
================================================
#!/bin/sh
export CFLAGS="-Ofast"
TARGET_ARCH=x86 TOOLCHAIN_NAME=x86-4.8 HOST_COMPILER=i686-linux-android "$(dirname "$0")/android-build.sh"
================================================
FILE: docs/Group-Chats.md
================================================
Group chats.
Note: we assume everyone in the chat trusts each other.
These group chats work by temporarily adding the 4 "closest" people defined by a distance function
in group.c in order to form a circle of connected peers. These peers then relay messages to each other.
A friend invites another friend to a group chat by sending them an invite packet. The friend either ignores
the invite or responds with a response packet if he wants to join the chat. The friend invite contains the type
of groupchat (text only, A/V) the friend is being invited to.
TODO: write more of this.
## Protocol
Invite packets:
Invite packet:
[uint8_t id 96][uint8_t id 0][uint16_t group chat number][33 bytes group chat identifier[1 byte type][32 bytes id]]
Response packet
[uint8_t id 96][uint8_t id 1][uint16_t group chat number(local)][uint16_t group chat number to join][33 bytes group chat identifier[1 byte type][32 bytes id]]
Peer online packet:
[uint8_t id 97][uint16_t group chat number (local)][33 bytes group chat identifier[1 byte type][32 bytes id]]
Peer leave packet:
[uint8_t id 98][uint16_t group chat number][uint8_t id 1]
Peer query packet:
[uint8_t id 98][uint16_t group chat number][uint8_t id 8]
Peer response packet:
[uint8_t id 98][uint16_t group chat number][uint8_t id 9][Repeated times number of peers: [uint16_t peer num][uint8_t 32bytes real public key][uint8_t 32bytes temp DHT public key][uint8_t name length][name]]
Title response packet:
[uint8_t id 98][uint16_t group chat number][uint8_t id 10][title]
Message packets:
[uint8_t id 99][uint16_t group chat number][uint16_t peer number][uint32_t message number][uint8_t with a value representing id of message][data]
Lossy Message packets:
[uint8_t id 199][uint16_t group chat number][uint16_t peer number][uint16_t message number][uint8_t with a value representing id of message][data]
Group chat types:
0: text
1: AV
Note: the message number is increased by 1 for each sent message.
message ids:
0 - ping
sent every ~60 seconds by every peer.
No data.
16 - new_peer
Tell everyone about a new peer in the chat.
[uint16_t peer_num][uint8_t 32bytes real public key][uint8_t 32bytes temp DHT public key]
17 - kill_peer
[uint16_t peer_num]
48 - name change
[uint8_t name[namelen]]
49 - groupchat title change
[uint8_t title[titlelen]]
64 - chat message
[uint8_t message[messagelen]]
65 - action (/me)
[uint8_t message[messagelen]]
================================================
FILE: docs/Hardening.txt
================================================
Currently an attacker with sufficient resources could launch a large scale
denial of service type attack by flooding the Tox network with a bunch of nodes
that do not act like real nodes to prevent people from finding each other.
Due to the design of Tox, this is the worst thing an attacker can do to disrupt
the network.
This solution's goal is to make these denial of service attack very very hard
to accomplish.
For the network to work every Tox node must:
1. Respond to ping requests.
2. Respond to get node requests with the ids of nodes closest to a queried id
(It is assumed each nodes know at least the 32 nodes closest to them.)
3. Properly send crypto request packets to their intended destination.
Currently the only thing a node needs to do to be part of the network is
respond correctly to ping requests.
The only people we really trust on the network are the nodes in our friends
list.
The behavior of each Tox node is easily predictable. This means that it possible
for Tox nodes to test the nodes that they are connected to to see if they
behave like normal Tox nodes and only send nodes that are confirmed to behave
like real Tox nodes as part of send node replies when other nodes query them.
If correctly done, this means that to poison the network an attacker can only
infiltrate the network if his "fake" nodes behave exactly like real nodes
completely defeating the purpose of the attack. Of course nodes must be
rechecked regularly to defeat an attack where someone floods the network with
many good nodes then suddenly turns them all bad.
This also prevents someone from accidentally killing the tox network with a bad
implementation of the protocol.
Implementation ideas (In Progress):
1. Use our friends to check if the nodes in our close list are good.
EX: If our friend queries a node close to us and it correctly returns our
ip/port and then sends a crypto request packet to it and it routes it correctly
to us then it is good.
Problems with this: People don't always have at least one online friend.
2. Pick random nodes (add ourselves some random (fake) friends to increase the
pool of available nodes) and make then send requests to other nodes, the
response is then relayed back to us and compared to how the node should have
behaved. If the node is found to be behaving correctly, it is set as trusted.
Only trusted nodes are sent in send node packets, that is unless the exact node
being queried for in the getnode packet is present, it will be sent in the
sendnode packet even if it is not trusted.
The hypothesis is that if to be part of the network nodes have to behave
correctly it should prevent disruption from nodes that behave incorrectly.
(This idea is currently being implemented in the harden branch.)
...
================================================
FILE: docs/Hardening_docs.txt
================================================
Hardening request packets are sent as crypto request packets (see crypto docs.)
NOTE: currently only get nodes requests are tested in the code which is why
there is only one test (more will be added soon.)
All hardening requests must contain exactly 768 bytes of data. (The data sent
must be padded with zeros if it is smaller than that.)
1. Get the information (IP_port, client_id) of the node we want to test.
2. Find a couple random nodes that is not that node (one for each test.)
3. Send crypto request packets to each of these random nodes with the data being:
[byte with value: 02 (get nodes test request)][struct Node_format (the node to
test.)][client_id(32 bytes) the id to query the node with.][padding]
4. The random node receives a packet.
-The packet is a get nodes test request:
send a get_node request to that node with the id to query in the request.
when a send_node response is received, send the following response to the
person who sent us the get nodes test request packet:
[byte with value: 03 (get nodes test response)][client_id(32 bytes):
the id of the tested node][The list of nodes it responded with in IPv6
Node format (struct Node_Format)]
PROTIP: (get node requests and response contain an encrypted part that you
can use to store information so that you don't
have to store in your memory where/if to send back the response from the
send node)
5. Receive the test responses.
-If the test(s) pass (the nodes behave in a satisfactory manner), make these
nodes have priority over those who don't pass the test(s).
================================================
FILE: docs/Prevent_Tracking.txt
================================================
Current privacy issues with the Tox DHT:
1. It makes tracking people across different IPs very easy.
Solution: Have each new DHT use a temporary public/private key pair not related
to the long term public/private key pair.
2. Metadata on which key is friends to which can be collected (The hardening
makes this somewhat harder by introducing a bunch of random traffic but not
impossible.).
Solution: If no long term keys were used in the DHT it would solve this
problem. (possibly knowing which ip is connected to which is much less
precious.)
So, it seems all our privacy problems are solved if we can manage to make every
node in the DHT have a keypair that is not related to the long term keys and is
generated every session.
So, every node in the DHT now has a temporary keypair not related to their real
long term one.
But, how do people find themselves then? We have to add a way for people to
tell their friends what their DHT public key is. We also have to somehow make
it so people can send/receive friend requests. This has to be done without
non-friends being able to find out where a node is.
The solution: Onion routing + enable the storage of some small amount of data
on DHT nodes.
Alice and bob are friends. Before joining the DHT they generate temporary
session keypairs to be used for the DHT instead of their long term keys.
Bob finds a bunch of random nodes then picks 3 random working ones (A, B, C).
Bob gets the known working node with an id closest to his real one from his list (D)
Bob then creates an onion (the packet will go through A, B, C and will end up at D)
announce request packet with his real public key, ping_id as zeros and
searching for his real public key.
Bob will announce response packets and will recursively send onion announce request
packets to closer and closer nodes until he finds the ones closest to his real public key.
Once he has done this, he will send some onion announce request packets with the right
ping_id previously received from the node when he queried it to announce himself to the node.
The nodes he announces himself to keep the information to send onion packets to that node in
memory.
Alice meanwhile searches for the nodes closest to Bobs real id using a temporary keypair and
announce request packets. She does this until she finds nodes that respond with a ping_id of zero.
She sends data to route request packet with information telling Bob her temporary id in the DHT
(or a friend request if she is not friends with him).
Bob finds her by using her temporary id and they connect to each other.
NOTE: crypto_box is used for all the asymmetric encryption and crypto_secretbox is used for all
the symmetric. Also every DHT node have a random symmetric key which they use to encrypt the stuff
in normal get node request that is used to encrypt stuff in the following.
Onion packet (request):
initial (sent from us to node A):
[uint8_t packet id (128)][nonce]
[our temp DHT public key]encrypted with our temp DHT private key and the pub key of Node A and the nonce:[
[IP_Port of node B][a random public key]encrypted with the random private key and the pub key of Node B and the nonce:[
[IP_Port of node C][a random public key]encrypted with the random private key and the pub key of Node C and the nonce:[
[IP_Port of node D][data to send to Node D]]]]
(sent from node A to node B):
[uint8_t packet id (129)][nonce]
[a random public key]encrypted with the random private key and the pub key of Node B and the nonce:[
[IP_Port of node C][a random public key]encrypted with the random private key and the pub key of Node C and the nonce:[
[IP_Port of node D][data to send to Node D]]][nonce (for the following symmetric encryption)]encrypted with temp symmetric key of Node A: [IP_Port (of us)]
(sent from node B to node C):
[uint8_t packet id (130)][nonce]
[a random public key]encrypted with the random private key and the pub key of Node C and the nonce:[
[IP_Port of node D][data to send to Node D]][nonce (for the following symmetric encryption)]
encrypted with temp symmetric key of Node B:[IP_Port (of Node A)[nonce (for the following symmetric encryption)]
encrypted with temp symmetric key of Node A: [IP_Port (of us)]]
(sent from node C to node D):
[data to send to Node D][nonce (for the following symmetric encryption)]encrypted with temp symmetric key of Node C:
[IP_Port (of Node B)[nonce (for the following symmetric encryption)]
encrypted with temp symmetric key of Node B:[IP_Port (of Node A)[nonce (for the following symmetric encryption)]
encrypted with temp symmetric key of Node A: [IP_Port (of us)]]]
Data sent to Node D:
announce request packet:
[uint8_t packet id (131)][nonce][our real long term public key or a temporary one (see next)]
encrypted (with our real long term private key if we want to announce ourselves, a temporary one if we are searching for friends) and the pub key of Node D and the nonce:
[[(32 bytes) ping_id][client id we are searching for][public key that we want those sending back data packets to use.][data to send back in response(fixed size)]]
(if the ping id is zero, respond with a announce response packet)
(If the ping id matches the one the node sent in the announce response and the public key matches the one being searched for,
add the part used to send data to our list (if the list is full make it replace the furthest entry))
data to route request packet:
[uint8_t packet id (133)][public key of destination node][nonce][temporary just generated public key]
encrypted with that temporary private key and the nonce and the public key from the announce response packet of the destination node:[data]
(if Node D contains the ret data for the node, it sends the stuff in this packet as a data to route response packet to the right node)
The data in the previous packet is in format: [real public key of sender]
encrypted with real private key of the sender, the nonce in the data packet and
the real public key of the receiver:[[uint8_t id][data (optional)]]
Data sent to us:
announce response packet:
[uint8_t packet id (132)][data to send back in response(fixed size)][nonce]
encrypted with the DHT private key of Node D, the public key in the request and the nonce:[[uint8_t is_stored]
[(32 bytes) ping_id if is_stored is 0 or 2, public key that must be used to send data packets if is_stored is 1][Node_Format * (maximum of 8)]]
(if the is_stored is not 0, it means the information to reach the client id we are searching for is stored on this node)
is_stored is 2 as a response to a peer trying to announce himself to tell the
peer that he is currently announced successfully.
data to route response packet:
[uint8_t packet id (134)][nonce][temporary just generated public key]
encrypted with that temporary private key, the nonce and the public key from the announce response packet of the destination node:[data]
Onion packet (response):
initial (sent from node D to node C):
[uint8_t packet id (140)][nonce (for the following symmetric encryption)]encrypted with temp symmetric key of Node C:
[IP_Port (of Node B)[nonce (for the following symmetric encryption)]
encrypted with temp symmetric key of Node B:[IP_Port (of Node A)[nonce (for the following symmetric encryption)]
encrypted with temp symmetric key of Node A: [IP_Port (of us)]]][data to send back]
(sent from node C to node B):
[uint8_t packet id (141)][nonce (for the following symmetric encryption)]
encrypted with temp symmetric key of Node B:[IP_Port (of Node A)[nonce (for the following symmetric encryption)]
encrypted with temp symmetric key of Node A: [IP_Port (of us)]][data to send back]
(sent from node B to node A):
[uint8_t packet id (142)][nonce (for the following symmetric encryption)]
encrypted with temp symmetric key of Node A: [IP_Port (of us)][data to send back]
(sent from node A to us):
[data to send back]
Data packets:
To tell our friend what our DHT public key is so that he can connect to us we send a data packet
with id 156 and the data being:[uint64_t (in network byte order) no_replay, the packet will only be
accepted if this number is bigger than the last one received] [our dht public key][Node_Format * (
maximum of 8) nodes closest to us so that the friend can find us faster]
================================================
FILE: docs/TCP_Network.txt
================================================
It has come to our attention that to achieve decent market penetration Tox
must work behind ALL internet connections, may they be behind enterprise NATs
or any other bad network conditions.
The people who have issues with the UDP direct connection approach seem to be a
small minority though it is hard to estimate how many.
This means that routing their packets using good nodes on the network will
probably not take a huge toll on the network and will assure that people
can use Tox regardless of the quality of their internet connection.
How it's going to work:
1. Alice, a Tox client on a TCP only network generates a temporary public key
and connects to a bootstrap node.
2. Using the bootstrap node she finds and connects to a couple (exact number
to be determined later) number of random nodes that have TCP relay support.
3. She uses the onion through the TCP relay connections to send friend requests
or tell online friends which TCP nodes she is connected to and her temporary
public key.
4. Bob receives an onion packet from Alice telling him which nodes she is
connected to. Bob connects to these nodes and establishes a routed connection
with alice using that temporary public key.
5. That connection is used by both to transmit encrypted Messenger and A/V
packets.
6. If one of the nodes shuts down while it is currently routing traffic, Alice
and bob just switch to one of the other nodes they are both connected to.
Detailed implementation details:
There are two distinct parts for TCP relays, the client part and the server
part.
The server acts as the actual relay. Servers must have fully forwarded TCP
ports (NAT-PMP and uPNP can help here). The first port the server will try
binding to is 443 followed by port 3389 and possibly some others. Onion packets
can be sent/received through the TCP servers.
Server:
The public/private key pair the TCP server uses is the same one he uses for the
DHT.
all crypto for communication with the server uses the crypto_box() function of
NaCl.
TCP doesn't have packets so what we will refer to as packets are sent this way:
[[uint16_t (length of data)][data]]
So if you would inspect the TCP stream you would see:
[[uint16_t (length of data)][data]][[uint16_t (length of
data)][data]][[uint16_t (length of data)][data]]
Note that both handshake packets don't have this format (the length for them is
always the same so we don't need to specify it.)
When the client connects to the server, he sends this packet:
[public key of client (32 bytes)][nonce for the encrypted data [24
bytes]][encrypted with the private key of the client and public key of the
server and the nonce:[public key (32 bytes) and][base nonce we want the server
to use to encrypt the packets sent to us (24 bytes)]]
The server responds with:
[nonce for the encrypted data [24 bytes]][encrypted with the public key of the
client and private key of the server and the nonce:[public key (32 bytes)
and][base nonce we want the client to use to encrypt the packets sent to us (24
bytes)]]
All packets to the server are end to end encrypted with the information
received
(and sent) in the handshake.
(first packet is encrypted with the base nonce the private key for which the
client sent the server the public key and the public key we sent to the client,
the next with base nonce + 1...)
The connection is set to an unconfirmed state until a packet is received and
decrypted correctly using the information in the handshake.
each packet sent to/from the server has an id (the first byte of the plain text
data of the packet.)
ids 0 to 15 are reserved for special packets, ids 16 to 255 are used to denote
who we want the data to be routed to/who the packet is from.
special ids and packets:
0 - Routing request.
[uint8_t id (0)][public key (32 bytes)]
1 - Routing request response.
[uint8_t id (1)][uint8_t (rpid) 0 if refused, packet id if accepted][public key
(32 bytes)]
2 - Connect notification:
[uint8_t id (2)][uint8_t (packet id of connection that got connected)]
3 - Disconnect notification:
[uint8_t id (3)][uint8_t (packet id of connection that got disconnected)]
4 - ping packet
[uint8_t id (4)][uint64_t ping_id (0 is invalid)]
5 - ping response (pong)
[uint8_t id (5)][uint64_t ping_id (0 is invalid)]
6 - OOB send
[uint8_t id (6)][destination public key (32 bytes)][data]
7 - OOB recv
[uint8_t id (7)][senders public key (32 bytes)][data]
8 - onion packet (same format as initial onion packet (See: Prevent
tracking.txt) but packet id is 8 instead of 128)
9 - onion packet response (same format as onion packet with id 142 but id is 9
instead.)
The rest of the special ids are reserved for possible future usage.
If the server receives a routing request he stores server side that the client
wants to connect to the person with that public key and sends back a Routing
request response with the rpid along with the public key sent in the request.
If for some reason the server must refuse the routing request (too many) he
sends the response with a rpid of 0.
If the person who the client wants to connect to is also online and wants to
connect to the client a connect notification is sent to both with the
appropriate packet id.
If either one disconnects, a disconnect notification is sent to the other with
appropriate packet id.
If a client sends a disconnect notification, the entry on the server for that
routed connection is cleared and a disconnect notification is sent to the peer
(if he was online)
If the server receives an onion packet he handles it the same as he would if it
was one received normally via UDP, he must also assure himself that any
responses must be sent to the proper client.
Ping responses must have the same ping_id as the request.
If the server receives a ping packet he must respond with a ping response.
The server will send a ping packet to clients every 30 seconds, they have 30
seconds to respond, if they don't the connection is deleted.
OOB send packets will be sent to the peer connected to the TCP server with the
destination public key as a OOB recv packet. The client sending this packet has
no way of knowing if the packet reached its destination.
Client:
Implementation details coming soon.
================================================
FILE: docs/TODO.md
================================================
# Toxcore todo list.
Welcome to the Toxcore todo list, this is very likely out of date, but either way it's a good jumping off point if
you're looking to see where core is going, or where it could use a little love.
There are 3 sections; In Progress, TODO, and Done. These tasks are somewhat sorted by priority, but that shouldn't be
taken to mean that this is the order tasks will (or should) be completed in.
## In Progress
- [ ] [IN PROGRESS] Audio/Video
- [X] [DONE] encoding/streaming/decoding
- [X] [DONE] Call initiation
- [X] [DONE] Encryption
- [ ] [NEEDS TESTING] Video packet splitting.
- [ ] [IN PROGRESS] Auditing.
- [ ] [IN PROGRESS] Prevent audio skew (seems to be easily solvable client side.)
- [ ] [IN PROGRESS] Group chats, audio done.
- [ ] Networking:
- [ ] [NEEDS TESTING] UPnP port forwarding. ([#969](https://github.com/irungentoo/toxcore/pull/969))
- [ ] [TODO] NAT-PMP port forwarding.
- [ ] DHT:
- [ ] [ALMOST DONE] Metadata collection prevention. (docs/Prevent_Tracking.txt)
- [ ] [IN PROGRESS] Hardening against attacks.
- [ ] [IN PROGRESS] Optimizing the code.
- [ ] [DONE] Friend only group chats
- [X] [DONE] Networking base.
- [X] [MOSTLY DONE] Syncing chat state between clients (nicknames, list of who is in chat, etc...)
- [ ] [TODO] Group private messages. (and adding friends from group chats using those private messages.)
- [ ] [TODO] Group file transfers.
- [ ] [IN PROGRESS] Friends list syncing
- [ ] [IN PROGRESS] Make toxcore thread safe.
- [ ] [MOSTLY DONE] A way for people to connect to people on Tox if they are behind a bad NAT that blocks UDP (or is
just unpunchable) ([docs/TCP_Network.txt](TCP_Network.txt)) (Current way doesn't scale very well.)
## TODO
- [ ] [TODO] Make the core save/datafile portable across client versions/different processor architectures.
- [ ] [TODO] Friend_requests.c:
- [ ] [TODO] What happens when a friend request is received needs to be changed.
- [ ] [DONE?] Add multiple nospam functionality. ([#1317](https://github.com/irungentoo/toxcore/pull/1317))
- [ ] [TODO] Offline messaging
- [ ] [TODO] Security audit from professionals
## Done
- [X] [DONE] File transfers
- [X] [DONE] IPV6 support
- [X] [DONE] Encrypted Saves. (see: toxencryptsave)
================================================
FILE: docs/Tox_middle_level_network_protocol.txt
================================================
The TCP client and TCP server part are in a state that can be considered
feature complete. Why doesn't Tox support TCP yet even if those parts are
complete?
The answer is that a way to ensure a smooth switchover between the TCP and UDP
needs to be added. If Tox first connects to the other user using TCP but then,
due to pure chance, manages to connect using the faster direct UDP connection,
Tox must switch seamlessly from the TCP to the UDP connection without there
being any data loss or the other user going offline and then back online. The
transition must be seamless whatever both connected users are doing - be it
transferring files or simply chatting together.
Possible evil/bad or simply TCP relays going offline must not impact the
connection between both clients.
Typically, Tox will use more than one TCP relay to connect to other peers for
maximum connection stability, which means there must be a way for Tox to take
advantage of multiple relays in a way that the user will never be aware of, if one
of them goes offline/tries to slow down the connection/decides to corrupt
packets/etc.
To accomplish this, Tox needs something between the low level protocol (TCP) and
high level Tox messaging protocol; hence the name middle level.
The plan is to move some functionality from lossless_UDP to a higher level:
more specifically, the functionality for detecting which packets a peer is
missing, and the ability to request and send them again. Lossless UDP uses plain
text packets to request missing packets from the other peer, while Tox is
currently designed to kill the connection if any packet tampering is detected.
This works very well when connecting directly with someone because if the
attacker can modify packets, it means he can kill your connection anyway. With
TCP relays, however, that is not the case. As such the packets used to request
missing packets must be encrypted. If it is detected that a packet has been
tampered, the connection must stay intact while the evil relay must be
disconnected from and replaced with a good relay; the behavior must be the same
as if the relay had just suddenly gone offline. Of course, something to protect
from evil "friends" framing relays must also be implemented.
Detailed implementation details:
cookie request packet:
[uint8_t 24][Sender's DHT Public key (32 bytes)][Random nonce (24
bytes)][Encrypted message containing: [Sender's real public key (32
bytes)][padding (32 bytes)][uint64_t number (must be sent
back untouched in cookie response)]]
Encrypted message is encrypted with sender's DHT private key, receiver's DHT
public key and the nonce.
cookie response packet:
[uint8_t 25][Random nonce (24 bytes)][Encrypted message containing:
[Cookie][uint64_t number (that was sent in the request)]]
Encrypted message is encrypted with sender's DHT private key, receiver's DHT
public key and the nonce.
The Cookie should be basically:
[nonce][encrypted data:[uint64_t time][Sender's real public key (32
bytes)][Sender's DHT public key (32 bytes)]]
Handshake packet:
[uint8_t 26][Cookie][nonce][Encrypted message containing: [random 24 bytes base
nonce][session public key of the peer (32 bytes)][sha512 hash of the entire
Cookie sitting outside the encrypted part][Other Cookie (used by the other to
respond to the handshake packet)]]
The handshake packet is encrypted using the real private key of the sender, the
real public key of the receiver and the nonce.
Alice wants to connect to Bob:
Alice sends a cookie request packet to Bob and gets a cookie response back.
Alice then generates a nonce and a temporary public/private keypair.
Alice then takes that nonce and just generated private key, the obtained
cookie, creates a new cookie and puts them in a handshake packet, which she
sends to Bob.
Bob gets the handshake packet, accepts the connection request, then generates a
nonce and a temporary public/private keypair and sends a handshake packet back
with this just generated information and with the cookie field being the Other
Cookie contained in the received handshake.
Both then use these temporary keys to generate the session key, with which every
data packet sent and received will be encrypted and decrypted. The nonce sent
in the handshake will be used to encrypt the first data packet sent, the nonce
+ 1 for the second, the nonce + 2 for the third, and so on.
Data packets:
[uint8_t 27][uint16_t (in network byte order) the last 2 bytes of the nonce
used to encrypt this][encrypted with the session key and a nonce:[plain data]]
Plain data in the data packets:
[uint32_t our recvbuffers buffer_start, (highest packet number handled +
1)][uint32_t packet number if lossless, our sendbuffer buffer_end if
lossy][data]
data ids:
0: padding (skipped until we hit a non zero (data id) byte)
1: packet request packet (lossy packet)
2: connection kill packet (lossy packet) (tells the other that the connection is over)
...
16+: reserved for Messenger usage (lossless packets).
192+: reserved for Messenger usage (lossy packets).
255: reserved for Messenger usage (lossless packet)
packet request packet: [uint8_t (1)][uint8_t num][uint8_t num][uint8_t
num]...[uint8_t num]
The list of nums are a list of packet numbers the other is requesting.
In order to get the real packet numbers from this list, take the recvbuffers buffer_start
from the packet, subtract 1 from it and put it in packet_num, then start from the
beginning of the num list: if num is zero, add 255 to packet_num, then do the
next num. If num isn't zero, add its value to packet_num, note that the other
has requested we send this packet again to them, then continue to the next num in
the list.
================================================
FILE: docs/av_api.md
================================================
#A/V API reference
##Take toxmsi/phone.c as a reference
###Initialization:
```
phone_t* initPhone(uint16_t _listen_port, uint16_t _send_port);
```
function initializes sample phone. _listen_port and _send_port are variables only meant
for local testing. You will not have to do anything regarding to that since
everything will be started within a mesenger.
Phone requires one msi session and two rtp sessions ( one for audio and one for
video ).
```
msi_session_t* msi_init_session( void* _core_handler, const uint8_t* _user_agent );
```
initializes msi session.
Params:
```
void* _core_handler - pointer to an object handling networking,
const uint8_t* _user_agent - string describing phone client version.
```
Return value:
msi_session_t* - pointer to a newly created msi session handler.
###msi_session_t reference:
How to handle msi session:
Controlling is done via callbacks and action handlers.
First register callbacks for every state/action received and make sure
NOT TO PLACE SOMETHING LIKE LOOPS THAT TAKES A LOT OF TIME TO EXECUTE; every callback is being called
directly from event loop. You can find examples in phone.c.
Register callbacks:
```
void msi_register_callback_call_started ( MCALLBACK );
void msi_register_callback_call_canceled ( MCALLBACK );
void msi_register_callback_call_rejected ( MCALLBACK );
void msi_register_callback_call_ended ( MCALLBACK );
void msi_register_callback_recv_invite ( MCALLBACK );
void msi_register_callback_recv_ringing ( MCALLBACK );
void msi_register_callback_recv_starting ( MCALLBACK );
void msi_register_callback_recv_ending ( MCALLBACK );
void msi_register_callback_recv_error ( MCALLBACK );
void msi_register_callback_requ_timeout ( MCALLBACK );
```
MCALLBACK is defined as: void (*callback) (void* _arg)
msi_session_t* handler is being thrown as \_arg so you can use that and \_agent_handler to get to your own phone handler
directly from callback.
Actions:
```
int msi_invite ( msi_session_t* _session, call_type _call_type, uint32_t _timeoutms );
```
Sends call invite. Before calling/sending invite msi_session_t::_friend_id is needed to be set or else
it will not work. _call_type is type of the call ( Audio/Video ) and _timeoutms is how long
will poll wait until request is terminated.
```
int msi_hangup ( msi_session_t* _session );
```
Hangs up active call
```
int msi_answer ( msi_session_t* _session, call_type _call_type );
```
Answer incomming call. _call_type set's callee call type.
```
int msi_cancel ( msi_session_t* _session );
```
Cancel current request.
```
int msi_reject ( msi_session_t* _session );
```
Reject incomming call.
###Now for rtp:
You will need 2 sessions; one for audio one for video.
You start them with:
```
rtp_session_t* rtp_init_session ( int _max_users, int _multi_session );
```
Params:
```
int _max_users - max users. -1 if undefined
int _multi_session - any positive number means uses multi session; -1 if not.
```
Return value:
```
rtp_session_t* - pointer to a newly created rtp session handler.
```
###How to handle rtp session:
Take a look at
```
void* phone_handle_media_transport_poll ( void* _hmtc_args_p ) in phone.c
```
on example. Basically what you do is just receive a message via:
```
struct rtp_msg_s* rtp_recv_msg ( rtp_session_t* _session );
```
and then you use payload within the rtp_msg_s struct. Don't forget to deallocate it with:
void rtp_free_msg ( rtp_session_t* _session, struct rtp_msg_s* _msg );
Receiving should be thread safe so don't worry about that.
When you capture and encode a payload you want to send it ( obviously ).
first create a new message with:
```
struct rtp_msg_s* rtp_msg_new ( rtp_session_t* _session, const uint8_t* _data, uint32_t _length );
```
and then send it with:
```
int rtp_send_msg ( rtp_session_t* _session, struct rtp_msg_s* _msg, void* _core_handler );
```
_core_handler is the same network handler as in msi_session_s struct.
##A/V initialization:
```
int init_receive_audio(codec_state *cs);
int init_receive_video(codec_state *cs);
Initialises the A/V decoders. On failure it will print the reason and return 0. On success it will return 1.
int init_send_audio(codec_state *cs);
int init_send_video(codec_state *cs);
Initialises the A/V encoders. On failure it will print the reason and return 0. On success it will return 1.
init_send_audio will also let the user select an input device. init_send_video will determine the webcam's output codec and initialise the appropriate decoder.
int video_encoder_refresh(codec_state *cs, int bps);
Reinitialises the video encoder with a new bitrate. ffmpeg does not expose the needed VP8 feature to change the bitrate on the fly, so this serves as a workaround.
In the future, VP8 should be used directly and ffmpeg should be dropped from the dependencies.
The variable bps is the required bitrate in bits per second.
```
###A/V encoding/decoding:
```
void *encode_video_thread(void *arg);
```
Spawns the video encoding thread. The argument should hold a pointer to a codec_state.
This function should only be called if video encoding is supported (when init_send_video returns 1).
Each video frame gets encoded into a packet, which is sent via RTP. Every 60 frames a new bidirectional interframe is encoded.
```
void *encode_audio_thread(void *arg);
```
Spawns the audio encoding thread. The argument should hold a pointer to a codec_state.
This function should only be called if audio encoding is supported (when init_send_audio returns 1).
Audio frames are read from the selected audio capture device during intitialisation. This audio capturing can be rerouted to a different device on the fly.
Each audio frame is encoded into a packet, and sent via RTP. All audio frames have the same amount of samples, which is defined in AV_codec.h.
```
int video_decoder_refresh(codec_state *cs, int width, int height);
```
Sets the SDL window dimensions and creates a pixel buffer with the requested size. It also creates a scaling context, which will be used to convert the input image format to YUV420P.
```
void *decode_video_thread(void *arg);
```
Spawns a video decoding thread. The argument should hold a pointer to a codec_state. The codec_state is assumed to contain a successfully initialised video decoder.
This function reads video packets and feeds them to the video decoder. If the video frame's resolution has changed, video_decoder_refresh() is called. Afterwards, the frame is displayed on the SDL window.
```
void *decode_audio_thread(void *arg);
```
Spawns an audio decoding thread. The argument should hold a pointer to a codec_state. The codec_state is assumed to contain a successfully initialised audio decoder.
All received audio packets are pushed into a jitter buffer and are reordered. If there is a missing packet, or a packet has arrived too late, it is treated as a lost packet and the audio decoder is informed of the packet loss. The audio decoder will then try to reconstruct the lost packet, based on information from previous packets.
Audio is played on the default OpenAL output device.
If you have any more qustions/bug reports/feature request contact the following users on the irc channel #tox-dev on irc.freenode.net:
For RTP and MSI: mannol
For audio and video: Martijnvdc
================================================
FILE: docs/updates/Crypto.md
================================================
Encryption library used: http://nacl.cr.yp.to/
When running the program for the first time the crypto_box_keypair() function is used to
generate the users public-private key pair. (32 bytes each)
The generated public key is set as the client_id of the peer.
Adding a friend
---------------
Alice adds Bob to her friend list by adding his 32 byte public key (client_id) to her friend list.
2 cases:
case 1: Alice adds the public key of Bob, then Bob waits for Alice to attempt to connect to him.
case 2: Bob and Alice add their respective public keys to their friend lists at the same time.
case 1:
Alice sends an onion data (see: Prevent_tracking.txt) packet to Bob with the encrypted part containing the friend request like so:
```
[char with a value of 32][nospam number (4 bytes)][Message]
```
Ex message: hello Bob it's me Alice -_- add me pl0x.
For more info on the nospam see: Spam_Prevention.txt
Bob receives the request and decrypts the message using the function crypto_box_open()
If the message decrypts successfully:
If Alice is already in Bob's friend list: case 2
If Alice is not in Bob's friend list and the nospam is good: Bob is prompt to add Alice and is shown the message from her.
If Bob accepts Alice friend request he adds her public key to his friend list.
case 2:
Bob and Alice both have the others public key in their friend list, they are ready for the next step: Connecting to an already added friend
In the next step only crypto_box() is used for encryption and only crypto_box_open() for decryption (just like in the last step.)
Connecting to an already added friend
-------------------------------------
see: Tox_middle_level_network_protocol.txt
Crypto request packets
--------------------------------------
```
[char with a value of 32][Bob (The reciever's) Public key (client_id) (32 bytes))][Alice's (The sender's) Public key (client_id) (32 bytes)][Random nonce (24 bytes)][Encrypted message]
```
The encrypted message is encrypted with crypto_box() (using Bob's public key, Alice's private key and the nonce (randomly generated 24 bytes)) and is a message from Alice in which she tells Bob who she is.
Each node can route the request to the receiver if they are connected to him. This is to bypass bad NATs.
================================================
FILE: docs/updates/DHT.md
================================================
DHT protocol
============
NOTE: only the protocol section is up to date, the rest needs to be rewritten.
Follows pretty much the principle of the torrent DHT: http://www.bittorrent.org/beps/bep_0005.html (READ IT)
But:
Vastly simplified packet format and encryption.
Boostrapping:
The first time you install the client we bootstrap it with a node. (bandwidth should not be a problem as the client only needs to be sent one reply.)
Basics
------
(All the numbers here are just guesses and are probably not optimal values)
client list: A list of node ids closest (mathematically see bittorrent doc) to ours matched with ip addresses + port number corresponding to that id and a timestamp containing the time or time since the client was successfully pinged.
"friends" list: A list containing the node_ids of all our "friends" or clients we want to connect to.
Also contains the ip addresses + port + node_ids + timestamp(of last ping like in the client list) of the 8 clients closest (mathematically see bittorrent doc) to each "friend"
One pinged lists:
-One for storing a list of ips along with their ping_ids and a timestamp for the ping requests
Entries in the pinged lists expire after 5 seconds.
If one of the lists becomes full, the expire rate reduces itself one second or the new ping takes the place of the oldest one.
Entries in client list and "friends" list expire after 300 seconds without ping response.
Each client stores a maximum of 32 entries in its client list.
Each client in the client list and "friends" list is pinged every 60 seconds.
Each client in the client list and "friends" list has a timestamp which denote the last time it was successfully pinged.
If the corresponding clients timestamp is more than 130 seconds old it is considered bad.
Send a get nodes request every 20 seconds to a random good node for each "friend" in our "friends" list.
Send a get nodes request every 20 seconds to a random good node in the client list.
When a client receives any request from another
-----------------------------------------------
- Respond to the request
- Ping request is replied to with with a ping response containing the same encrypted data
- Get nodes request is replied with a send nodes reply containing the same encrypted data and the good nodes from the client list and/or the "friends" list that are closest to the requested_node_id
- If the requesting client is not in the client list:
- If there are no bad clients in the list and the list is full:
- If the id of the other client is closer (mathematically see bittorrent doc) than at least one of the clients in the list or our "friends" list:
- Send a ping request to the client.
- if not forget about the client.
- If there are bad clients and/or the list isn't full:
- Send a ping request to the client
When a client receives a response
---------------------------------
- Ping response
- If the node was previously pinged with a matching ping_id (check in the corresponding pinged list.)
- If the node is in the client list the matching client's timestamp is set to current time.
- If the node is in the "friends" list the matching client's timestamp is set to current time for every occurrence.
- If the node is not in the client list:
- If the list isn't full, add it to the list.
- If the list is full, the furthest away (mathematically see bittorrent doc) bad client is replaced by the new one.
- If the list is filled with good nodes replace the furthest client with it only if it is closer than the replaced node.
- for each friend in the "friends" list:
- If that friend's client list isn't full, add that client to it
- If that friend's client list contains bad clients, replace the furthest one with that client.
- If that friend's client list contains only good clients
- If the client is closer to the friend than one of the other clients, it replaces the farthest one
- If not, nothing happens.
- Send nodes
- If the ping_id matches what we sent previously (check in the corresponding pinged list.):
- Each node in the response is pinged.
Protocol
--------
Node format:
```
[uint8_t family (2 == IPv4, 10 == IPv6, 130 == TCP IPv4, 138 == TCP IPv6)][ip (in network byte order), length=4 bytes if ipv4, 16 bytes if ipv6][port (in network byte order), length=2 bytes][char array (node_id), length=32 bytes]
```
see also: DHT.h (pack_nodes() and unpack_nodes())
Valid queries and Responses:
Ping(Request and response):
```
[byte with value: 00 for request, 01 for response][char array (client node_id), length=32 bytes][random 24 byte nonce][Encrypted with the nonce and private key of the sender: [1 byte type (0 for request, 1 for response)][random 8 byte (ping_id)]]
```
ping_id = a random integer, the response must contain the exact same number as the request
Get nodes (Request):
Packet contents:
```
[byte with value: 02][char array (client node_id), length=32 bytes][random 24 byte nonce][Encrypted with the nonce and private key of the sender:[char array: requested_node_id (node_id of which we want the ip), length=32 bytes][Sendback data (must be sent back unmodified by in the response), length=8 bytes]]
```
Valid replies: a send_nodes packet
Send_nodes (response (for all addresses)):
```
[byte with value: 04][char array (client node_id), length=32 bytes][random 24 byte nonce][Encrypted with the nonce and private key of the sender:[uint8_t number of nodes in this packet][Nodes in node format, length=?? * (number of nodes (maximum of 4 nodes)) bytes][Sendback data, length=8 bytes]]
```
================================================
FILE: docs/updates/Spam-Prevention.md
================================================
Situation 1:
Someone randomly goes around the DHT sending friend requests to everyone.
Prevented by:
Every friend address:
[client_id (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)]
contains a number (nospam).
The nospam in every friend request to that friend must be that number.
If not it is rejected.
================================================
FILE: docs/updates/Symmetric-NAT-Transversal.md
================================================
Notes:
Friend requests need to be routed.
The current DHT should be capable of punching all NATs except symmetric ones.
######
Symmetric NAT hole punching:
If we are not connected to the friend and if the DHT is queried and ips
returned for the friend are the same but the port is different, the friend is
assumed to be behind a symmetric NAT.
Before attempting the procedure we first send a routed ping request to the
friend. This request is to be routed through the nodes who returned the ip of
the peer.
As soon as we receive one routed ping request from the other peer, we respond
with a ping response.
Ping request/response packet:
See: Crypto request packets in [[Crypto]]
Message:
For the ping request:
[char with a value of 254][char with 0][8 byte random number]
For the ping response:
[char with a value of 254][char with 1][8 byte random number (The same that was sent in the request)]
As soon as we get a proper ping response from the other we run the different
ports returned by the DHT through our port guessing algorithm.
######
Port guessing algorithm:
Right now it just tries all the ports directly beside the known ports.(A better one is needed)
######
We send DHT ping requests to all the guessed ports, only a couple at a time.
================================================
FILE: libtoxav.pc.in
================================================
prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@
Name: libtoxav
Description: Tox A/V library
Requires: libtoxcore
Version: @PACKAGE_VERSION@
Libs: -L${libdir} -ltoxav @AV_LIBS@
Cflags: -I${includedir}
================================================
FILE: libtoxcore.pc.in
================================================
prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@
Name: libtoxcore
Description: Tox protocol library
Requires:
Version: @PACKAGE_VERSION@
Libs: @NACL_OBJECTS_PKGCONFIG@ -L${libdir} @NACL_LDFLAGS@ -ltoxdns -ltoxencryptsave -ltoxcore @NACL_LIBS@ @LIBS@ @MATH_LDFLAGS@ @PTHREAD_LDFLAGS@
Cflags: -I${includedir}
================================================
FILE: m4/pkg.m4
================================================
# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*-
# serial 1 (pkg-config-0.24)
#
# Copyright © 2004 Scott James Remnant .
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# As a special exception to the GNU General Public License, if you
# distribute this file as part of a program that contains a
# configuration script generated by Autoconf, you may include it under
# the same distribution terms that you use for the rest of that program.
# PKG_PROG_PKG_CONFIG([MIN-VERSION])
# ----------------------------------
AC_DEFUN([PKG_PROG_PKG_CONFIG],
[m4_pattern_forbid([^_?PKG_[A-Z_]+$])
m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$])
m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$])
AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])
AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path])
AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path])
if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
AC_PATH_TOOL([PKG_CONFIG], [pkg-config])
fi
if test -n "$PKG_CONFIG"; then
_pkg_min_version=m4_default([$1], [0.9.0])
AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version])
if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
AC_MSG_RESULT([yes])
else
AC_MSG_RESULT([no])
PKG_CONFIG=""
fi
fi[]dnl
])# PKG_PROG_PKG_CONFIG
# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
#
# Check to see whether a particular set of modules exists. Similar
# to PKG_CHECK_MODULES(), but does not set variables or print errors.
#
# Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG])
# only at the first occurrence in configure.ac, so if the first place
# it's called might be skipped (such as if it is within an "if", you
# have to call PKG_CHECK_EXISTS manually
# --------------------------------------------------------------
AC_DEFUN([PKG_CHECK_EXISTS],
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
if test -n "$PKG_CONFIG" && \
AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then
m4_default([$2], [:])
m4_ifvaln([$3], [else
$3])dnl
fi])
# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])
# ---------------------------------------------
m4_define([_PKG_CONFIG],
[if test -n "$$1"; then
pkg_cv_[]$1="$$1"
elif test -n "$PKG_CONFIG"; then
PKG_CHECK_EXISTS([$3],
[pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes ],
[pkg_failed=yes])
else
pkg_failed=untried
fi[]dnl
])# _PKG_CONFIG
# _PKG_SHORT_ERRORS_SUPPORTED
# -----------------------------
AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED],
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
_pkg_short_errors_supported=yes
else
_pkg_short_errors_supported=no
fi[]dnl
])# _PKG_SHORT_ERRORS_SUPPORTED
# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
# [ACTION-IF-NOT-FOUND])
#
#
# Note that if there is a possibility the first call to
# PKG_CHECK_MODULES might not happen, you should be sure to include an
# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac
#
#
# --------------------------------------------------------------
AC_DEFUN([PKG_CHECK_MODULES],
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl
AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl
pkg_failed=no
AC_MSG_CHECKING([for $1])
_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2])
_PKG_CONFIG([$1][_LIBS], [libs], [$2])
m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS
and $1[]_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.])
if test $pkg_failed = yes; then
AC_MSG_RESULT([no])
_PKG_SHORT_ERRORS_SUPPORTED
if test $_pkg_short_errors_supported = yes; then
$1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1`
else
$1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD
m4_default([$4], [AC_MSG_ERROR(
[Package requirements ($2) were not met:
$$1_PKG_ERRORS
Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.
_PKG_TEXT])[]dnl
])
elif test $pkg_failed = untried; then
AC_MSG_RESULT([no])
m4_default([$4], [AC_MSG_FAILURE(
[The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
_PKG_TEXT
To get pkg-config, see .])[]dnl
])
else
$1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS
$1[]_LIBS=$pkg_cv_[]$1[]_LIBS
AC_MSG_RESULT([yes])
$3
fi[]dnl
])# PKG_CHECK_MODULES
# PKG_INSTALLDIR(DIRECTORY)
# -------------------------
# Substitutes the variable pkgconfigdir as the location where a module
# should install pkg-config .pc files. By default the directory is
# $libdir/pkgconfig, but the default can be changed by passing
# DIRECTORY. The user can override through the --with-pkgconfigdir
# parameter.
AC_DEFUN([PKG_INSTALLDIR],
[m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])])
m4_pushdef([pkg_description],
[pkg-config installation directory @<:@]pkg_default[@:>@])
AC_ARG_WITH([pkgconfigdir],
[AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],,
[with_pkgconfigdir=]pkg_default)
AC_SUBST([pkgconfigdir], [$with_pkgconfigdir])
m4_popdef([pkg_default])
m4_popdef([pkg_description])
]) dnl PKG_INSTALLDIR
# PKG_NOARCH_INSTALLDIR(DIRECTORY)
# -------------------------
# Substitutes the variable noarch_pkgconfigdir as the location where a
# module should install arch-independent pkg-config .pc files. By
# default the directory is $datadir/pkgconfig, but the default can be
# changed by passing DIRECTORY. The user can override through the
# --with-noarch-pkgconfigdir parameter.
AC_DEFUN([PKG_NOARCH_INSTALLDIR],
[m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])])
m4_pushdef([pkg_description],
[pkg-config arch-independent installation directory @<:@]pkg_default[@:>@])
AC_ARG_WITH([noarch-pkgconfigdir],
[AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],,
[with_noarch_pkgconfigdir=]pkg_default)
AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir])
m4_popdef([pkg_default])
m4_popdef([pkg_description])
]) dnl PKG_NOARCH_INSTALLDIR
================================================
FILE: other/DHT_bootstrap.c
================================================
/* DHT boostrap
*
* A simple DHT boostrap node for tox.
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "../toxcore/DHT.h"
#include "../toxcore/LAN_discovery.h"
#include "../toxcore/friend_requests.h"
#include "../toxcore/util.h"
#define TCP_RELAY_ENABLED
#ifdef TCP_RELAY_ENABLED
#include "../toxcore/TCP_server.h"
#endif
#include "../testing/misc_tools.c"
#ifdef DHT_NODE_EXTRA_PACKETS
#include "./bootstrap_node_packets.h"
#define DHT_VERSION_NUMBER 1
#define DHT_MOTD "This is a test motd"
#endif
/* Sleep function (x = milliseconds) */
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
#define c_sleep(x) Sleep(1*x)
#else
#include
#include
#define c_sleep(x) usleep(1000*x)
#endif
#define PORT 33445
void manage_keys(DHT *dht)
{
const uint32_t KEYS_SIZE = crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES;
uint8_t keys[KEYS_SIZE];
FILE *keys_file = fopen("key", "r");
if (keys_file != NULL) {
/* If file was opened successfully -- load keys,
otherwise save new keys */
size_t read_size = fread(keys, sizeof(uint8_t), KEYS_SIZE, keys_file);
if (read_size != KEYS_SIZE) {
printf("Error while reading the key file\nExiting.\n");
exit(1);
}
memcpy(dht->self_public_key, keys, crypto_box_PUBLICKEYBYTES);
memcpy(dht->self_secret_key, keys + crypto_box_PUBLICKEYBYTES, crypto_box_SECRETKEYBYTES);
printf("Keys loaded successfully.\n");
} else {
memcpy(keys, dht->self_public_key, crypto_box_PUBLICKEYBYTES);
memcpy(keys + crypto_box_PUBLICKEYBYTES, dht->self_secret_key, crypto_box_SECRETKEYBYTES);
keys_file = fopen("key", "w");
if (keys_file == NULL) {
printf("Error opening key file in write mode.\nKeys will not be saved.\n");
return;
}
if (fwrite(keys, sizeof(uint8_t), KEYS_SIZE, keys_file) != KEYS_SIZE) {
printf("Error while writing the key file.\nExiting.\n");
exit(1);
}
printf("Keys saved successfully.\n");
}
fclose(keys_file);
}
int main(int argc, char *argv[])
{
if (argc == 2 && !strncasecmp(argv[1], "-h", 3)) {
printf("Usage (connected) : %s [--ipv4|--ipv6] IP PORT KEY\n", argv[0]);
printf("Usage (unconnected): %s [--ipv4|--ipv6]\n", argv[0]);
exit(0);
}
/* let user override default by cmdline */
uint8_t ipv6enabled = TOX_ENABLE_IPV6_DEFAULT; /* x */
int argvoffset = cmdline_parsefor_ipv46(argc, argv, &ipv6enabled);
if (argvoffset < 0)
exit(1);
/* Initialize networking -
Bind to ip 0.0.0.0 / [::] : PORT */
IP ip;
ip_init(&ip, ipv6enabled);
DHT *dht = new_DHT(new_networking(ip, PORT));
Onion *onion = new_onion(dht);
Onion_Announce *onion_a = new_onion_announce(dht);
#ifdef DHT_NODE_EXTRA_PACKETS
bootstrap_set_callbacks(dht->net, DHT_VERSION_NUMBER, DHT_MOTD, sizeof(DHT_MOTD));
#endif
if (!(onion && onion_a)) {
printf("Something failed to initialize.\n");
exit(1);
}
perror("Initialization");
manage_keys(dht);
printf("Public key: ");
uint32_t i;
#ifdef TCP_RELAY_ENABLED
#define NUM_PORTS 3
uint16_t ports[NUM_PORTS] = {443, 3389, PORT};
TCP_Server *tcp_s = new_TCP_server(ipv6enabled, NUM_PORTS, ports, dht->self_secret_key, onion);
if (tcp_s == NULL) {
printf("TCP server failed to initialize.\n");
exit(1);
}
#endif
FILE *file;
file = fopen("PUBLIC_ID.txt", "w");
for (i = 0; i < 32; i++) {
printf("%02hhX", dht->self_public_key[i]);
fprintf(file, "%02hhX", dht->self_public_key[i]);
}
fclose(file);
printf("\n");
printf("Port: %u\n", ntohs(dht->net->port));
if (argc > argvoffset + 3) {
printf("Trying to bootstrap into the network...\n");
uint16_t port = htons(atoi(argv[argvoffset + 2]));
uint8_t *bootstrap_key = hex_string_to_bin(argv[argvoffset + 3]);
int res = DHT_bootstrap_from_address(dht, argv[argvoffset + 1],
ipv6enabled, port, bootstrap_key);
free(bootstrap_key);
if (!res) {
printf("Failed to convert \"%s\" into an IP address. Exiting...\n", argv[argvoffset + 1]);
exit(1);
}
}
int is_waiting_for_dht_connection = 1;
uint64_t last_LANdiscovery = 0;
LANdiscovery_init(dht);
while (1) {
if (is_waiting_for_dht_connection && DHT_isconnected(dht)) {
printf("Connected to other bootstrap node successfully.\n");
is_waiting_for_dht_connection = 0;
}
do_DHT(dht);
if (is_timeout(last_LANdiscovery, is_waiting_for_dht_connection ? 5 : LAN_DISCOVERY_INTERVAL)) {
send_LANdiscovery(htons(PORT), dht);
last_LANdiscovery = unix_time();
}
#ifdef TCP_RELAY_ENABLED
do_TCP_server(tcp_s);
#endif
networking_poll(dht->net);
c_sleep(1);
}
return 0;
}
================================================
FILE: other/DHTnodes
================================================
As maintaining 2 separate lists of the same information seemed redundant, this list has been phased out.
For a current DHT node list please visit https://wiki.tox.chat/doku.php?id=users:nodes
================================================
FILE: other/Makefile.inc
================================================
bin_PROGRAMS += DHT_bootstrap
DHT_bootstrap_SOURCES = ../other/DHT_bootstrap.c \
../toxcore/DHT.h \
../toxcore/friend_requests.h \
../other/bootstrap_node_packets.h \
../other/bootstrap_node_packets.c
DHT_bootstrap_CFLAGS = -I$(top_srcdir)/other \
$(LIBSODIUM_CFLAGS) \
$(NACL_CFLAGS)
DHT_bootstrap_LDADD = $(LIBSODIUM_LDFLAGS) \
$(NACL_LDFLAGS) \
libtoxcore.la \
$(LIBSODIUM_LIBS) \
$(NACL_OBJECTS) \
$(NACL_LIBS) \
$(WINSOCK2_LIBS)
EXTRA_DIST += $(top_srcdir)/other/DHTnodes \
$(top_srcdir)/other/tox.png
================================================
FILE: other/apidsl/README.md
================================================
This folder contains the input file (``tox.in.h``) that has to be used to generate the ``tox.h`` api with: https://github.com/iphydf/apidsl
# Minimal requirements
There are some minimal requirements to contribute to ``tox.h``:
* unix environment
* ``astyle`` ``>=2.03``
* [``apidsl``](https://github.com/iphydf/apidsl) (you can use provided service with curl instead)
## Quick way
If you want to do it quickly and you don't have time for anything other than copypasting commands, you should have ``curl`` installed.
1. Make sure that you have ``curl`` and ``>=astyle-2.03`` installed
2. Modify [``tox.in.h``](/other/apidsl/tox.in.h)
3. Run command below ↓
Command to run from ``toxcore`` directory (quick way, involves using curl):
```bash
rm toxcore/tox.h && \
( curl -X POST --data-binary @- https://criticism.herokuapp.com/apidsl < ./other/apidsl/tox.in.h > ./toxcore/tox.h ) && \
astyle --options=./other/astyle/astylerc ./toxcore/tox.h
```
When formatting will be complete, you should see output like:
```
Formatted ./toxcore/tox.h
```
You may want to make sure with ``git diff`` that changes made in ``tox.h`` reflect changes in ``tox.in.h``.
And you're done.
## Manually
If you prefer to have more control over what is happening, there are steps below:
1. Install [``apidsl``](https://github.com/iphydf/apidsl)
2. Install ``astyle``, version 2.03 or later.
3. Modify [``tox.in.h``](/other/apidsl/tox.in.h)
4. Use ``apidsl`` ``??``
5. Parse generated ``tox.h`` with astyle, minimal command for it would be:
```bash
astyle --options=./other/astyle/astylerc ./toxcore/tox.h
```
**Always pass output from ``apidsl`` through astyle.**
================================================
FILE: other/apidsl/tox.in.h
================================================
%{
/* tox.h
*
* The Tox public API.
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef TOX_H
#define TOX_H
#include
#include
#include
#ifdef __cplusplus
extern "C" {
#endif
%}
/*****************************************************************************
* `tox.h` SHOULD *NOT* BE EDITED MANUALLY – any changes should be made to *
* `tox.in.h`, located in `other/apidsl/`. For instructions on how to *
* generate `tox.h` from `tox.in.h` please refer to `other/apidsl/README.md` *
*****************************************************************************/
/** \page core Public core API for Tox clients.
*
* Every function that can fail takes a function-specific error code pointer
* that can be used to diagnose problems with the Tox state or the function
* arguments. The error code pointer can be NULL, which does not influence the
* function's behaviour, but can be done if the reason for failure is irrelevant
* to the client.
*
* The exception to this rule are simple allocation functions whose only failure
* mode is allocation failure. They return NULL in that case, and do not set an
* error code.
*
* Every error code type has an OK value to which functions will set their error
* code value on success. Clients can keep their error code uninitialised before
* passing it to a function. The library guarantees that after returning, the
* value pointed to by the error code pointer has been initialised.
*
* Functions with pointer parameters often have a NULL error code, meaning they
* could not perform any operation, because one of the required parameters was
* NULL. Some functions operate correctly or are defined as effectless on NULL.
*
* Some functions additionally return a value outside their
* return type domain, or a bool containing true on success and false on
* failure.
*
* All functions that take a Tox instance pointer will cause undefined behaviour
* when passed a NULL Tox pointer.
*
* All integer values are expected in host byte order.
*
* Functions with parameters with enum types cause unspecified behaviour if the
* enumeration value is outside the valid range of the type. If possible, the
* function will try to use a sane default, but there will be no error code,
* and one possible action for the function to take is to have no effect.
*/
/** \subsection events Events and callbacks
*
* Events are handled by callbacks. One callback can be registered per event.
* All events have a callback function type named `tox_{event}_cb` and a
* function to register it named `tox_callback_{event}`. Passing a NULL
* callback will result in no callback being registered for that event. Only
* one callback per event can be registered, so if a client needs multiple
* event listeners, it needs to implement the dispatch functionality itself.
*/
/** \subsection threading Threading implications
*
* It is possible to run multiple concurrent threads with a Tox instance for
* each thread. It is also possible to run all Tox instances in the same thread.
* A common way to run Tox (multiple or single instance) is to have one thread
* running a simple ${tox.iterate} loop, sleeping for ${tox.iteration_interval}
* milliseconds on each iteration.
*
* If you want to access a single Tox instance from multiple threads, access
* to the instance must be synchronised. While multiple threads can concurrently
* access multiple different Tox instances, no more than one API function can
* operate on a single instance at any given time.
*
* Functions that write to variable length byte arrays will always have a size
* function associated with them. The result of this size function is only valid
* until another mutating function (one that takes a pointer to non-const Tox)
* is called. Thus, clients must ensure that no other thread calls a mutating
* function between the call to the size function and the call to the retrieval
* function.
*
* E.g. to get the current nickname, one would write
*
* \code
* size_t length = ${tox.self.name.size}(tox);
* uint8_t *name = malloc(length);
* if (!name) abort();
* ${tox.self.name.get}(tox, name);
* \endcode
*
* If any other thread calls ${tox.self.name.set} while this thread is allocating
* memory, the length may have become invalid, and the call to
* ${tox.self.name.get} may cause undefined behaviour.
*/
// The rest of this file is in class tox.
class tox {
/**
* The Tox instance type. All the state associated with a connection is held
* within the instance. Multiple instances can exist and operate concurrently.
* The maximum number of Tox instances that can exist on a single network
* device is limited. Note that this is not just a per-process limit, since the
* limiting factor is the number of usable ports on a device.
*/
struct this;
/*******************************************************************************
*
* :: API version
*
******************************************************************************/
/**
* The major version number. Incremented when the API or ABI changes in an
* incompatible way.
*/
#define TOX_VERSION_MAJOR 0u
/**
* The minor version number. Incremented when functionality is added without
* breaking the API or ABI. Set to 0 when the major version number is
* incremented.
*/
#define TOX_VERSION_MINOR 0u
/**
* The patch or revision number. Incremented when bugfixes are applied without
* changing any functionality or API or ABI.
*/
#define TOX_VERSION_PATCH 0u
/**
* A macro to check at preprocessing time whether the client code is compatible
* with the installed version of Tox.
*/
#define TOX_VERSION_IS_API_COMPATIBLE(MAJOR, MINOR, PATCH) \
(TOX_VERSION_MAJOR == MAJOR && \
(TOX_VERSION_MINOR > MINOR || \
(TOX_VERSION_MINOR == MINOR && \
TOX_VERSION_PATCH >= PATCH)))
/**
* A macro to make compilation fail if the client code is not compatible with
* the installed version of Tox.
*/
#define TOX_VERSION_REQUIRE(MAJOR, MINOR, PATCH) \
typedef char tox_required_version[TOX_IS_COMPATIBLE(MAJOR, MINOR, PATCH) ? 1 : -1]
static namespace version {
/**
* Return the major version number of the library. Can be used to display the
* Tox library version or to check whether the client is compatible with the
* dynamically linked version of Tox.
*/
uint32_t major();
/**
* Return the minor version number of the library.
*/
uint32_t minor();
/**
* Return the patch number of the library.
*/
uint32_t patch();
/**
* Return whether the compiled library version is compatible with the passed
* version numbers.
*/
bool is_compatible(uint32_t major, uint32_t minor, uint32_t patch);
}
/**
* A convenience macro to call tox_version_is_compatible with the currently
* compiling API version.
*/
#define TOX_VERSION_IS_ABI_COMPATIBLE() \
tox_version_is_compatible(TOX_VERSION_MAJOR, TOX_VERSION_MINOR, TOX_VERSION_PATCH)
/*******************************************************************************
*
* :: Numeric constants
*
******************************************************************************/
/**
* The size of a Tox Public Key in bytes.
*/
const PUBLIC_KEY_SIZE = 32;
/**
* The size of a Tox Secret Key in bytes.
*/
const SECRET_KEY_SIZE = 32;
/**
* The size of a Tox address in bytes. Tox addresses are in the format
* [Public Key ($PUBLIC_KEY_SIZE bytes)][nospam (4 bytes)][checksum (2 bytes)].
*
* The checksum is computed over the Public Key and the nospam value. The first
* byte is an XOR of all the even bytes (0, 2, 4, ...), the second byte is an
* XOR of all the odd bytes (1, 3, 5, ...) of the Public Key and nospam.
*/
const ADDRESS_SIZE = PUBLIC_KEY_SIZE + sizeof(uint32_t) + sizeof(uint16_t);
/**
* Maximum length of a nickname in bytes.
*/
const MAX_NAME_LENGTH = 128;
/**
* Maximum length of a status message in bytes.
*/
const MAX_STATUS_MESSAGE_LENGTH = 1007;
/**
* Maximum length of a friend request message in bytes.
*/
const MAX_FRIEND_REQUEST_LENGTH = 1016;
/**
* Maximum length of a single message after which it should be split.
*/
const MAX_MESSAGE_LENGTH = 1372;
/**
* Maximum size of custom packets. TODO: should be LENGTH?
*/
const MAX_CUSTOM_PACKET_SIZE = 1373;
/**
* The number of bytes in a hash generated by $hash.
*/
const HASH_LENGTH = 32;
/**
* The number of bytes in a file id.
*/
const FILE_ID_LENGTH = 32;
/**
* Maximum file name length for file transfers.
*/
const MAX_FILENAME_LENGTH = 255;
/*******************************************************************************
*
* :: Global enumerations
*
******************************************************************************/
/**
* Represents the possible statuses a client can have.
*/
enum class USER_STATUS {
/**
* User is online and available.
*/
NONE,
/**
* User is away. Clients can set this e.g. after a user defined
* inactivity time.
*/
AWAY,
/**
* User is busy. Signals to other clients that this client does not
* currently wish to communicate.
*/
BUSY,
}
/**
* Represents message types for ${tox.friend.send.message} and group chat
* messages.
*/
enum class MESSAGE_TYPE {
/**
* Normal text message. Similar to PRIVMSG on IRC.
*/
NORMAL,
/**
* A message describing an user action. This is similar to /me (CTCP ACTION)
* on IRC.
*/
ACTION,
}
/*******************************************************************************
*
* :: Startup options
*
******************************************************************************/
/**
* Type of proxy used to connect to TCP relays.
*/
enum class PROXY_TYPE {
/**
* Don't use a proxy.
*/
NONE,
/**
* HTTP proxy using CONNECT.
*/
HTTP,
/**
* SOCKS proxy for simple socket pipes.
*/
SOCKS5,
}
/**
* Type of savedata to create the Tox instance from.
*/
enum class SAVEDATA_TYPE {
/**
* No savedata.
*/
NONE,
/**
* Savedata is one that was obtained from ${savedata.get}
*/
TOX_SAVE,
/**
* Savedata is a secret key of length ${SECRET_KEY_SIZE}
*/
SECRET_KEY,
}
static class options {
/**
* This struct contains all the startup options for Tox. You can either allocate
* this object yourself, and pass it to $default, or call
* $new to get a new default options object.
*/
struct this {
/**
* The type of socket to create.
*
* If this is set to false, an IPv4 socket is created, which subsequently
* only allows IPv4 communication.
* If it is set to true, an IPv6 socket is created, allowing both IPv4 and
* IPv6 communication.
*/
bool ipv6_enabled;
/**
* Enable the use of UDP communication when available.
*
* Setting this to false will force Tox to use TCP only. Communications will
* need to be relayed through a TCP relay node, potentially slowing them down.
* Disabling UDP support is necessary when using anonymous proxies or Tor.
*/
bool udp_enabled;
namespace proxy {
/**
* Pass communications through a proxy.
*/
PROXY_TYPE type;
/**
* The IP address or DNS name of the proxy to be used.
*
* If used, this must be non-NULL and be a valid DNS name. The name must not
* exceed 255 characters, and be in a NUL-terminated C string format
* (255 chars + 1 NUL byte).
*
* This member is ignored (it can be NULL) if proxy_type is TOX_PROXY_TYPE_NONE.
*/
string host;
/**
* The port to use to connect to the proxy server.
*
* Ports must be in the range (1, 65535). The value is ignored if
* proxy_type is TOX_PROXY_TYPE_NONE.
*/
uint16_t port;
}
/**
* The start port of the inclusive port range to attempt to use.
*
* If both start_port and end_port are 0, the default port range will be
* used: [33445, 33545].
*
* If either start_port or end_port is 0 while the other is non-zero, the
* non-zero port will be the only port in the range.
*
* Having start_port > end_port will yield the same behavior as if start_port
* and end_port were swapped.
*/
uint16_t start_port;
/**
* The end port of the inclusive port range to attempt to use.
*/
uint16_t end_port;
/**
* The port to use for the TCP server (relay). If 0, the TCP server is
* disabled.
*
* Enabling it is not required for Tox to function properly.
*
* When enabled, your Tox instance can act as a TCP relay for other Tox
* instance. This leads to increased traffic, thus when writing a client
* it is recommended to enable TCP server only if the user has an option
* to disable it.
*/
uint16_t tcp_port;
namespace savedata {
/**
* The type of savedata to load from.
*/
SAVEDATA_TYPE type;
/**
* The savedata.
*/
const uint8_t[length] data;
/**
* The length of the savedata.
*/
size_t length;
}
}
/**
* Initialises a $this object with the default options.
*
* The result of this function is independent of the original options. All
* values will be overwritten, no values will be read (so it is permissible
* to pass an uninitialised object).
*
* If options is NULL, this function has no effect.
*
* @param options An options object to be filled with default options.
*/
void default();
/**
* Allocates a new $this object and initialises it with the default
* options. This function can be used to preserve long term ABI compatibility by
* giving the responsibility of allocation and deallocation to the Tox library.
*
* Objects returned from this function must be freed using the $free
* function.
*
* @return A new $this object with default options or NULL on failure.
*/
static this new() {
/**
* The function failed to allocate enough memory for the options struct.
*/
MALLOC,
}
/**
* Releases all resources associated with an options objects.
*
* Passing a pointer that was not returned by $new results in
* undefined behaviour.
*/
void free();
}
/*******************************************************************************
*
* :: Creation and destruction
*
******************************************************************************/
/**
* @brief Creates and initialises a new Tox instance with the options passed.
*
* This function will bring the instance into a valid state. Running the event
* loop with a new instance will operate correctly.
*
* If loading failed or succeeded only partially, the new or partially loaded
* instance is returned and an error code is set.
*
* @param options An options object as described above. If this parameter is
* NULL, the default options are used.
*
* @see $iterate for the event loop.
*
* @return A new Tox instance pointer on success or NULL on failure.
*/
static this new(const options_t *options) {
NULL,
/**
* The function was unable to allocate enough memory to store the internal
* structures for the Tox object.
*/
MALLOC,
/**
* The function was unable to bind to a port. This may mean that all ports
* have already been bound, e.g. by other Tox instances, or it may mean
* a permission error. You may be able to gather more information from errno.
*/
PORT_ALLOC,
namespace PROXY {
/**
* proxy_type was invalid.
*/
BAD_TYPE,
/**
* proxy_type was valid but the proxy_host passed had an invalid format
* or was NULL.
*/
BAD_HOST,
/**
* proxy_type was valid, but the proxy_port was invalid.
*/
BAD_PORT,
/**
* The proxy address passed could not be resolved.
*/
NOT_FOUND,
}
namespace LOAD {
/**
* The byte array to be loaded contained an encrypted save.
*/
ENCRYPTED,
/**
* The data format was invalid. This can happen when loading data that was
* saved by an older version of Tox, or when the data has been corrupted.
* When loading from badly formatted data, some data may have been loaded,
* and the rest is discarded. Passing an invalid length parameter also
* causes this error.
*/
BAD_FORMAT,
}
}
/**
* Releases all resources associated with the Tox instance and disconnects from
* the network.
*
* After calling this function, the Tox pointer becomes invalid. No other
* functions can be called, and the pointer value can no longer be read.
*/
void kill();
uint8_t[size] savedata {
/**
* Calculates the number of bytes required to store the tox instance with
* $get. This function cannot fail. The result is always greater than 0.
*
* @see threading for concurrency implications.
*/
size();
/**
* Store all information associated with the tox instance to a byte array.
*
* @param data A memory region large enough to store the tox instance data.
* Call $size to find the number of bytes required. If this parameter
* is NULL, this function has no effect.
*/
get();
}
/*******************************************************************************
*
* :: Connection lifecycle and event loop
*
******************************************************************************/
/**
* Sends a "get nodes" request to the given bootstrap node with IP, port, and
* public key to setup connections.
*
* This function will attempt to connect to the node using UDP. You must use
* this function even if ${options.this.udp_enabled} was set to false.
*
* @param address The hostname or IP address (IPv4 or IPv6) of the node.
* @param port The port on the host on which the bootstrap Tox instance is
* listening.
* @param public_key The long term public key of the bootstrap node
* ($PUBLIC_KEY_SIZE bytes).
* @return true on success.
*/
bool bootstrap(string address, uint16_t port, const uint8_t[PUBLIC_KEY_SIZE] public_key) {
NULL,
/**
* The address could not be resolved to an IP address, or the IP address
* passed was invalid.
*/
BAD_HOST,
/**
* The port passed was invalid. The valid port range is (1, 65535).
*/
BAD_PORT,
}
/**
* Adds additional host:port pair as TCP relay.
*
* This function can be used to initiate TCP connections to different ports on
* the same bootstrap node, or to add TCP relays without using them as
* bootstrap nodes.
*
* @param address The hostname or IP address (IPv4 or IPv6) of the TCP relay.
* @param port The port on the host on which the TCP relay is listening.
* @param public_key The long term public key of the TCP relay
* ($PUBLIC_KEY_SIZE bytes).
* @return true on success.
*/
bool add_tcp_relay(string address, uint16_t port, const uint8_t[PUBLIC_KEY_SIZE] public_key)
with error for bootstrap;
/**
* Protocols that can be used to connect to the network or friends.
*/
enum class CONNECTION {
/**
* There is no connection. This instance, or the friend the state change is
* about, is now offline.
*/
NONE,
/**
* A TCP connection has been established. For the own instance, this means it
* is connected through a TCP relay, only. For a friend, this means that the
* connection to that particular friend goes through a TCP relay.
*/
TCP,
/**
* A UDP connection has been established. For the own instance, this means it
* is able to send UDP packets to DHT nodes, but may still be connected to
* a TCP relay. For a friend, this means that the connection to that
* particular friend was built using direct UDP packets.
*/
UDP,
}
inline namespace self {
CONNECTION connection_status {
/**
* Return whether we are connected to the DHT. The return value is equal to the
* last value received through the `${event connection_status}` callback.
*/
get();
}
/**
* This event is triggered whenever there is a change in the DHT connection
* state. When disconnected, a client may choose to call $bootstrap again, to
* reconnect to the DHT. Note that this state may frequently change for short
* amounts of time. Clients should therefore not immediately bootstrap on
* receiving a disconnect.
*
* TODO: how long should a client wait before bootstrapping again?
*/
event connection_status {
/**
* @param connection_status Whether we are connected to the DHT.
*/
typedef void(CONNECTION connection_status);
}
}
/**
* Return the time in milliseconds before $iterate() should be called again
* for optimal performance.
*/
const uint32_t iteration_interval();
/**
* The main loop that needs to be run in intervals of $iteration_interval()
* milliseconds.
*/
void iterate();
/*******************************************************************************
*
* :: Internal client information (Tox address/id)
*
******************************************************************************/
inline namespace self {
uint8_t[ADDRESS_SIZE] address {
/**
* Writes the Tox friend address of the client to a byte array. The address is
* not in human-readable format. If a client wants to display the address,
* formatting is required.
*
* @param address A memory region of at least $ADDRESS_SIZE bytes. If this
* parameter is NULL, this function has no effect.
* @see $ADDRESS_SIZE for the address format.
*/
get();
}
uint32_t nospam {
/**
* Set the 4-byte nospam part of the address.
*
* @param nospam Any 32 bit unsigned integer.
*/
set();
/**
* Get the 4-byte nospam part of the address.
*/
get();
}
uint8_t[PUBLIC_KEY_SIZE] public_key {
/**
* Copy the Tox Public Key (long term) from the Tox object.
*
* @param public_key A memory region of at least $PUBLIC_KEY_SIZE bytes. If
* this parameter is NULL, this function has no effect.
*/
get();
}
uint8_t[SECRET_KEY_SIZE] secret_key {
/**
* Copy the Tox Secret Key from the Tox object.
*
* @param secret_key A memory region of at least $SECRET_KEY_SIZE bytes. If
* this parameter is NULL, this function has no effect.
*/
get();
}
}
/*******************************************************************************
*
* :: User-visible client information (nickname/status)
*
******************************************************************************/
/**
* Common error codes for all functions that set a piece of user-visible
* client information.
*/
error for set_info {
NULL,
/**
* Information length exceeded maximum permissible size.
*/
TOO_LONG,
}
inline namespace self {
uint8_t[length <= MAX_NAME_LENGTH] name {
/**
* Set the nickname for the Tox client.
*
* Nickname length cannot exceed $MAX_NAME_LENGTH. If length is 0, the name
* parameter is ignored (it can be NULL), and the nickname is set back to empty.
*
* @param name A byte array containing the new nickname.
* @param length The size of the name byte array.
*
* @return true on success.
*/
set() with error for set_info;
/**
* Return the length of the current nickname as passed to $set.
*
* If no nickname was set before calling this function, the name is empty,
* and this function returns 0.
*
* @see threading for concurrency implications.
*/
size();
/**
* Write the nickname set by $set to a byte array.
*
* If no nickname was set before calling this function, the name is empty,
* and this function has no effect.
*
* Call $size to find out how much memory to allocate for
* the result.
*
* @param name A valid memory location large enough to hold the nickname.
* If this parameter is NULL, the function has no effect.
*/
get();
}
uint8_t[length <= MAX_STATUS_MESSAGE_LENGTH] status_message {
/**
* Set the client's status message.
*
* Status message length cannot exceed $MAX_STATUS_MESSAGE_LENGTH. If
* length is 0, the status parameter is ignored (it can be NULL), and the
* user status is set back to empty.
*/
set() with error for set_info;
/**
* Return the length of the current status message as passed to $set.
*
* If no status message was set before calling this function, the status
* is empty, and this function returns 0.
*
* @see threading for concurrency implications.
*/
size();
/**
* Write the status message set by $set to a byte array.
*
* If no status message was set before calling this function, the status is
* empty, and this function has no effect.
*
* Call $size to find out how much memory to allocate for
* the result.
*
* @param status A valid memory location large enough to hold the status message.
* If this parameter is NULL, the function has no effect.
*/
get();
}
USER_STATUS status {
/**
* Set the client's user status.
*
* @param user_status One of the user statuses listed in the enumeration above.
*/
set();
/**
* Returns the client's user status.
*/
get();
}
}
/*******************************************************************************
*
* :: Friend list management
*
******************************************************************************/
namespace friend {
/**
* Add a friend to the friend list and send a friend request.
*
* A friend request message must be at least 1 byte long and at most
* $MAX_FRIEND_REQUEST_LENGTH.
*
* Friend numbers are unique identifiers used in all functions that operate on
* friends. Once added, a friend number is stable for the lifetime of the Tox
* object. After saving the state and reloading it, the friend numbers may not
* be the same as before. Deleting a friend creates a gap in the friend number
* set, which is filled by the next adding of a friend. Any pattern in friend
* numbers should not be relied on.
*
* If more than INT32_MAX friends are added, this function causes undefined
* behaviour.
*
* @param address The address of the friend (returned by ${self.address.get} of
* the friend you wish to add) it must be $ADDRESS_SIZE bytes.
* @param message The message that will be sent along with the friend request.
* @param length The length of the data byte array.
*
* @return the friend number on success, UINT32_MAX on failure.
*/
uint32_t add(
const uint8_t[ADDRESS_SIZE] address,
const uint8_t[length <= MAX_FRIEND_REQUEST_LENGTH] message
) {
NULL,
/**
* The length of the friend request message exceeded
* $MAX_FRIEND_REQUEST_LENGTH.
*/
TOO_LONG,
/**
* The friend request message was empty. This, and the TOO_LONG code will
* never be returned from $add_norequest.
*/
NO_MESSAGE,
/**
* The friend address belongs to the sending client.
*/
OWN_KEY,
/**
* A friend request has already been sent, or the address belongs to a friend
* that is already on the friend list.
*/
ALREADY_SENT,
/**
* The friend address checksum failed.
*/
BAD_CHECKSUM,
/**
* The friend was already there, but the nospam value was different.
*/
SET_NEW_NOSPAM,
/**
* A memory allocation failed when trying to increase the friend list size.
*/
MALLOC,
}
/**
* Add a friend without sending a friend request.
*
* This function is used to add a friend in response to a friend request. If the
* client receives a friend request, it can be reasonably sure that the other
* client added this client as a friend, eliminating the need for a friend
* request.
*
* This function is also useful in a situation where both instances are
* controlled by the same entity, so that this entity can perform the mutual
* friend adding. In this case, there is no need for a friend request, either.
*
* @param public_key A byte array of length $PUBLIC_KEY_SIZE containing the
* Public Key (not the Address) of the friend to add.
*
* @return the friend number on success, UINT32_MAX on failure.
* @see $add for a more detailed description of friend numbers.
*/
uint32_t add_norequest(const uint8_t[PUBLIC_KEY_SIZE] public_key)
with error for add;
/**
* Remove a friend from the friend list.
*
* This does not notify the friend of their deletion. After calling this
* function, this client will appear offline to the friend and no communication
* can occur between the two.
*
* @param friend_number Friend number for the friend to be deleted.
*
* @return true on success.
*/
bool delete(uint32_t friend_number) {
/**
* There was no friend with the given friend number. No friends were deleted.
*/
FRIEND_NOT_FOUND,
}
}
/*******************************************************************************
*
* :: Friend list queries
*
******************************************************************************/
namespace friend {
/**
* Return the friend number associated with that Public Key.
*
* @return the friend number on success, UINT32_MAX on failure.
* @param public_key A byte array containing the Public Key.
*/
const uint32_t by_public_key(const uint8_t[PUBLIC_KEY_SIZE] public_key) {
NULL,
/**
* No friend with the given Public Key exists on the friend list.
*/
NOT_FOUND,
}
/**
* Checks if a friend with the given friend number exists and returns true if
* it does.
*/
const bool exists(uint32_t friend_number);
}
inline namespace self {
uint32_t[size] friend_list {
/**
* Return the number of friends on the friend list.
*
* This function can be used to determine how much memory to allocate for
* $get.
*/
size();
/**
* Copy a list of valid friend numbers into an array.
*
* Call $size to determine the number of elements to allocate.
*
* @param list A memory region with enough space to hold the friend list. If
* this parameter is NULL, this function has no effect.
*/
get();
}
}
namespace friend {
uint8_t[PUBLIC_KEY_SIZE] public_key {
/**
* Copies the Public Key associated with a given friend number to a byte array.
*
* @param friend_number The friend number you want the Public Key of.
* @param public_key A memory region of at least $PUBLIC_KEY_SIZE bytes. If
* this parameter is NULL, this function has no effect.
*
* @return true on success.
*/
get(uint32_t friend_number) {
/**
* No friend with the given number exists on the friend list.
*/
FRIEND_NOT_FOUND,
}
}
}
namespace friend {
uint64_t last_online {
/**
* Return a unix-time timestamp of the last time the friend associated with a given
* friend number was seen online. This function will return UINT64_MAX on error.
*
* @param friend_number The friend number you want to query.
*/
get(uint32_t friend_number) {
/**
* No friend with the given number exists on the friend list.
*/
FRIEND_NOT_FOUND,
}
}
}
/*******************************************************************************
*
* :: Friend-specific state queries (can also be received through callbacks)
*
******************************************************************************/
namespace friend {
/**
* Common error codes for friend state query functions.
*/
error for query {
/**
* The pointer parameter for storing the query result (name, message) was
* NULL. Unlike the `_self_` variants of these functions, which have no effect
* when a parameter is NULL, these functions return an error in that case.
*/
NULL,
/**
* The friend_number did not designate a valid friend.
*/
FRIEND_NOT_FOUND,
}
uint8_t[length <= MAX_NAME_LENGTH] name {
/**
* Return the length of the friend's name. If the friend number is invalid, the
* return value is unspecified.
*
* The return value is equal to the `length` argument received by the last
* `${event name}` callback.
*/
size(uint32_t friend_number)
with error for query;
/**
* Write the name of the friend designated by the given friend number to a byte
* array.
*
* Call $size to determine the allocation size for the `name`
* parameter.
*
* The data written to `name` is equal to the data received by the last
* `${event name}` callback.
*
* @param name A valid memory region large enough to store the friend's name.
*
* @return true on success.
*/
get(uint32_t friend_number)
with error for query;
}
/**
* This event is triggered when a friend changes their name.
*/
event name {
/**
* @param friend_number The friend number of the friend whose name changed.
* @param name A byte array containing the same data as
* ${name.get} would write to its `name` parameter.
* @param length A value equal to the return value of
* ${name.size}.
*/
typedef void(uint32_t friend_number, const uint8_t[length <= MAX_NAME_LENGTH] name);
}
uint8_t[length <= MAX_STATUS_MESSAGE_LENGTH] status_message {
/**
* Return the length of the friend's status message. If the friend number is
* invalid, the return value is SIZE_MAX.
*/
size(uint32_t friend_number)
with error for query;
/**
* Write the status message of the friend designated by the given friend number to a byte
* array.
*
* Call $size to determine the allocation size for the `status_name`
* parameter.
*
* The data written to `status_message` is equal to the data received by the last
* `${event status_message}` callback.
*
* @param status_message A valid memory region large enough to store the friend's status message.
*/
get(uint32_t friend_number)
with error for query;
}
/**
* This event is triggered when a friend changes their status message.
*/
event status_message {
/**
* @param friend_number The friend number of the friend whose status message
* changed.
* @param message A byte array containing the same data as
* ${status_message.get} would write to its `status_message` parameter.
* @param length A value equal to the return value of
* ${status_message.size}.
*/
typedef void(uint32_t friend_number, const uint8_t[length <= MAX_STATUS_MESSAGE_LENGTH] message);
}
USER_STATUS status {
/**
* Return the friend's user status (away/busy/...). If the friend number is
* invalid, the return value is unspecified.
*
* The status returned is equal to the last status received through the
* `${event status}` callback.
*/
get(uint32_t friend_number)
with error for query;
}
/**
* This event is triggered when a friend changes their user status.
*/
event status {
/**
* @param friend_number The friend number of the friend whose user status
* changed.
* @param status The new user status.
*/
typedef void(uint32_t friend_number, USER_STATUS status);
}
CONNECTION connection_status {
/**
* Check whether a friend is currently connected to this client.
*
* The result of this function is equal to the last value received by the
* `${event connection_status}` callback.
*
* @param friend_number The friend number for which to query the connection
* status.
*
* @return the friend's connection status as it was received through the
* `${event connection_status}` event.
*/
get(uint32_t friend_number)
with error for query;
}
/**
* This event is triggered when a friend goes offline after having been online,
* or when a friend goes online.
*
* This callback is not called when adding friends. It is assumed that when
* adding friends, their connection status is initially offline.
*/
event connection_status {
/**
* @param friend_number The friend number of the friend whose connection status
* changed.
* @param connection_status The result of calling
* ${connection_status.get} on the passed friend_number.
*/
typedef void(uint32_t friend_number, CONNECTION connection_status);
}
bool typing {
/**
* Check whether a friend is currently typing a message.
*
* @param friend_number The friend number for which to query the typing status.
*
* @return true if the friend is typing.
* @return false if the friend is not typing, or the friend number was
* invalid. Inspect the error code to determine which case it is.
*/
get(uint32_t friend_number)
with error for query;
}
/**
* This event is triggered when a friend starts or stops typing.
*/
event typing {
/**
* @param friend_number The friend number of the friend who started or stopped
* typing.
* @param is_typing The result of calling ${typing.get} on the passed
* friend_number.
*/
typedef void(uint32_t friend_number, bool is_typing);
}
}
/*******************************************************************************
*
* :: Sending private messages
*
******************************************************************************/
inline namespace self {
bool typing {
/**
* Set the client's typing status for a friend.
*
* The client is responsible for turning it on or off.
*
* @param friend_number The friend to which the client is typing a message.
* @param typing The typing status. True means the client is typing.
*
* @return true on success.
*/
set(uint32_t friend_number) {
/**
* The friend number did not designate a valid friend.
*/
FRIEND_NOT_FOUND,
}
}
}
namespace friend {
namespace send {
/**
* Send a text chat message to an online friend.
*
* This function creates a chat message packet and pushes it into the send
* queue.
*
* The message length may not exceed $MAX_MESSAGE_LENGTH. Larger messages
* must be split by the client and sent as separate messages. Other clients can
* then reassemble the fragments. Messages may not be empty.
*
* The return value of this function is the message ID. If a read receipt is
* received, the triggered `${event read_receipt}` event will be passed this message ID.
*
* Message IDs are unique per friend. The first message ID is 0. Message IDs are
* incremented by 1 each time a message is sent. If UINT32_MAX messages were
* sent, the next message ID is 0.
*
* @param type Message type (normal, action, ...).
* @param friend_number The friend number of the friend to send the message to.
* @param message A non-NULL pointer to the first element of a byte array
* containing the message text.
* @param length Length of the message to be sent.
*/
uint32_t message(uint32_t friend_number, MESSAGE_TYPE type, const uint8_t[length <= MAX_MESSAGE_LENGTH] message) {
NULL,
/**
* The friend number did not designate a valid friend.
*/
FRIEND_NOT_FOUND,
/**
* This client is currently not connected to the friend.
*/
FRIEND_NOT_CONNECTED,
/**
* An allocation error occurred while increasing the send queue size.
*/
SENDQ,
/**
* Message length exceeded $MAX_MESSAGE_LENGTH.
*/
TOO_LONG,
/**
* Attempted to send a zero-length message.
*/
EMPTY,
}
}
/**
* This event is triggered when the friend receives the message sent with
* ${send.message} with the corresponding message ID.
*/
event read_receipt {
/**
* @param friend_number The friend number of the friend who received the message.
* @param message_id The message ID as returned from ${send.message}
* corresponding to the message sent.
*/
typedef void(uint32_t friend_number, uint32_t message_id);
}
}
/*******************************************************************************
*
* :: Receiving private messages and friend requests
*
******************************************************************************/
namespace friend {
/**
* This event is triggered when a friend request is received.
*/
event request {
/**
* @param public_key The Public Key of the user who sent the friend request.
* @param time_delta A delta in seconds between when the message was composed
* and when it is being transmitted. For messages that are sent immediately,
* it will be 0. If a message was written and couldn't be sent immediately
* (due to a connection failure, for example), the time_delta is an
* approximation of when it was composed.
* @param message The message they sent along with the request.
* @param length The size of the message byte array.
*/
typedef void(const uint8_t[PUBLIC_KEY_SIZE] public_key
//, uint32_t time_delta
, const uint8_t[length <= MAX_MESSAGE_LENGTH] message);
}
/**
* This event is triggered when a message from a friend is received.
*/
event message {
/**
* @param friend_number The friend number of the friend who sent the message.
* @param time_delta Time between composition and sending.
* @param message The message data they sent.
* @param length The size of the message byte array.
*
* @see ${event request} for more information on time_delta.
*/
typedef void(uint32_t friend_number
//, uint32_t time_delta
, MESSAGE_TYPE type, const uint8_t[length <= MAX_MESSAGE_LENGTH] message);
}
}
/*******************************************************************************
*
* :: File transmission: common between sending and receiving
*
******************************************************************************/
/**
* Generates a cryptographic hash of the given data.
*
* This function may be used by clients for any purpose, but is provided
* primarily for validating cached avatars. This use is highly recommended to
* avoid unnecessary avatar updates.
*
* If hash is NULL or data is NULL while length is not 0 the function returns false,
* otherwise it returns true.
*
* This function is a wrapper to internal message-digest functions.
*
* @param hash A valid memory location the hash data. It must be at least
* TOX_HASH_LENGTH bytes in size.
* @param data Data to be hashed or NULL.
* @param length Size of the data array or 0.
*
* @return true if hash was not NULL.
*/
static bool hash(uint8_t[HASH_LENGTH] hash, const uint8_t[length] data);
namespace file {
enum KIND {
/**
* Arbitrary file data. Clients can choose to handle it based on the file name
* or magic or any other way they choose.
*/
DATA,
/**
* Avatar file_id. This consists of $hash(image).
* Avatar data. This consists of the image data.
*
* Avatars can be sent at any time the client wishes. Generally, a client will
* send the avatar to a friend when that friend comes online, and to all
* friends when the avatar changed. A client can save some traffic by
* remembering which friend received the updated avatar already and only send
* it if the friend has an out of date avatar.
*
* Clients who receive avatar send requests can reject it (by sending
* ${CONTROL.CANCEL} before any other controls), or accept it (by
* sending ${CONTROL.RESUME}). The file_id of length $HASH_LENGTH bytes
* (same length as $FILE_ID_LENGTH) will contain the hash. A client can compare
* this hash with a saved hash and send ${CONTROL.CANCEL} to terminate the avatar
* transfer if it matches.
*
* When file_size is set to 0 in the transfer request it means that the client
* has no avatar.
*/
AVATAR,
}
enum class CONTROL {
/**
* Sent by the receiving side to accept a file send request. Also sent after a
* $PAUSE command to continue sending or receiving.
*/
RESUME,
/**
* Sent by clients to pause the file transfer. The initial state of a file
* transfer is always paused on the receiving side and running on the sending
* side. If both the sending and receiving side pause the transfer, then both
* need to send $RESUME for the transfer to resume.
*/
PAUSE,
/**
* Sent by the receiving side to reject a file send request before any other
* commands are sent. Also sent by either side to terminate a file transfer.
*/
CANCEL,
}
/**
* Sends a file control command to a friend for a given file transfer.
*
* @param friend_number The friend number of the friend the file is being
* transferred to or received from.
* @param file_number The friend-specific identifier for the file transfer.
* @param control The control command to send.
*
* @return true on success.
*/
bool control(uint32_t friend_number, uint32_t file_number, CONTROL control) {
/**
* The friend_number passed did not designate a valid friend.
*/
FRIEND_NOT_FOUND,
/**
* This client is currently not connected to the friend.
*/
FRIEND_NOT_CONNECTED,
/**
* No file transfer with the given file number was found for the given friend.
*/
NOT_FOUND,
/**
* A RESUME control was sent, but the file transfer is running normally.
*/
NOT_PAUSED,
/**
* A RESUME control was sent, but the file transfer was paused by the other
* party. Only the party that paused the transfer can resume it.
*/
DENIED,
/**
* A PAUSE control was sent, but the file transfer was already paused.
*/
ALREADY_PAUSED,
/**
* Packet queue is full.
*/
SENDQ,
}
/**
* This event is triggered when a file control command is received from a
* friend.
*/
event recv_control {
/**
* When receiving ${CONTROL.CANCEL}, the client should release the
* resources associated with the file number and consider the transfer failed.
*
* @param friend_number The friend number of the friend who is sending the file.
* @param file_number The friend-specific file number the data received is
* associated with.
* @param control The file control command received.
*/
typedef void(uint32_t friend_number, uint32_t file_number, CONTROL control);
}
/**
* Sends a file seek control command to a friend for a given file transfer.
*
* This function can only be called to resume a file transfer right before
* ${CONTROL.RESUME} is sent.
*
* @param friend_number The friend number of the friend the file is being
* received from.
* @param file_number The friend-specific identifier for the file transfer.
* @param position The position that the file should be seeked to.
*/
bool seek(uint32_t friend_number, uint32_t file_number, uint64_t position) {
/**
* The friend_number passed did not designate a valid friend.
*/
FRIEND_NOT_FOUND,
/**
* This client is currently not connected to the friend.
*/
FRIEND_NOT_CONNECTED,
/**
* No file transfer with the given file number was found for the given friend.
*/
NOT_FOUND,
/**
* File was not in a state where it could be seeked.
*/
DENIED,
/**
* Seek position was invalid
*/
INVALID_POSITION,
/**
* Packet queue is full.
*/
SENDQ,
}
error for get {
NULL,
/**
* The friend_number passed did not designate a valid friend.
*/
FRIEND_NOT_FOUND,
/**
* No file transfer with the given file number was found for the given friend.
*/
NOT_FOUND,
}
/**
* Copy the file id associated to the file transfer to a byte array.
*
* @param friend_number The friend number of the friend the file is being
* transferred to or received from.
* @param file_number The friend-specific identifier for the file transfer.
* @param file_id A memory region of at least $FILE_ID_LENGTH bytes. If
* this parameter is NULL, this function has no effect.
*
* @return true on success.
*/
const bool get_file_id(uint32_t friend_number, uint32_t file_number, uint8_t[FILE_ID_LENGTH] file_id)
with error for get;
}
/*******************************************************************************
*
* :: File transmission: sending
*
******************************************************************************/
namespace file {
/**
* Send a file transmission request.
*
* Maximum filename length is $MAX_FILENAME_LENGTH bytes. The filename
* should generally just be a file name, not a path with directory names.
*
* If a non-UINT64_MAX file size is provided, it can be used by both sides to
* determine the sending progress. File size can be set to UINT64_MAX for streaming
* data of unknown size.
*
* File transmission occurs in chunks, which are requested through the
* `${event chunk_request}` event.
*
* When a friend goes offline, all file transfers associated with the friend are
* purged from core.
*
* If the file contents change during a transfer, the behaviour is unspecified
* in general. What will actually happen depends on the mode in which the file
* was modified and how the client determines the file size.
*
* - If the file size was increased
* - and sending mode was streaming (file_size = UINT64_MAX), the behaviour
* will be as expected.
* - and sending mode was file (file_size != UINT64_MAX), the
* ${event chunk_request} callback will receive length = 0 when Core thinks
* the file transfer has finished. If the client remembers the file size as
* it was when sending the request, it will terminate the transfer normally.
* If the client re-reads the size, it will think the friend cancelled the
* transfer.
* - If the file size was decreased
* - and sending mode was streaming, the behaviour is as expected.
* - and sending mode was file, the callback will return 0 at the new
* (earlier) end-of-file, signalling to the friend that the transfer was
* cancelled.
* - If the file contents were modified
* - at a position before the current read, the two files (local and remote)
* will differ after the transfer terminates.
* - at a position after the current read, the file transfer will succeed as
* expected.
* - In either case, both sides will regard the transfer as complete and
* successful.
*
* @param friend_number The friend number of the friend the file send request
* should be sent to.
* @param kind The meaning of the file to be sent.
* @param file_size Size in bytes of the file the client wants to send, UINT64_MAX if
* unknown or streaming.
* @param file_id A file identifier of length $FILE_ID_LENGTH that can be used to
* uniquely identify file transfers across core restarts. If NULL, a random one will
* be generated by core. It can then be obtained by using $get_file_id().
* @param filename Name of the file. Does not need to be the actual name. This
* name will be sent along with the file send request.
* @param filename_length Size in bytes of the filename.
*
* @return A file number used as an identifier in subsequent callbacks. This
* number is per friend. File numbers are reused after a transfer terminates.
* On failure, this function returns UINT32_MAX. Any pattern in file numbers
* should not be relied on.
*/
uint32_t send(uint32_t friend_number, uint32_t kind, uint64_t file_size, const uint8_t[FILE_ID_LENGTH] file_id, const uint8_t[filename_length <= MAX_FILENAME_LENGTH] filename) {
NULL,
/**
* The friend_number passed did not designate a valid friend.
*/
FRIEND_NOT_FOUND,
/**
* This client is currently not connected to the friend.
*/
FRIEND_NOT_CONNECTED,
/**
* Filename length exceeded $MAX_FILENAME_LENGTH bytes.
*/
NAME_TOO_LONG,
/**
* Too many ongoing transfers. The maximum number of concurrent file transfers
* is 256 per friend per direction (sending and receiving).
*/
TOO_MANY,
}
/**
* Send a chunk of file data to a friend.
*
* This function is called in response to the `${event chunk_request}` callback. The
* length parameter should be equal to the one received though the callback.
* If it is zero, the transfer is assumed complete. For files with known size,
* Core will know that the transfer is complete after the last byte has been
* received, so it is not necessary (though not harmful) to send a zero-length
* chunk to terminate. For streams, core will know that the transfer is finished
* if a chunk with length less than the length requested in the callback is sent.
*
* @param friend_number The friend number of the receiving friend for this file.
* @param file_number The file transfer identifier returned by tox_file_send.
* @param position The file or stream position from which to continue reading.
* @return true on success.
*/
bool send_chunk(uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t[length] data) {
/**
* The length parameter was non-zero, but data was NULL.
*/
NULL,
/**
* The friend_number passed did not designate a valid friend.
*/
FRIEND_NOT_FOUND,
/**
* This client is currently not connected to the friend.
*/
FRIEND_NOT_CONNECTED,
/**
* No file transfer with the given file number was found for the given friend.
*/
NOT_FOUND,
/**
* File transfer was found but isn't in a transferring state: (paused, done,
* broken, etc...) (happens only when not called from the request chunk callback).
*/
NOT_TRANSFERRING,
/**
* Attempted to send more or less data than requested. The requested data size is
* adjusted according to maximum transmission unit and the expected end of
* the file. Trying to send less or more than requested will return this error.
*/
INVALID_LENGTH,
/**
* Packet queue is full.
*/
SENDQ,
/**
* Position parameter was wrong.
*/
WRONG_POSITION,
}
/**
* This event is triggered when Core is ready to send more file data.
*/
event chunk_request {
/**
* If the length parameter is 0, the file transfer is finished, and the client's
* resources associated with the file number should be released. After a call
* with zero length, the file number can be reused for future file transfers.
*
* If the requested position is not equal to the client's idea of the current
* file or stream position, it will need to seek. In case of read-once streams,
* the client should keep the last read chunk so that a seek back can be
* supported. A seek-back only ever needs to read from the last requested chunk.
* This happens when a chunk was requested, but the send failed. A seek-back
* request can occur an arbitrary number of times for any given chunk.
*
* In response to receiving this callback, the client should call the function
* `$send_chunk` with the requested chunk. If the number of bytes sent
* through that function is zero, the file transfer is assumed complete. A
* client must send the full length of data requested with this callback.
*
* @param friend_number The friend number of the receiving friend for this file.
* @param file_number The file transfer identifier returned by $send.
* @param position The file or stream position from which to continue reading.
* @param length The number of bytes requested for the current chunk.
*/
typedef void(uint32_t friend_number, uint32_t file_number, uint64_t position, size_t length);
}
}
/*******************************************************************************
*
* :: File transmission: receiving
*
******************************************************************************/
namespace file {
/**
* This event is triggered when a file transfer request is received.
*/
event recv {
/**
* The client should acquire resources to be associated with the file transfer.
* Incoming file transfers start in the PAUSED state. After this callback
* returns, a transfer can be rejected by sending a ${CONTROL.CANCEL}
* control command before any other control commands. It can be accepted by
* sending ${CONTROL.RESUME}.
*
* @param friend_number The friend number of the friend who is sending the file
* transfer request.
* @param file_number The friend-specific file number the data received is
* associated with.
* @param kind The meaning of the file to be sent.
* @param file_size Size in bytes of the file the client wants to send,
* UINT64_MAX if unknown or streaming.
* @param filename Name of the file. Does not need to be the actual name. This
* name will be sent along with the file send request.
* @param filename_length Size in bytes of the filename.
*/
typedef void(uint32_t friend_number, uint32_t file_number, uint32_t kind,
uint64_t file_size, const uint8_t[filename_length <= MAX_FILENAME_LENGTH] filename);
}
/**
* This event is first triggered when a file transfer request is received, and
* subsequently when a chunk of file data for an accepted request was received.
*/
event recv_chunk {
/**
* When length is 0, the transfer is finished and the client should release the
* resources it acquired for the transfer. After a call with length = 0, the
* file number can be reused for new file transfers.
*
* If position is equal to file_size (received in the file_receive callback)
* when the transfer finishes, the file was received completely. Otherwise, if
* file_size was UINT64_MAX, streaming ended successfully when length is 0.
*
* @param friend_number The friend number of the friend who is sending the file.
* @param file_number The friend-specific file number the data received is
* associated with.
* @param position The file position of the first byte in data.
* @param data A byte array containing the received chunk.
* @param length The length of the received chunk.
*/
typedef void(uint32_t friend_number, uint32_t file_number, uint64_t position,
const uint8_t[length] data);
}
}
/*******************************************************************************
*
* :: Group chat management
*
******************************************************************************/
/******************************************************************************
*
* :: Group chat message sending and receiving
*
******************************************************************************/
/*******************************************************************************
*
* :: Low-level custom packet sending and receiving
*
******************************************************************************/
namespace friend {
inline namespace send {
error for custom_packet {
NULL,
/**
* The friend number did not designate a valid friend.
*/
FRIEND_NOT_FOUND,
/**
* This client is currently not connected to the friend.
*/
FRIEND_NOT_CONNECTED,
/**
* The first byte of data was not in the specified range for the packet type.
* This range is 200-254 for lossy, and 160-191 for lossless packets.
*/
INVALID,
/**
* Attempted to send an empty packet.
*/
EMPTY,
/**
* Packet data length exceeded $MAX_CUSTOM_PACKET_SIZE.
*/
TOO_LONG,
/**
* Packet queue is full.
*/
SENDQ,
}
/**
* Send a custom lossy packet to a friend.
*
* The first byte of data must be in the range 200-254. Maximum length of a
* custom packet is $MAX_CUSTOM_PACKET_SIZE.
*
* Lossy packets behave like UDP packets, meaning they might never reach the
* other side or might arrive more than once (if someone is messing with the
* connection) or might arrive in the wrong order.
*
* Unless latency is an issue, it is recommended that you use lossless custom
* packets instead.
*
* @param friend_number The friend number of the friend this lossy packet
* should be sent to.
* @param data A byte array containing the packet data.
* @param length The length of the packet data byte array.
*
* @return true on success.
*/
bool lossy_packet(uint32_t friend_number, const uint8_t[length <= MAX_CUSTOM_PACKET_SIZE] data)
with error for custom_packet;
/**
* Send a custom lossless packet to a friend.
*
* The first byte of data must be in the range 160-191. Maximum length of a
* custom packet is $MAX_CUSTOM_PACKET_SIZE.
*
* Lossless packet behaviour is comparable to TCP (reliability, arrive in order)
* but with packets instead of a stream.
*
* @param friend_number The friend number of the friend this lossless packet
* should be sent to.
* @param data A byte array containing the packet data.
* @param length The length of the packet data byte array.
*
* @return true on success.
*/
bool lossless_packet(uint32_t friend_number, const uint8_t[length <= MAX_CUSTOM_PACKET_SIZE] data)
with error for custom_packet;
}
event lossy_packet {
/**
* @param friend_number The friend number of the friend who sent a lossy packet.
* @param data A byte array containing the received packet data.
* @param length The length of the packet data byte array.
*/
typedef void(uint32_t friend_number, const uint8_t[length <= MAX_CUSTOM_PACKET_SIZE] data);
}
event lossless_packet {
/**
* @param friend_number The friend number of the friend who sent the packet.
* @param data A byte array containing the received packet data.
* @param length The length of the packet data byte array.
*/
typedef void(uint32_t friend_number, const uint8_t[length <= MAX_CUSTOM_PACKET_SIZE] data);
}
}
/*******************************************************************************
*
* :: Low-level network information
*
******************************************************************************/
inline namespace self {
uint8_t[PUBLIC_KEY_SIZE] dht_id {
/**
* Writes the temporary DHT public key of this instance to a byte array.
*
* This can be used in combination with an externally accessible IP address and
* the bound port (from ${udp_port.get}) to run a temporary bootstrap node.
*
* Be aware that every time a new instance is created, the DHT public key
* changes, meaning this cannot be used to run a permanent bootstrap node.
*
* @param dht_id A memory region of at least $PUBLIC_KEY_SIZE bytes. If this
* parameter is NULL, this function has no effect.
*/
get();
}
error for get_port {
/**
* The instance was not bound to any port.
*/
NOT_BOUND,
}
uint16_t udp_port {
/**
* Return the UDP port this Tox instance is bound to.
*/
get() with error for get_port;
}
uint16_t tcp_port {
/**
* Return the TCP port this Tox instance is bound to. This is only relevant if
* the instance is acting as a TCP relay.
*/
get() with error for get_port;
}
}
} // class tox
%{
#include "tox_old.h"
#ifdef __cplusplus
}
#endif
#endif
%}
================================================
FILE: other/apidsl/toxav.in.h
================================================
%{
/* toxav.h
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef TOXAV_H
#define TOXAV_H
#include
#include
#include
#ifdef __cplusplus
extern "C" {
#endif
%}
/** \page av Public audio/video API for Tox clients.
*
* This API can handle multiple calls. Each call has its state, in very rare
* occasions the library can change the state of the call without apps knowledge.
*
*/
/** \subsection events Events and callbacks
*
* As in Core API, events are handled by callbacks. One callback can be
* registered per event. All events have a callback function type named
* `toxav_{event}_cb` and a function to register it named `toxav_callback_{event}`.
* Passing a NULL callback will result in no callback being registered for that
* event. Only one callback per event can be registered, so if a client needs
* multiple event listeners, it needs to implement the dispatch functionality
* itself. Unlike Core API, lack of some event handlers will cause the the
* library to drop calls before they are started. Hanging up call from a
* callback causes undefined behaviour.
*
*/
/** \subsection threading Threading implications
*
* Unlike the Core API, this API is fully thread-safe. The library will ensure
* the proper synchronization of parallel calls.
*
* A common way to run ToxAV (multiple or single instance) is to have a thread,
* separate from tox instance thread, running a simple ${toxAV.iterate} loop,
* sleeping for ${toxAV.iteration_interval} * milliseconds on each iteration.
*
* An important thing to note is that events are triggered from both tox and
* toxav thread (see above). Audio and video receive frame events are triggered
* from toxav thread while all the other events are triggered from tox thread.
*
* Tox thread has priority with mutex mechanisms. Any api function can
* fail if mutexes are held by tox thread in which case they will set SYNC
* error code.
*/
/**
* External Tox type.
*/
class tox {
struct this;
}
/**
* ToxAV.
*/
class toxAV {
/**
* The ToxAV instance type. Each ToxAV instance can be bound to only one Tox
* instance, and Tox instance can have only one ToxAV instance. One must make
* sure to close ToxAV instance prior closing Tox instance otherwise undefined
* behaviour occurs. Upon closing of ToxAV instance, all active calls will be
* forcibly terminated without notifying peers.
*
*/
struct this;
/*******************************************************************************
*
* :: API version
*
******************************************************************************/
/**
* The major version number. Incremented when the API or ABI changes in an
* incompatible way.
*/
#define TOXAV_VERSION_MAJOR 0u
/**
* The minor version number. Incremented when functionality is added without
* breaking the API or ABI. Set to 0 when the major version number is
* incremented.
*/
#define TOXAV_VERSION_MINOR 0u
/**
* The patch or revision number. Incremented when bugfixes are applied without
* changing any functionality or API or ABI.
*/
#define TOXAV_VERSION_PATCH 0u
/**
* A macro to check at preprocessing time whether the client code is compatible
* with the installed version of ToxAV.
*/
#define TOXAV_VERSION_IS_API_COMPATIBLE(MAJOR, MINOR, PATCH) \
(TOXAV_VERSION_MAJOR == MAJOR && \
(TOXAV_VERSION_MINOR > MINOR || \
(TOXAV_VERSION_MINOR == MINOR && \
TOXAV_VERSION_PATCH >= PATCH)))
/**
* A macro to make compilation fail if the client code is not compatible with
* the installed version of ToxAV.
*/
#define TOXAV_VERSION_REQUIRE(MAJOR, MINOR, PATCH) \
typedef char toxav_required_version[TOXAV_IS_COMPATIBLE(MAJOR, MINOR, PATCH) ? 1 : -1]
/**
* A convenience macro to call ${version.is_compatible} with the currently
* compiling API version.
*/
#define TOXAV_VERSION_IS_ABI_COMPATIBLE() \
toxav_version_is_compatible(TOXAV_VERSION_MAJOR, TOXAV_VERSION_MINOR, TOXAV_VERSION_PATCH)
static namespace version {
/**
* Return the major version number of the library. Can be used to display the
* ToxAV library version or to check whether the client is compatible with the
* dynamically linked version of ToxAV.
*/
uint32_t major();
/**
* Return the minor version number of the library.
*/
uint32_t minor();
/**
* Return the patch number of the library.
*/
uint32_t patch();
/**
* Return whether the compiled library version is compatible with the passed
* version numbers.
*/
bool is_compatible(uint32_t major, uint32_t minor, uint32_t patch);
}
/*******************************************************************************
*
* :: Creation and destruction
*
******************************************************************************/
/**
* Start new A/V session. There can only be only one session per Tox instance.
*/
static this new (tox::this *tox) {
NULL,
/**
* Memory allocation failure while trying to allocate structures required for
* the A/V session.
*/
MALLOC,
/**
* Attempted to create a second session for the same Tox instance.
*/
MULTIPLE,
}
/**
* Releases all resources associated with the A/V session.
*
* If any calls were ongoing, these will be forcibly terminated without
* notifying peers. After calling this function, no other functions may be
* called and the av pointer becomes invalid.
*/
void kill();
/**
* Returns the Tox instance the A/V object was created for.
*/
tox::this *tox { get(); }
/*******************************************************************************
*
* :: A/V event loop
*
******************************************************************************/
/**
* Returns the interval in milliseconds when the next toxav_iterate call should
* be. If no call is active at the moment, this function returns 200.
*/
const uint32_t iteration_interval();
/**
* Main loop for the session. This function needs to be called in intervals of
* toxav_iteration_interval() milliseconds. It is best called in the separate
* thread from tox_iterate.
*/
void iterate();
/*******************************************************************************
*
* :: Call setup
*
******************************************************************************/
/**
* Call a friend. This will start ringing the friend.
*
* It is the client's responsibility to stop ringing after a certain timeout,
* if such behaviour is desired. If the client does not stop ringing, the
* library will not stop until the friend is disconnected. Audio and video
* receiving are both enabled by default.
*
* @param friend_number The friend number of the friend that should be called.
* @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable
* audio sending.
* @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable
* video sending.
*/
bool call(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate) {
/**
* A resource allocation error occurred while trying to create the structures
* required for the call.
*/
MALLOC,
/**
* Synchronization error occurred.
*/
SYNC,
/**
* The friend number did not designate a valid friend.
*/
FRIEND_NOT_FOUND,
/**
* The friend was valid, but not currently connected.
*/
FRIEND_NOT_CONNECTED,
/**
* Attempted to call a friend while already in an audio or video call with
* them.
*/
FRIEND_ALREADY_IN_CALL,
/**
* Audio or video bit rate is invalid.
*/
INVALID_BIT_RATE,
}
event call {
/**
* The function type for the ${event call} callback.
*
* @param friend_number The friend number from which the call is incoming.
* @param audio_enabled True if friend is sending audio.
* @param video_enabled True if friend is sending video.
*/
typedef void(uint32_t friend_number, bool audio_enabled, bool video_enabled);
}
/**
* Accept an incoming call.
*
* If answering fails for any reason, the call will still be pending and it is
* possible to try and answer it later. Audio and video receiving are both
* enabled by default.
*
* @param friend_number The friend number of the friend that is calling.
* @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable
* audio sending.
* @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable
* video sending.
*/
bool answer(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate) {
/**
* Synchronization error occurred.
*/
SYNC,
/**
* Failed to initialize codecs for call session. Note that codec initiation
* will fail if there is no receive callback registered for either audio or
* video.
*/
CODEC_INITIALIZATION,
/**
* The friend number did not designate a valid friend.
*/
FRIEND_NOT_FOUND,
/**
* The friend was valid, but they are not currently trying to initiate a call.
* This is also returned if this client is already in a call with the friend.
*/
FRIEND_NOT_CALLING,
/**
* Audio or video bit rate is invalid.
*/
INVALID_BIT_RATE,
}
/*******************************************************************************
*
* :: Call state graph
*
******************************************************************************/
bitmask FRIEND_CALL_STATE {
/**
* Set by the AV core if an error occurred on the remote end or if friend
* timed out. This is the final state after which no more state
* transitions can occur for the call. This call state will never be triggered
* in combination with other call states.
*/
ERROR,
/**
* The call has finished. This is the final state after which no more state
* transitions can occur for the call. This call state will never be
* triggered in combination with other call states.
*/
FINISHED,
/**
* The flag that marks that friend is sending audio.
*/
SENDING_A,
/**
* The flag that marks that friend is sending video.
*/
SENDING_V,
/**
* The flag that marks that friend is receiving audio.
*/
ACCEPTING_A,
/**
* The flag that marks that friend is receiving video.
*/
ACCEPTING_V,
}
event call_state {
/**
* The function type for the ${event call_state} callback.
*
* @param friend_number The friend number for which the call state changed.
* @param state The bitmask of the new call state which is guaranteed to be
* different than the previous state. The state is set to 0 when the call is
* paused. The bitmask represents all the activities currently performed by the
* friend.
*/
typedef void(uint32_t friend_number, uint32_t state);
}
/*******************************************************************************
*
* :: Call control
*
******************************************************************************/
enum class CALL_CONTROL {
/**
* Resume a previously paused call. Only valid if the pause was caused by this
* client, if not, this control is ignored. Not valid before the call is accepted.
*/
RESUME,
/**
* Put a call on hold. Not valid before the call is accepted.
*/
PAUSE,
/**
* Reject a call if it was not answered, yet. Cancel a call after it was
* answered.
*/
CANCEL,
/**
* Request that the friend stops sending audio. Regardless of the friend's
* compliance, this will cause the ${event audio.receive_frame} event to stop being
* triggered on receiving an audio frame from the friend.
*/
MUTE_AUDIO,
/**
* Calling this control will notify client to start sending audio again.
*/
UNMUTE_AUDIO,
/**
* Request that the friend stops sending video. Regardless of the friend's
* compliance, this will cause the ${event video.receive_frame} event to stop being
* triggered on receiving a video frame from the friend.
*/
HIDE_VIDEO,
/**
* Calling this control will notify client to start sending video again.
*/
SHOW_VIDEO,
}
/**
* Sends a call control command to a friend.
*
* @param friend_number The friend number of the friend this client is in a call
* with.
* @param control The control command to send.
*
* @return true on success.
*/
bool call_control (uint32_t friend_number, CALL_CONTROL control) {
/**
* Synchronization error occurred.
*/
SYNC,
/**
* The friend_number passed did not designate a valid friend.
*/
FRIEND_NOT_FOUND,
/**
* This client is currently not in a call with the friend. Before the call is
* answered, only CANCEL is a valid control.
*/
FRIEND_NOT_IN_CALL,
/**
* Happens if user tried to pause an already paused call or if trying to
* resume a call that is not paused.
*/
INVALID_TRANSITION,
}
/*******************************************************************************
*
* :: Controlling bit rates
*
******************************************************************************/
namespace bit_rate {
/**
* Set the bit rate to be used in subsequent audio/video frames.
*
* @param friend_number The friend number of the friend for which to set the
* bit rate.
* @param audio_bit_rate The new audio bit rate in Kb/sec. Set to 0 to disable
* audio sending. Set to -1 to leave unchanged.
* @param video_bit_rate The new video bit rate in Kb/sec. Set to 0 to disable
* video sending. Set to -1 to leave unchanged.
*
*/
bool set(uint32_t friend_number, int32_t audio_bit_rate, int32_t video_bit_rate) {
/**
* Synchronization error occurred.
*/
SYNC,
/**
* The audio bit rate passed was not one of the supported values.
*/
INVALID_AUDIO_BIT_RATE,
/**
* The video bit rate passed was not one of the supported values.
*/
INVALID_VIDEO_BIT_RATE,
/**
* The friend_number passed did not designate a valid friend.
*/
FRIEND_NOT_FOUND,
/**
* This client is currently not in a call with the friend.
*/
FRIEND_NOT_IN_CALL,
}
event status {
/**
* The function type for the ${event status} callback. The event is triggered
* when the network becomes too saturated for current bit rates at which
* point core suggests new bit rates.
*
* @param friend_number The friend number of the friend for which to set the
* bit rate.
* @param audio_bit_rate Suggested maximum audio bit rate in Kb/sec.
* @param video_bit_rate Suggested maximum video bit rate in Kb/sec.
*/
typedef void(uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate);
}
}
/*******************************************************************************
*
* :: A/V sending
*
******************************************************************************/
error for send_frame {
/**
* In case of video, one of Y, U, or V was NULL. In case of audio, the samples
* data pointer was NULL.
*/
NULL,
/**
* The friend_number passed did not designate a valid friend.
*/
FRIEND_NOT_FOUND,
/**
* This client is currently not in a call with the friend.
*/
FRIEND_NOT_IN_CALL,
/**
* Synchronization error occurred.
*/
SYNC,
/**
* One of the frame parameters was invalid. E.g. the resolution may be too
* small or too large, or the audio sampling rate may be unsupported.
*/
INVALID,
/**
* Either friend turned off audio or video receiving or we turned off sending
* for the said payload.
*/
PAYLOAD_TYPE_DISABLED,
/**
* Failed to push frame through rtp interface.
*/
RTP_FAILED,
}
namespace audio {
/**
* Send an audio frame to a friend.
*
* The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]...
* Meaning: sample 1 for channel 1, sample 1 for channel 2, ...
* For mono audio, this has no meaning, every sample is subsequent. For stereo,
* this means the expected format is LRLRLR... with samples for left and right
* alternating.
*
* @param friend_number The friend number of the friend to which to send an
* audio frame.
* @param pcm An array of audio samples. The size of this array must be
* sample_count * channels.
* @param sample_count Number of samples in this frame. Valid numbers here are
* ((sample rate) * (audio length) / 1000), where audio length can be
* 2.5, 5, 10, 20, 40 or 60 millseconds.
* @param channels Number of audio channels. Supported values are 1 and 2.
* @param sampling_rate Audio sampling rate used in this frame. Valid sampling
* rates are 8000, 12000, 16000, 24000, or 48000.
*/
bool send_frame(uint32_t friend_number, const int16_t *pcm, size_t sample_count,
uint8_t channels, uint32_t sampling_rate) with error for send_frame;
}
namespace video {
/**
* Send a video frame to a friend.
*
* Y - plane should be of size: height * width
* U - plane should be of size: (height/2) * (width/2)
* V - plane should be of size: (height/2) * (width/2)
*
* @param friend_number The friend number of the friend to which to send a video
* frame.
* @param width Width of the frame in pixels.
* @param height Height of the frame in pixels.
* @param y Y (Luminance) plane data.
* @param u U (Chroma) plane data.
* @param v V (Chroma) plane data.
*/
bool send_frame(uint32_t friend_number, uint16_t width, uint16_t height,
const uint8_t *y, const uint8_t *u, const uint8_t *v) with error for send_frame;
}
/*******************************************************************************
*
* :: A/V receiving
*
******************************************************************************/
namespace audio {
event receive_frame {
/**
* The function type for the ${event receive_frame} callback. The callback can be
* called multiple times per single iteration depending on the amount of queued
* frames in the buffer. The received format is the same as in send function.
*
* @param friend_number The friend number of the friend who sent an audio frame.
* @param pcm An array of audio samples (sample_count * channels elements).
* @param sample_count The number of audio samples per channel in the PCM array.
* @param channels Number of audio channels.
* @param sampling_rate Sampling rate used in this frame.
*
*/
typedef void(uint32_t friend_number, const int16_t *pcm, size_t sample_count,
uint8_t channels, uint32_t sampling_rate);
}
}
namespace video {
event receive_frame {
/**
* The function type for the ${event receive_frame} callback.
*
* @param friend_number The friend number of the friend who sent a video frame.
* @param width Width of the frame in pixels.
* @param height Height of the frame in pixels.
* @param y
* @param u
* @param v Plane data.
* The size of plane data is derived from width and height where
* Y = MAX(width, abs(ystride)) * height,
* U = MAX(width/2, abs(ustride)) * (height/2) and
* V = MAX(width/2, abs(vstride)) * (height/2).
* @param ystride
* @param ustride
* @param vstride Strides data. Strides represent padding for each plane
* that may or may not be present. You must handle strides in
* your image processing code. Strides are negative if the
* image is bottom-up hence why you MUST abs() it when
* calculating plane buffer size.
*/
typedef void(uint32_t friend_number, uint16_t width, uint16_t height,
const uint8_t *y, const uint8_t *u, const uint8_t *v,
int32_t ystride, int32_t ustride, int32_t vstride);
}
}
}
%{
/**
* NOTE Compatibility with old toxav group calls TODO remove
*/
/* Create a new toxav group.
*
* return group number on success.
* return -1 on failure.
*
* Audio data callback format:
* audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata)
*
* Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)).
*/
int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(void *, int, int, const int16_t *, unsigned int, uint8_t,
unsigned int, void *), void *userdata);
/* Join a AV group (you need to have been invited first.)
*
* returns group number on success
* returns -1 on failure.
*
* Audio data callback format (same as the one for toxav_add_av_groupchat()):
* audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata)
*
* Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)).
*/
int toxav_join_av_groupchat(Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length,
void (*audio_callback)(void *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *), void *userdata);
/* Send audio to the group chat.
*
* return 0 on success.
* return -1 on failure.
*
* Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)).
*
* Valid number of samples are ((sample rate) * (audio length (Valid ones are: 2.5, 5, 10, 20, 40 or 60 ms)) / 1000)
* Valid number of channels are 1 or 2.
* Valid sample rates are 8000, 12000, 16000, 24000, or 48000.
*
* Recommended values are: samples = 960, channels = 1, sample_rate = 48000
*/
int toxav_group_send_audio(Tox *tox, int groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels,
unsigned int sample_rate);
#ifdef __cplusplus
}
#endif
#endif /* TOXAV_H */
%}
================================================
FILE: other/astyle/README.md
================================================
This directory can house various tools and utilities.
# How to use astyle
## Manually
### For all files
Run from ``toxcore`` directory:
```bash
astyle --options=./other/astyle/astylerc ./toxcore/*.c ./toxcore/*.h ./toxdns/*.c ./toxdns/*.h ./testing/*.c ./toxav/*.c ./toxav/*.h ./other/*.c ./other/bootstrap_daemon/*.c ./toxencryptsave/*.c ./toxencryptsave/*.h ./auto_tests/*.c
```
### For selected file
Run from ``toxcore`` directory, e.g. for [``tox.h``](/toxcore/tox.h) file:
```bash
astyle --options=./other/astyle/astylerc ./toxcore/tox.h
```
## Automatically, as pre-commit hook (*NIX only)
Copy [``astylerc``](/other/astyle/astylerc) to ``toxcore/.git/hooks``
# Why
``astylerc`` - this file can be used in the pre-commit hook to try its best at making the code conform to the coding style of toxcore.
Furthermore, it is being used to format ``tox.h`` after using [``apidsl``](/other/apidsl) to generate it.
================================================
FILE: other/astyle/astylerc
================================================
--style=kr
--pad-header
--max-code-length=120
--convert-tabs
--indent-switches
--pad-oper
--align-pointer=name
--align-reference=name
--preserve-date
--lineend=linux
--break-blocks
================================================
FILE: other/astyle/pre-commit
================================================
#!/usr/bin/env sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
for file in `git diff-index --diff-filter=ACMR --name-only HEAD`; do
if [[ $file == *.c || $file == *.h ]]
then
echo $file
`which astyle` $file --options=tools/astylerc
git add $file
fi
done
================================================
FILE: other/bootstrap_daemon/README.md
================================================
#Instructions
- [For `systemd` users](#systemd)
- [Setting up](#systemd-setting-up)
- [Updating](#systemd-updating)
- [Troubleshooting](#systemd-troubleshooting)
- [For `SysVinit` users](#sysvinit)
- [Setting up](#sysvinit-setting-up)
- [Updating](#sysvinit-updating)
- [Troubleshooting](#sysvinit-troubleshooting)
- [For `Docker` users](#docker)
- [Setting up](#docker-setting-up)
- [Updating](#docker-updating)
- [Troubleshooting](#docker-troubleshooting)
These instructions are primarily tested on Debian Linux, Wheezy for SysVinit and Jessie for systemd, but they should work on other POSIX-compliant systems too.
##For `systemd` users
###Setting up
For security reasons we run the daemon under its own user.
Create a new user by executing the following:
```sh
sudo useradd --home-dir /var/lib/tox-bootstrapd --create-home --system --shell /sbin/nologin --comment "Account to run Tox's DHT bootstrap daemon" --user-group tox-bootstrapd
```
Restrict access to home directory:
```sh
sudo chmod 700 /var/lib/tox-bootstrapd
```
Copy `tox-bootstrapd.conf` file to where `ExecStart=` from `tox-bootstrapd.service` points to. By default it's `/etc/tox-bootstrapd.conf`.
```sh
sudo cp tox-bootstrapd.conf /etc/tox-bootstrapd.conf
```
Go over everything in the copied `tox-bootstrapd.conf` file. Set options you want and add actual working nodes to the `bootstrap_nodes` list, instead of the example ones, if you want your node to connect to the Tox network. Make sure `pid_file_path` matches `PIDFile=` from `tox-bootstrapd.service`.
Copy `tox-bootstrapd.service` to `/etc/systemd/system/`:
```sh
sudo cp tox-bootstrapd.service /etc/systemd/system/
```
You must uncomment the next line in tox-bootstrapd.service, if you want to use port number < 1024
#CapabilityBoundingSet=CAP_NET_BIND_SERVICE
and, possibly, install `libcap2-bin` or `libcap2` package, depending of your distribution.
Reload systemd units definitions, enable service for automatic start (if needed), start it and verify it's running:
```sh
sudo systemctl daemon-reload
sudo systemctl enable tox-bootstrapd.service
sudo systemctl start tox-bootstrapd.service
sudo systemctl status tox-bootstrapd.service
```
Get your public key and check that the daemon initialized correctly:
```sh
sudo grep "tox-bootstrapd" /var/log/syslog
```
###Updating
You want to make sure that the daemon uses the newest toxcore, as there might have been some changes done to the DHT, so it's advised to update the daemon at least once every month.
To update the daemon first stop it:
```sh
sudo systemctl stop tox-bootstrapd.service
```
Then update your toxcore git repository, rebuild the toxcore and the daemon and make sure to install them.
Check if `tox-bootstrapd.service` in toxcore git repository was modified since the last time you copied it, as you might need to update it too.
After all of this is done, simply start the daemon back again:
```sh
sudo systemctl start tox-bootstrapd.service
```
Note that `tox-bootstrapd.service` file might
###Troubleshooting
- Check daemon's status:
```sh
sudo systemctl status tox-bootstrapd.service
```
- Check the log for errors:
```sh
sudo grep "tox-bootstrapd" /var/log/syslog
# or
sudo journalctl --pager-end
# or
sudo journalctl -f _SYSTEMD_UNIT=tox-bootstrapd.service
```
- Make sure tox-bootstrapd user has write permission for keys and pid files.
- Make sure tox-bootstrapd has read permission for the config file.
- Make sure tox-bootstrapd location matches its path in tox-bootstrapd.service file.
##For `SysVinit` users
###Setting up
For security reasons we run the daemon under its own user.
Create a new user by executing the following:
```sh
sudo useradd --home-dir /var/lib/tox-bootstrapd --create-home --system --shell /sbin/nologin --comment "Account to run Tox's DHT bootstrap daemon" --user-group tox-bootstrapd
```
Restrict access to home directory:
```sh
sudo chmod 700 /var/lib/tox-bootstrapd
```
Copy `tox-bootstrapd.conf` file to where `CFGFILE` variable from `tox-bootstrapd.sh` points to. By default it's `/etc/tox-bootstrapd.conf`.
```sh
sudo cp tox-bootstrapd.conf /etc/tox-bootstrapd.conf
```
Go over everything in the copied `tox-bootstrapd.conf` file. Set options you want and add actual working nodes to the `bootstrap_nodes` list, instead of the example ones, if you want your node to connect to the Tox network. Make sure `pid_file_path` matches `PIDFILE` from `tox-bootstrapd.sh`.
Look at the variable declarations in the beginning of `tox-bootstrapd.sh` init script to see if you need to change anything for it to work on your system. The default values must be fine for most users and we assume that you use those next.
If you have configured the daemon to use any port numbers that are lower than 1024, you need to execute the command below, as by default non-privileged users cannot open ports <1024. The change persists through reboot:
```sh
sudo setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/tox-bootstrapd
```
Copy `tox-bootstrapd.sh` init script to `/etc/init.d/tox-bootstrapd` (note the disappearance of ".sh" ending):
```sh
sudo cp tox-bootstrapd.sh /etc/init.d/tox-bootstrapd
```
Set permissions for the init system to run the script:
```sh
sudo chmod 755 /etc/init.d/tox-bootstrapd
```
Make the init system aware of the script, start the daemon and verify it's running:
```sh
sudo update-rc.d tox-bootstrapd defaults
sudo service tox-bootstrapd start
sudo service tox-bootstrapd status
```
Get your public key and check that the daemon initialized correctly:
```sh
sudo grep "tox-bootstrapd" /var/log/syslog
```
###Updating
You want to make sure that the daemon uses the newest toxcore, as there might have been some changes done to the DHT, so it's advised to update the daemon at least once every month.
To update the daemon first stop it:
```sh
sudo service tox-bootstrapd stop
```
Then update your toxcore git repository, rebuild the toxcore and the daemon and make sure to install them.
Check if `tox-bootstrapd.sh` in toxcore git repository was modified since the last time you copied it, as you might need to update it too.
After all of this is done, simply start the daemon back again:
```sh
sudo service tox-bootstrapd start
```
###Troubleshooting
- Check daemon's status:
```sh
sudo service tox-bootstrapd status
```
- Check the log for errors:
```sh
sudo grep "tox-bootstrapd" /var/log/syslog
```
- Check that variables in the beginning of `/etc/init.d/tox-bootstrapd` are valid.
- Make sure tox-bootstrapd user has write permission for keys and pid files.
- Make sure tox-bootstrapd has read permission for the config file.
- Make sure tox-bootstrapd location matches its path in the `/etc/init.d/tox-bootstrapd` init script.
##For `Docker` users:
###Setting up
If you are familiar with Docker and would rather run the daemon in a Docker container, run the following from this directory:
```sh
sudo docker build -t tox-bootstrapd docker/
sudo useradd --home-dir /var/lib/tox-bootstrapd --create-home --system --shell /sbin/nologin --comment "Account to run Tox's DHT bootstrap daemon" --user-group tox-bootstrapd
sudo chmod 700 /var/lib/tox-bootstrapd
sudo docker run -d --name tox-bootstrapd --restart always -v /var/lib/tox-bootstrapd/:/var/lib/tox-bootstrapd/ -p 443:443 -p 3389:3389 -p 33445:33445 -p 33445:33445/udp tox-bootstrapd
```
We create a new user and protect its home directory in order to mount it in the Docker image, so that the kyepair the daemon uses would be stored on the host system, which makes it less likely that you would loose the keypair while playing with or updating the Docker container.
You can check logs for your public key or any errors:
```sh
sudo docker logs tox-bootstrapd
```
Note that the Docker container runs a script which pulls a list of bootstrap nodes off https://nodes.tox.chat/ and adds them in the config file.
###Updating
You want to make sure that the daemon uses the newest toxcore, as there might have been some changes done to the DHT, so it's advised to update the daemon at least once every month.
To update the daemon, all you need is to erase current container with its image:
```sh
sudo docker stop tox-bootstrapd
sudo docker rm tox-bootstrapd
sudo docker rmi tox-bootstrapd
```
Then rebuild and run the image again:
```sh
sudo docker build -t tox-bootstrapd docker/
sudo docker run -d --name tox-bootstrapd --restart always -v /var/lib/tox-bootstrapd/:/var/lib/tox-bootstrapd/ -p 443:443 -p 3389:3389 -p 33445:33445 -p 33445:33445/udp tox-bootstrapd
```
###Troubleshooting
- Check if the container is running:
```sh
sudo docker ps -a
```
- Check the log for errors:
```sh
sudo docker logs tox-bootstrapd
```
================================================
FILE: other/bootstrap_daemon/docker/Dockerfile
================================================
FROM debian:jessie
WORKDIR /tmp/tox
RUN export BUILD_PACKAGES="\
build-essential \
libtool \
autotools-dev \
automake \
checkinstall \
check \
git \
yasm \
libsodium-dev \
libconfig-dev \
python3" && \
export RUNTIME_PACKAGES="\
libconfig9 \
libsodium13" && \
# get all deps
apt-get update && apt-get install -y $BUILD_PACKAGES $RUNTIME_PACKAGES && \
# install toxcore and daemon
git clone https://github.com/irungentoo/toxcore && \
cd toxcore && \
./autogen.sh && \
./configure --enable-daemon && \
make -j`nproc` && \
make install -j`nproc` && \
ldconfig && \
# add new user
useradd --home-dir /var/lib/tox-bootstrapd --create-home \
--system --shell /sbin/nologin \
--comment "Account to run Tox's DHT bootstrap daemon" \
--user-group tox-bootstrapd && \
chmod 700 /var/lib/tox-bootstrapd && \
cp other/bootstrap_daemon/tox-bootstrapd.conf /etc/tox-bootstrapd.conf && \
# remove all the example bootstrap nodes from the config file
sed -i '/^bootstrap_nodes = /,$d' /etc/tox-bootstrapd.conf && \
# add bootstrap nodes from https://nodes.tox.chat/
python3 other/bootstrap_daemon/docker/get-nodes.py >> /etc/tox-bootstrapd.conf && \
# perform cleanup
export AUTO_ADDED_PACKAGES="$(apt-mark showauto)" && \
apt-get remove --purge -y $BUILD_PACKAGES $AUTO_ADDED_PACKAGES && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
cd / && \
rm -rf /tmp/*
WORKDIR /var/lib/tox-bootstrapd
USER tox-bootstrapd
ENTRYPOINT /usr/local/bin/tox-bootstrapd \
--config /etc/tox-bootstrapd.conf \
--log-backend stdout \
--foreground
EXPOSE 443/tcp 3389/tcp 33445/tcp 33445/udp
================================================
FILE: other/bootstrap_daemon/docker/get-nodes.py
================================================
#!/usr/bin/env python3
"""
Copyright (c) 2016 by nurupo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
# Gets a list of nodes from https://nodes.tox.chat/json and prints them out
# in the format of tox-bootstrapd config file.
import urllib.request
import json
response = urllib.request.urlopen('https://nodes.tox.chat/json')
raw_json = response.read().decode('ascii', 'ignore')
nodes = json.loads(raw_json)['nodes']
output = 'bootstrap_nodes = (\n'
for node in nodes:
node_output = ' { // ' + node['maintainer'] + '\n'
node_output += ' public_key = "' + node['public_key'] + '"\n'
node_output += ' port = ' + str(node['port']) + '\n'
node_output += ' address = "'
if len(node['ipv4']) > 4:
output += node_output + node['ipv4'] + '"\n },\n'
if len(node['ipv6']) > 4:
output += node_output + node['ipv6'] + '"\n },\n'
# remove last comma
output = output[:-2] + '\n)\n'
print(output)
================================================
FILE: other/bootstrap_daemon/src/Makefile.inc
================================================
if BUILD_DHT_BOOTSTRAP_DAEMON
bin_PROGRAMS += tox-bootstrapd
tox_bootstrapd_SOURCES = \
../other/bootstrap_daemon/src/command_line_arguments.c \
../other/bootstrap_daemon/src/command_line_arguments.h \
../other/bootstrap_daemon/src/config.c \
../other/bootstrap_daemon/src/config_defaults.h \
../other/bootstrap_daemon/src/config.h \
../other/bootstrap_daemon/src/log.c \
../other/bootstrap_daemon/src/log.h \
../other/bootstrap_daemon/src/tox-bootstrapd.c \
../other/bootstrap_daemon/src/global.h \
../other/bootstrap_node_packets.c \
../other/bootstrap_node_packets.h
tox_bootstrapd_CFLAGS = \
-I$(top_srcdir)/other/bootstrap_daemon \
$(LIBSODIUM_CFLAGS) \
$(NACL_CFLAGS) \
$(LIBCONFIG_CFLAGS)
tox_bootstrapd_LDADD = \
$(LIBSODIUM_LDFLAGS) \
$(NACL_LDFLAGS) \
libtoxcore.la \
$(LIBCONFIG_LIBS) \
$(LIBSODIUM_LIBS) \
$(NACL_LIBS)
endif
EXTRA_DIST += \
$(top_srcdir)/other/bootstrap_daemon/tox-bootstrapd.conf \
$(top_srcdir)/other/bootstrap_daemon/tox-bootstrapd.service \
$(top_srcdir)/other/bootstrap_daemon/tox-bootstrapd.sh
================================================
FILE: other/bootstrap_daemon/src/command_line_arguments.c
================================================
/* command_line_arguments.c
*
* Tox DHT bootstrap daemon.
* Command line argument handling.
*
* Copyright (C) 2015-2016 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#include "command_line_arguments.h"
#include "global.h"
#include
#include
#include
/**
* Prints --help message
*/
void print_help()
{
// 2 space ident
// make sure all lines fit into 80 columns
// make sure options are listed in alphabetical order
write_log(LOG_LEVEL_INFO,
"Usage: tox-bootstrapd [OPTION]... --config=FILE_PATH\n"
"\n"
"Options:\n"
" --config=FILE_PATH Specify path to the config file.\n"
" This is a required option.\n"
" Set FILE_PATH to a path to an empty file in order to\n"
" use default settings.\n"
" --foreground Run the daemon in foreground. The daemon won't fork\n"
" (detach from the terminal) and won't use the PID file.\n"
" --help Print this help message.\n"
" --log-backend=BACKEND Specify which logging backend to use.\n"
" Valid BACKEND values (case sensetive):\n"
" syslog Writes log messages to syslog.\n"
" Default option when no --log-backend is\n"
" specified.\n"
" stdout Writes log messages to stdout/stderr.\n"
" --version Print version information.\n");
}
void handle_command_line_arguments(int argc, char *argv[], char **cfg_file_path, LOG_BACKEND *log_backend,
bool *run_in_foreground)
{
if (argc < 2) {
write_log(LOG_LEVEL_ERROR, "Error: No arguments provided.\n\n");
print_help();
exit(1);
}
opterr = 0;
static struct option long_options[] = {
{"config", required_argument, 0, 'c'}, // required option
{"foreground", no_argument, 0, 'f'},
{"help", no_argument, 0, 'h'},
{"log-backend", required_argument, 0, 'l'}, // optional, defaults to syslog
{"version", no_argument, 0, 'v'},
{0, 0, 0, 0 }
};
bool cfg_file_path_set = false;
bool log_backend_set = false;
*run_in_foreground = false;
int opt;
while ((opt = getopt_long(argc, argv, ":", long_options, NULL)) != -1) {
switch (opt) {
case 'c':
*cfg_file_path = optarg;
cfg_file_path_set = true;
break;
case 'f':
*run_in_foreground = true;
break;
case 'h':
print_help();
exit(0);
case 'l':
if (strcmp(optarg, "syslog") == 0) {
*log_backend = LOG_BACKEND_SYSLOG;
log_backend_set = true;
} else if (strcmp(optarg, "stdout") == 0) {
*log_backend = LOG_BACKEND_STDOUT;
log_backend_set = true;
} else {
write_log(LOG_LEVEL_ERROR, "Error: Invalid BACKEND value for --log-backend option passed: %s\n\n", optarg);
print_help();
exit(1);
}
break;
case 'v':
write_log(LOG_LEVEL_INFO, "Version: %lu\n", DAEMON_VERSION_NUMBER);
exit(0);
case '?':
write_log(LOG_LEVEL_ERROR, "Error: Unrecognized option %s\n\n", argv[optind - 1]);
print_help();
exit(1);
case ':':
write_log(LOG_LEVEL_ERROR, "Error: No argument provided for option %s\n\n", argv[optind - 1]);
print_help();
exit(1);
}
}
if (!log_backend_set) {
*log_backend = LOG_BACKEND_SYSLOG;
}
if (!cfg_file_path_set) {
write_log(LOG_LEVEL_ERROR, "Error: The required --config option wasn't specified\n\n");
print_help();
exit(1);
}
}
================================================
FILE: other/bootstrap_daemon/src/command_line_arguments.h
================================================
/* command_line_arguments.h
*
* Tox DHT bootstrap daemon.
* Command line argument handling.
*
* Copyright (C) 2015-2016 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef COMMAND_LINE_ARGUMENTS_H
#define COMMAND_LINE_ARGUMENTS_H
#include "log.h"
/**
* Handles command line arguments, setting cfg_file_path and log_backend.
* Terminates the application if incorrect arguments are specified.
*
* @param argc Argc passed into main().
* @param argv Argv passed into main().
* @param cfg_file_path Sets to the provided by the user config file path.
* @param log_backend Sets to the provided by the user log backend option.
* @param run_in_foreground Sets to the provided by the user foreground option.
*/
void handle_command_line_arguments(int argc, char *argv[], char **cfg_file_path, LOG_BACKEND *log_backend, bool *run_in_foreground);
#endif // COMMAND_LINE_ARGUMENTS_H
================================================
FILE: other/bootstrap_daemon/src/config.c
================================================
/* config.c
*
* Tox DHT bootstrap daemon.
* Functionality related to dealing with the config file.
*
* Copyright (C) 2014-2016 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#include "config.h"
#include "config_defaults.h"
#include "log.h"
#include
#include
#include
#include
#include "../../bootstrap_node_packets.h"
/**
* Parses tcp relay ports from `cfg` and puts them into `tcp_relay_ports` array.
*
* Supposed to be called from get_general_config only.
*
* Important: iff `tcp_relay_port_count` > 0, then you are responsible for freeing `tcp_relay_ports`.
*/
void parse_tcp_relay_ports_config(config_t *cfg, uint16_t **tcp_relay_ports, int *tcp_relay_port_count)
{
const char *NAME_TCP_RELAY_PORTS = "tcp_relay_ports";
*tcp_relay_port_count = 0;
config_setting_t *ports_array = config_lookup(cfg, NAME_TCP_RELAY_PORTS);
if (ports_array == NULL) {
write_log(LOG_LEVEL_WARNING, "No '%s' setting in the configuration file.\n", NAME_TCP_RELAY_PORTS);
write_log(LOG_LEVEL_WARNING, "Using default '%s':\n", NAME_TCP_RELAY_PORTS);
uint16_t default_ports[DEFAULT_TCP_RELAY_PORTS_COUNT] = {DEFAULT_TCP_RELAY_PORTS};
int i;
for (i = 0; i < DEFAULT_TCP_RELAY_PORTS_COUNT; i ++) {
write_log(LOG_LEVEL_INFO, "Port #%d: %u\n", i, default_ports[i]);
}
// similar procedure to the one of reading config file below
*tcp_relay_ports = malloc(DEFAULT_TCP_RELAY_PORTS_COUNT * sizeof(uint16_t));
for (i = 0; i < DEFAULT_TCP_RELAY_PORTS_COUNT; i ++) {
(*tcp_relay_ports)[*tcp_relay_port_count] = default_ports[i];
if ((*tcp_relay_ports)[*tcp_relay_port_count] < MIN_ALLOWED_PORT
|| (*tcp_relay_ports)[*tcp_relay_port_count] > MAX_ALLOWED_PORT) {
write_log(LOG_LEVEL_WARNING, "Port #%d: Invalid port: %u, should be in [%d, %d]. Skipping.\n", i,
(*tcp_relay_ports)[*tcp_relay_port_count], MIN_ALLOWED_PORT, MAX_ALLOWED_PORT);
continue;
}
(*tcp_relay_port_count) ++;
}
// the loop above skips invalid ports, so we adjust the allocated memory size
if ((*tcp_relay_port_count) > 0) {
*tcp_relay_ports = realloc(*tcp_relay_ports, (*tcp_relay_port_count) * sizeof(uint16_t));
} else {
free(*tcp_relay_ports);
*tcp_relay_ports = NULL;
}
return;
}
if (config_setting_is_array(ports_array) == CONFIG_FALSE) {
write_log(LOG_LEVEL_ERROR, "'%s' setting should be an array. Array syntax: 'setting = [value1, value2, ...]'.\n",
NAME_TCP_RELAY_PORTS);
return;
}
int config_port_count = config_setting_length(ports_array);
if (config_port_count == 0) {
write_log(LOG_LEVEL_ERROR, "'%s' is empty.\n", NAME_TCP_RELAY_PORTS);
return;
}
*tcp_relay_ports = malloc(config_port_count * sizeof(uint16_t));
int i;
for (i = 0; i < config_port_count; i ++) {
config_setting_t *elem = config_setting_get_elem(ports_array, i);
if (elem == NULL) {
// it's NULL if `ports_array` is not an array (we have that check earlier) or if `i` is out of range, which should not be
write_log(LOG_LEVEL_WARNING, "Port #%d: Something went wrong while parsing the port. Stopping reading ports.\n", i);
break;
}
if (config_setting_is_number(elem) == CONFIG_FALSE) {
write_log(LOG_LEVEL_WARNING, "Port #%d: Not a number. Skipping.\n", i);
continue;
}
(*tcp_relay_ports)[*tcp_relay_port_count] = config_setting_get_int(elem);
if ((*tcp_relay_ports)[*tcp_relay_port_count] < MIN_ALLOWED_PORT
|| (*tcp_relay_ports)[*tcp_relay_port_count] > MAX_ALLOWED_PORT) {
write_log(LOG_LEVEL_WARNING, "Port #%d: Invalid port: %u, should be in [%d, %d]. Skipping.\n", i,
(*tcp_relay_ports)[*tcp_relay_port_count], MIN_ALLOWED_PORT, MAX_ALLOWED_PORT);
continue;
}
(*tcp_relay_port_count) ++;
}
// the loop above skips invalid ports, so we adjust the allocated memory size
if ((*tcp_relay_port_count) > 0) {
*tcp_relay_ports = realloc(*tcp_relay_ports, (*tcp_relay_port_count) * sizeof(uint16_t));
} else {
free(*tcp_relay_ports);
*tcp_relay_ports = NULL;
}
}
int get_general_config(const char *cfg_file_path, char **pid_file_path, char **keys_file_path, int *port,
int *enable_ipv6,
int *enable_ipv4_fallback, int *enable_lan_discovery, int *enable_tcp_relay, uint16_t **tcp_relay_ports,
int *tcp_relay_port_count, int *enable_motd, char **motd)
{
config_t cfg;
const char *NAME_PORT = "port";
const char *NAME_PID_FILE_PATH = "pid_file_path";
const char *NAME_KEYS_FILE_PATH = "keys_file_path";
const char *NAME_ENABLE_IPV6 = "enable_ipv6";
const char *NAME_ENABLE_IPV4_FALLBACK = "enable_ipv4_fallback";
const char *NAME_ENABLE_LAN_DISCOVERY = "enable_lan_discovery";
const char *NAME_ENABLE_TCP_RELAY = "enable_tcp_relay";
const char *NAME_ENABLE_MOTD = "enable_motd";
const char *NAME_MOTD = "motd";
config_init(&cfg);
// Read the file. If there is an error, report it and exit.
if (config_read_file(&cfg, cfg_file_path) == CONFIG_FALSE) {
write_log(LOG_LEVEL_ERROR, "%s:%d - %s\n", config_error_file(&cfg), config_error_line(&cfg), config_error_text(&cfg));
config_destroy(&cfg);
return 0;
}
// Get port
if (config_lookup_int(&cfg, NAME_PORT, port) == CONFIG_FALSE) {
write_log(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_PORT);
write_log(LOG_LEVEL_WARNING, "Using default '%s': %d\n", NAME_PORT, DEFAULT_PORT);
*port = DEFAULT_PORT;
}
// Get PID file location
const char *tmp_pid_file;
if (config_lookup_string(&cfg, NAME_PID_FILE_PATH, &tmp_pid_file) == CONFIG_FALSE) {
write_log(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_PID_FILE_PATH);
write_log(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_PID_FILE_PATH, DEFAULT_PID_FILE_PATH);
tmp_pid_file = DEFAULT_PID_FILE_PATH;
}
*pid_file_path = malloc(strlen(tmp_pid_file) + 1);
strcpy(*pid_file_path, tmp_pid_file);
// Get keys file location
const char *tmp_keys_file;
if (config_lookup_string(&cfg, NAME_KEYS_FILE_PATH, &tmp_keys_file) == CONFIG_FALSE) {
write_log(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_KEYS_FILE_PATH);
write_log(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_KEYS_FILE_PATH, DEFAULT_KEYS_FILE_PATH);
tmp_keys_file = DEFAULT_KEYS_FILE_PATH;
}
*keys_file_path = malloc(strlen(tmp_keys_file) + 1);
strcpy(*keys_file_path, tmp_keys_file);
// Get IPv6 option
if (config_lookup_bool(&cfg, NAME_ENABLE_IPV6, enable_ipv6) == CONFIG_FALSE) {
write_log(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_ENABLE_IPV6);
write_log(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_ENABLE_IPV6, DEFAULT_ENABLE_IPV6 ? "true" : "false");
*enable_ipv6 = DEFAULT_ENABLE_IPV6;
}
// Get IPv4 fallback option
if (config_lookup_bool(&cfg, NAME_ENABLE_IPV4_FALLBACK, enable_ipv4_fallback) == CONFIG_FALSE) {
write_log(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_ENABLE_IPV4_FALLBACK);
write_log(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_ENABLE_IPV4_FALLBACK,
DEFAULT_ENABLE_IPV4_FALLBACK ? "true" : "false");
*enable_ipv4_fallback = DEFAULT_ENABLE_IPV4_FALLBACK;
}
// Get LAN discovery option
if (config_lookup_bool(&cfg, NAME_ENABLE_LAN_DISCOVERY, enable_lan_discovery) == CONFIG_FALSE) {
write_log(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_ENABLE_LAN_DISCOVERY);
write_log(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_ENABLE_LAN_DISCOVERY,
DEFAULT_ENABLE_LAN_DISCOVERY ? "true" : "false");
*enable_lan_discovery = DEFAULT_ENABLE_LAN_DISCOVERY;
}
// Get TCP relay option
if (config_lookup_bool(&cfg, NAME_ENABLE_TCP_RELAY, enable_tcp_relay) == CONFIG_FALSE) {
write_log(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_ENABLE_TCP_RELAY);
write_log(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_ENABLE_TCP_RELAY,
DEFAULT_ENABLE_TCP_RELAY ? "true" : "false");
*enable_tcp_relay = DEFAULT_ENABLE_TCP_RELAY;
}
if (*enable_tcp_relay) {
parse_tcp_relay_ports_config(&cfg, tcp_relay_ports, tcp_relay_port_count);
} else {
*tcp_relay_port_count = 0;
}
// Get MOTD option
if (config_lookup_bool(&cfg, NAME_ENABLE_MOTD, enable_motd) == CONFIG_FALSE) {
write_log(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_ENABLE_MOTD);
write_log(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_ENABLE_MOTD,
DEFAULT_ENABLE_MOTD ? "true" : "false");
*enable_motd = DEFAULT_ENABLE_MOTD;
}
if (*enable_motd) {
// Get MOTD
const char *tmp_motd;
if (config_lookup_string(&cfg, NAME_MOTD, &tmp_motd) == CONFIG_FALSE) {
write_log(LOG_LEVEL_WARNING, "No '%s' setting in configuration file.\n", NAME_MOTD);
write_log(LOG_LEVEL_WARNING, "Using default '%s': %s\n", NAME_MOTD, DEFAULT_MOTD);
tmp_motd = DEFAULT_MOTD;
}
size_t tmp_motd_length = strlen(tmp_motd) + 1;
size_t motd_length = tmp_motd_length > MAX_MOTD_LENGTH ? MAX_MOTD_LENGTH : tmp_motd_length;
*motd = malloc(motd_length);
strncpy(*motd, tmp_motd, motd_length);
(*motd)[motd_length - 1] = '\0';
}
config_destroy(&cfg);
write_log(LOG_LEVEL_INFO, "Successfully read:\n");
write_log(LOG_LEVEL_INFO, "'%s': %s\n", NAME_PID_FILE_PATH, *pid_file_path);
write_log(LOG_LEVEL_INFO, "'%s': %s\n", NAME_KEYS_FILE_PATH, *keys_file_path);
write_log(LOG_LEVEL_INFO, "'%s': %d\n", NAME_PORT, *port);
write_log(LOG_LEVEL_INFO, "'%s': %s\n", NAME_ENABLE_IPV6, *enable_ipv6 ? "true" : "false");
write_log(LOG_LEVEL_INFO, "'%s': %s\n", NAME_ENABLE_IPV4_FALLBACK, *enable_ipv4_fallback ? "true" : "false");
write_log(LOG_LEVEL_INFO, "'%s': %s\n", NAME_ENABLE_LAN_DISCOVERY, *enable_lan_discovery ? "true" : "false");
write_log(LOG_LEVEL_INFO, "'%s': %s\n", NAME_ENABLE_TCP_RELAY, *enable_tcp_relay ? "true" : "false");
// show info about tcp ports only if tcp relay is enabled
if (*enable_tcp_relay) {
if (*tcp_relay_port_count == 0) {
write_log(LOG_LEVEL_ERROR, "No TCP ports could be read.\n");
} else {
write_log(LOG_LEVEL_INFO, "Read %d TCP ports:\n", *tcp_relay_port_count);
int i;
for (i = 0; i < *tcp_relay_port_count; i ++) {
write_log(LOG_LEVEL_INFO, "Port #%d: %u\n", i, (*tcp_relay_ports)[i]);
}
}
}
write_log(LOG_LEVEL_INFO, "'%s': %s\n", NAME_ENABLE_MOTD, *enable_motd ? "true" : "false");
if (*enable_motd) {
write_log(LOG_LEVEL_INFO, "'%s': %s\n", NAME_MOTD, *motd);
}
return 1;
}
/**
*
* Converts a hex string with even number of characters into binary.
*
* Important: You are responsible for freeing the return value.
*
* @return binary on success,
* NULL on failure.
*/
uint8_t *hex_string_to_bin(char *hex_string)
{
if (strlen(hex_string) % 2 != 0) {
return NULL;
}
size_t len = strlen(hex_string) / 2;
uint8_t *ret = malloc(len);
char *pos = hex_string;
size_t i;
for (i = 0; i < len; ++i, pos += 2) {
sscanf(pos, "%2hhx", &ret[i]);
}
return ret;
}
int bootstrap_from_config(const char *cfg_file_path, DHT *dht, int enable_ipv6)
{
const char *NAME_BOOTSTRAP_NODES = "bootstrap_nodes";
const char *NAME_PUBLIC_KEY = "public_key";
const char *NAME_PORT = "port";
const char *NAME_ADDRESS = "address";
config_t cfg;
config_init(&cfg);
if (config_read_file(&cfg, cfg_file_path) == CONFIG_FALSE) {
write_log(LOG_LEVEL_ERROR, "%s:%d - %s\n", config_error_file(&cfg), config_error_line(&cfg), config_error_text(&cfg));
config_destroy(&cfg);
return 0;
}
config_setting_t *node_list = config_lookup(&cfg, NAME_BOOTSTRAP_NODES);
if (node_list == NULL) {
write_log(LOG_LEVEL_WARNING, "No '%s' setting in the configuration file. Skipping bootstrapping.\n",
NAME_BOOTSTRAP_NODES);
config_destroy(&cfg);
return 1;
}
if (config_setting_length(node_list) == 0) {
write_log(LOG_LEVEL_WARNING, "No bootstrap nodes found. Skipping bootstrapping.\n");
config_destroy(&cfg);
return 1;
}
int bs_port;
const char *bs_address;
const char *bs_public_key;
config_setting_t *node;
int i = 0;
while (config_setting_length(node_list)) {
node = config_setting_get_elem(node_list, 0);
if (node == NULL) {
config_destroy(&cfg);
return 0;
}
// Check that all settings are present
if (config_setting_lookup_string(node, NAME_PUBLIC_KEY, &bs_public_key) == CONFIG_FALSE) {
write_log(LOG_LEVEL_WARNING, "Bootstrap node #%d: Couldn't find '%s' setting. Skipping the node.\n", i,
NAME_PUBLIC_KEY);
goto next;
}
if (config_setting_lookup_int(node, NAME_PORT, &bs_port) == CONFIG_FALSE) {
write_log(LOG_LEVEL_WARNING, "Bootstrap node #%d: Couldn't find '%s' setting. Skipping the node.\n", i, NAME_PORT);
goto next;
}
if (config_setting_lookup_string(node, NAME_ADDRESS, &bs_address) == CONFIG_FALSE) {
write_log(LOG_LEVEL_WARNING, "Bootstrap node #%d: Couldn't find '%s' setting. Skipping the node.\n", i, NAME_ADDRESS);
goto next;
}
// Process settings
if (strlen(bs_public_key) != crypto_box_PUBLICKEYBYTES * 2) {
write_log(LOG_LEVEL_WARNING, "Bootstrap node #%d: Invalid '%s': %s. Skipping the node.\n", i, NAME_PUBLIC_KEY,
bs_public_key);
goto next;
}
if (bs_port < MIN_ALLOWED_PORT || bs_port > MAX_ALLOWED_PORT) {
write_log(LOG_LEVEL_WARNING, "Bootstrap node #%d: Invalid '%s': %d, should be in [%d, %d]. Skipping the node.\n", i,
NAME_PORT,
bs_port, MIN_ALLOWED_PORT, MAX_ALLOWED_PORT);
goto next;
}
uint8_t *bs_public_key_bin = hex_string_to_bin((char *)bs_public_key);
const int address_resolved = DHT_bootstrap_from_address(dht, bs_address, enable_ipv6, htons(bs_port),
bs_public_key_bin);
free(bs_public_key_bin);
if (!address_resolved) {
write_log(LOG_LEVEL_WARNING, "Bootstrap node #%d: Invalid '%s': %s. Skipping the node.\n", i, NAME_ADDRESS, bs_address);
goto next;
}
write_log(LOG_LEVEL_INFO, "Successfully added bootstrap node #%d: %s:%d %s\n", i, bs_address, bs_port, bs_public_key);
next:
// config_setting_lookup_string() allocates string inside and doesn't allow us to free it direcly
// though it's freed when the element is removed, so we free it right away in order to keep memory
// consumption minimal
config_setting_remove_elem(node_list, 0);
i++;
}
config_destroy(&cfg);
return 1;
}
================================================
FILE: other/bootstrap_daemon/src/config_defaults.h
================================================
/* config_defaults.h
*
* Tox DHT bootstrap daemon.
* Default config options for when they are missing in the config file.
*
* Copyright (C) 2014-2016 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef CONFIG_DEFAULTS_H
#define CONFIG_DEFAULTS_H
#include "global.h"
#define DEFAULT_PID_FILE_PATH "tox-bootstrapd.pid"
#define DEFAULT_KEYS_FILE_PATH "tox-bootstrapd.keys"
#define DEFAULT_PORT 33445
#define DEFAULT_ENABLE_IPV6 1 // 1 - true, 0 - false
#define DEFAULT_ENABLE_IPV4_FALLBACK 1 // 1 - true, 0 - false
#define DEFAULT_ENABLE_LAN_DISCOVERY 1 // 1 - true, 0 - false
#define DEFAULT_ENABLE_TCP_RELAY 1 // 1 - true, 0 - false
#define DEFAULT_TCP_RELAY_PORTS 443, 3389, 33445 // comma-separated list of ports. make sure to adjust DEFAULT_TCP_RELAY_PORTS_COUNT accordingly
#define DEFAULT_TCP_RELAY_PORTS_COUNT 3
#define DEFAULT_ENABLE_MOTD 1 // 1 - true, 0 - false
#define DEFAULT_MOTD DAEMON_NAME
#endif // CONFIG_DEFAULTS_H
================================================
FILE: other/bootstrap_daemon/src/global.h
================================================
/* global.h
*
* Tox DHT bootstrap daemon.
* Globally used defines.
*
* Copyright (C) 2014-2016 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef GLOBAL_H
#define GLOBAL_H
#define DAEMON_NAME "tox-bootstrapd"
#define DAEMON_VERSION_NUMBER 2016010100UL // yyyymmmddvv format: yyyy year, mm month, dd day, vv version change count for that day
#define MIN_ALLOWED_PORT 1
#define MAX_ALLOWED_PORT 65535
#endif // GLOBAL_H
================================================
FILE: other/bootstrap_daemon/src/log.c
================================================
/* log.c
*
* Tox DHT bootstrap daemon.
* Logging utility with support of multipel logging backends.
*
* Copyright (C) 2015-2016 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#include "log.h"
#include "global.h"
#include
#include
#include
LOG_BACKEND current_backend = -1;
bool open_log(LOG_BACKEND backend)
{
if (current_backend != -1) {
return false;
}
if (backend == LOG_BACKEND_SYSLOG) {
openlog(DAEMON_NAME, LOG_NOWAIT | LOG_PID, LOG_DAEMON);
}
current_backend = backend;
return true;
}
bool close_log()
{
if (current_backend == -1) {
return false;
}
if (current_backend == LOG_BACKEND_SYSLOG) {
closelog();
}
current_backend = -1;
return true;
}
int level_syslog(LOG_LEVEL level)
{
switch (level) {
case LOG_LEVEL_INFO:
return LOG_INFO;
case LOG_LEVEL_WARNING:
return LOG_WARNING;
case LOG_LEVEL_ERROR:
return LOG_ERR;
}
return LOG_INFO;
}
void log_syslog(LOG_LEVEL level, const char *format, va_list args)
{
vsyslog(level_syslog(level), format, args);
}
FILE *level_stdout(LOG_LEVEL level)
{
switch (level) {
case LOG_LEVEL_INFO:
return stdout;
case LOG_LEVEL_WARNING: // intentional fallthrough
case LOG_LEVEL_ERROR:
return stderr;
}
return stdout;
}
void log_stdout(LOG_LEVEL level, const char *format, va_list args)
{
vfprintf(level_stdout(level), format, args);
fflush(level_stdout(level));
}
bool write_log(LOG_LEVEL level, const char *format, ...)
{
va_list args;
va_start(args, format);
switch (current_backend) {
case LOG_BACKEND_SYSLOG:
log_syslog(level, format, args);
break;
case LOG_BACKEND_STDOUT:
log_stdout(level, format, args);
break;
}
va_end(args);
return current_backend != -1;
}
================================================
FILE: other/bootstrap_daemon/src/log.h
================================================
/* log.h
*
* Tox DHT bootstrap daemon.
* Logging utility with support of multipel logging backends.
*
* Copyright (C) 2015-2016 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef LOG_H
#define LOG_H
#include
typedef enum LOG_BACKEND {
LOG_BACKEND_SYSLOG,
LOG_BACKEND_STDOUT
} LOG_BACKEND;
typedef enum LOG_LEVEL {
LOG_LEVEL_INFO,
LOG_LEVEL_WARNING,
LOG_LEVEL_ERROR
} LOG_LEVEL;
/**
* Initializes logger.
* @param backend Specifies which backend to use.
* @return true on success, flase if log is already opened.
*/
bool open_log(LOG_BACKEND backend);
/**
* Releases all used resources by the logger.
* @return true on success, flase if log is already closed.
*/
bool close_log();
/**
* Writes a message to the log.
* @param level Log level to use.
* @param format printf-like format string.
* @param ... Zero or more arguments, similar to printf function.
* @return true on success, flase if log is closed.
*/
bool write_log(LOG_LEVEL level, const char *format, ...);
#endif // LOG_H
================================================
FILE: other/bootstrap_daemon/src/tox-bootstrapd.c
================================================
/* tox-bootstrapd.c
*
* Tox DHT bootstrap daemon.
* Main file.
*
* Copyright (C) 2014-2016 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
// system provided
#include
// C
#include
#include
#include
#include
// toxcore
#include "../../../toxcore/LAN_discovery.h"
#include "../../../toxcore/onion_announce.h"
#include "../../../toxcore/TCP_server.h"
#include "../../../toxcore/util.h"
// misc
#include "../../bootstrap_node_packets.h"
#include "command_line_arguments.h"
#include "config.h"
#include "global.h"
#include "log.h"
#define SLEEP_MILLISECONDS(MS) usleep(1000*MS)
// Uses the already existing key or creates one if it didn't exist
//
// retirns 1 on success
// 0 on failure - no keys were read or stored
int manage_keys(DHT *dht, char *keys_file_path)
{
const uint32_t KEYS_SIZE = crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES;
uint8_t keys[KEYS_SIZE];
FILE *keys_file;
// Check if file exits, proceed to open and load keys
keys_file = fopen(keys_file_path, "r");
if (keys_file != NULL) {
const size_t read_size = fread(keys, sizeof(uint8_t), KEYS_SIZE, keys_file);
if (read_size != KEYS_SIZE) {
fclose(keys_file);
return 0;
}
memcpy(dht->self_public_key, keys, crypto_box_PUBLICKEYBYTES);
memcpy(dht->self_secret_key, keys + crypto_box_PUBLICKEYBYTES, crypto_box_SECRETKEYBYTES);
} else {
// Otherwise save new keys
memcpy(keys, dht->self_public_key, crypto_box_PUBLICKEYBYTES);
memcpy(keys + crypto_box_PUBLICKEYBYTES, dht->self_secret_key, crypto_box_SECRETKEYBYTES);
keys_file = fopen(keys_file_path, "w");
if (!keys_file)
return 0;
const size_t write_size = fwrite(keys, sizeof(uint8_t), KEYS_SIZE, keys_file);
if (write_size != KEYS_SIZE) {
fclose(keys_file);
return 0;
}
}
fclose(keys_file);
return 1;
}
// Prints public key
void print_public_key(const uint8_t *public_key)
{
char buffer[2 * crypto_box_PUBLICKEYBYTES + 1];
int index = 0;
size_t i;
for (i = 0; i < crypto_box_PUBLICKEYBYTES; i++) {
index += sprintf(buffer + index, "%02hhX", public_key[i]);
}
write_log(LOG_LEVEL_INFO, "Public Key: %s\n", buffer);
return;
}
// Demonizes the process, appending PID to the PID file and closing file descriptors based on log backend
// Terminates the application if the daemonization fails.
void daemonize(LOG_BACKEND log_backend, char *pid_file_path)
{
// Check if the PID file exists
FILE *pid_file;
if ((pid_file = fopen(pid_file_path, "r"))) {
write_log(LOG_LEVEL_WARNING, "Another instance of the daemon is already running, PID file %s exists.\n", pid_file_path);
fclose(pid_file);
}
// Open the PID file for writing
pid_file = fopen(pid_file_path, "a+");
if (pid_file == NULL) {
write_log(LOG_LEVEL_ERROR, "Couldn't open the PID file for writing: %s. Exiting.\n", pid_file_path);
exit(1);
}
// Fork off from the parent process
const pid_t pid = fork();
if (pid > 0) {
fprintf(pid_file, "%d", pid);
fclose(pid_file);
write_log(LOG_LEVEL_INFO, "Forked successfully: PID: %d.\n", pid);
exit(0);
} else {
fclose(pid_file);
}
if (pid < 0) {
write_log(LOG_LEVEL_ERROR, "Forking failed. Exiting.\n");
exit(1);
}
// Create a new SID for the child process
if (setsid() < 0) {
write_log(LOG_LEVEL_ERROR, "SID creation failure. Exiting.\n");
exit(1);
}
// Change the current working directory
if ((chdir("/")) < 0) {
write_log(LOG_LEVEL_ERROR, "Couldn't change working directory to '/'. Exiting.\n");
exit(1);
}
// Go quiet
if (log_backend != LOG_BACKEND_STDOUT) {
close(STDOUT_FILENO);
close(STDIN_FILENO);
close(STDERR_FILENO);
}
}
int main(int argc, char *argv[])
{
umask(077);
char *cfg_file_path;
LOG_BACKEND log_backend;
bool run_in_foreground;
// choose backend for printing command line argument parsing output based on whether the daemon is being run from a terminal
log_backend = isatty(STDOUT_FILENO) ? LOG_BACKEND_STDOUT : LOG_BACKEND_SYSLOG;
open_log(log_backend);
handle_command_line_arguments(argc, argv, &cfg_file_path, &log_backend, &run_in_foreground);
close_log();
open_log(log_backend);
write_log(LOG_LEVEL_INFO, "Running \"%s\" version %lu.\n", DAEMON_NAME, DAEMON_VERSION_NUMBER);
char *pid_file_path, *keys_file_path;
int port;
int enable_ipv6;
int enable_ipv4_fallback;
int enable_lan_discovery;
int enable_tcp_relay;
uint16_t *tcp_relay_ports;
int tcp_relay_port_count;
int enable_motd;
char *motd;
if (get_general_config(cfg_file_path, &pid_file_path, &keys_file_path, &port, &enable_ipv6, &enable_ipv4_fallback,
&enable_lan_discovery, &enable_tcp_relay, &tcp_relay_ports, &tcp_relay_port_count, &enable_motd, &motd)) {
write_log(LOG_LEVEL_INFO, "General config read successfully\n");
} else {
write_log(LOG_LEVEL_ERROR, "Couldn't read config file: %s. Exiting.\n", cfg_file_path);
return 1;
}
if (port < MIN_ALLOWED_PORT || port > MAX_ALLOWED_PORT) {
write_log(LOG_LEVEL_ERROR, "Invalid port: %d, should be in [%d, %d]. Exiting.\n", port, MIN_ALLOWED_PORT,
MAX_ALLOWED_PORT);
return 1;
}
if (!run_in_foreground) {
daemonize(log_backend, pid_file_path);
}
free(pid_file_path);
IP ip;
ip_init(&ip, enable_ipv6);
Networking_Core *net = new_networking(ip, port);
if (net == NULL) {
if (enable_ipv6 && enable_ipv4_fallback) {
write_log(LOG_LEVEL_WARNING, "Couldn't initialize IPv6 networking. Falling back to using IPv4.\n");
enable_ipv6 = 0;
ip_init(&ip, enable_ipv6);
net = new_networking(ip, port);
if (net == NULL) {
write_log(LOG_LEVEL_ERROR, "Couldn't fallback to IPv4. Exiting.\n");
return 1;
}
} else {
write_log(LOG_LEVEL_ERROR, "Couldn't initialize networking. Exiting.\n");
return 1;
}
}
DHT *dht = new_DHT(net);
if (dht == NULL) {
write_log(LOG_LEVEL_ERROR, "Couldn't initialize Tox DHT instance. Exiting.\n");
return 1;
}
Onion *onion = new_onion(dht);
Onion_Announce *onion_a = new_onion_announce(dht);
if (!(onion && onion_a)) {
write_log(LOG_LEVEL_ERROR, "Couldn't initialize Tox Onion. Exiting.\n");
return 1;
}
if (enable_motd) {
if (bootstrap_set_callbacks(dht->net, DAEMON_VERSION_NUMBER, (uint8_t *)motd, strlen(motd) + 1) == 0) {
write_log(LOG_LEVEL_INFO, "Set MOTD successfully.\n");
} else {
write_log(LOG_LEVEL_ERROR, "Couldn't set MOTD: %s. Exiting.\n", motd);
return 1;
}
free(motd);
}
if (manage_keys(dht, keys_file_path)) {
write_log(LOG_LEVEL_INFO, "Keys are managed successfully.\n");
} else {
write_log(LOG_LEVEL_ERROR, "Couldn't read/write: %s. Exiting.\n", keys_file_path);
return 1;
}
free(keys_file_path);
TCP_Server *tcp_server = NULL;
if (enable_tcp_relay) {
if (tcp_relay_port_count == 0) {
write_log(LOG_LEVEL_ERROR, "No TCP relay ports read. Exiting.\n");
return 1;
}
tcp_server = new_TCP_server(enable_ipv6, tcp_relay_port_count, tcp_relay_ports, dht->self_secret_key, onion);
// tcp_relay_port_count != 0 at this point
free(tcp_relay_ports);
if (tcp_server != NULL) {
write_log(LOG_LEVEL_INFO, "Initialized Tox TCP server successfully.\n");
} else {
write_log(LOG_LEVEL_ERROR, "Couldn't initialize Tox TCP server. Exiting.\n");
return 1;
}
}
if (bootstrap_from_config(cfg_file_path, dht, enable_ipv6)) {
write_log(LOG_LEVEL_INFO, "List of bootstrap nodes read successfully.\n");
} else {
write_log(LOG_LEVEL_ERROR, "Couldn't read list of bootstrap nodes in %s. Exiting.\n", cfg_file_path);
return 1;
}
print_public_key(dht->self_public_key);
uint64_t last_LANdiscovery = 0;
const uint16_t htons_port = htons(port);
int waiting_for_dht_connection = 1;
if (enable_lan_discovery) {
LANdiscovery_init(dht);
write_log(LOG_LEVEL_INFO, "Initialized LAN discovery successfully.\n");
}
while (1) {
do_DHT(dht);
if (enable_lan_discovery && is_timeout(last_LANdiscovery, LAN_DISCOVERY_INTERVAL)) {
send_LANdiscovery(htons_port, dht);
last_LANdiscovery = unix_time();
}
if (enable_tcp_relay) {
do_TCP_server(tcp_server);
}
networking_poll(dht->net);
if (waiting_for_dht_connection && DHT_isconnected(dht)) {
write_log(LOG_LEVEL_INFO, "Connected to another bootstrap node successfully.\n");
waiting_for_dht_connection = 0;
}
SLEEP_MILLISECONDS(30);
}
return 1;
}
================================================
FILE: other/bootstrap_daemon/tox-bootstrapd.conf
================================================
// Tox DHT bootstrap daemon configuration file.
// Listening port (UDP).
port = 33445
// A key file is like a password, so keep it where no one can read it.
// If there is no key file, a new one will be generated.
// The daemon should have permission to read/write it.
keys_file_path = "/var/lib/tox-bootstrapd/keys"
// The PID file written to by the daemon.
// Make sure that the user that daemon runs as has permissions to write to the
// PID file.
pid_file_path = "/var/run/tox-bootstrapd/tox-bootstrapd.pid"
// Enable IPv6.
enable_ipv6 = true
// Fallback to IPv4 in case IPv6 fails.
enable_ipv4_fallback = true
// Automatically bootstrap with nodes on local area network.
enable_lan_discovery = true
enable_tcp_relay = true
// While Tox uses 33445 port by default, 443 (https) and 3389 (rdp) ports are very
// common among nodes, so it's encouraged to keep them in place.
tcp_relay_ports = [443, 3389, 33445]
// Reply to MOTD (Message Of The Day) requests.
enable_motd = true
// Just a message that is sent when someone requests MOTD.
// Put anything you want, but note that it will be trimmed to fit into 255 bytes.
motd = "tox-bootstrapd"
// Any number of nodes the daemon will bootstrap itself off.
//
// Remember to replace the provided example with your own node list.
// There is a maintained list of bootstrap nodes on Tox's wiki, if you need it
// (https://wiki.tox.chat/doku.php?id=users:nodes).
//
// You may leave the list empty or remove "bootstrap_nodes" completely,
// in both cases this will be interpreted as if you don't want to bootstrap
// from anyone.
//
// address = any IPv4 or IPv6 address and also any US-ASCII domain name.
bootstrap_nodes = (
{ // Example Node 1 (IPv4)
address = "127.0.0.1"
port = 33445
public_key = "728925473812C7AAC482BE7250BCCAD0B8CB9F737BF3D42ABD34459C1768F854"
},
{ // Example Node 2 (IPv6)
address = "::1/128"
port = 33445
public_key = "3E78BACF0F84235B30054B54898F56793E1DEF8BD46B1038B9D822E8460FAB67"
},
{ // Example Node 3 (US-ASCII domain name)
address = "example.org"
port = 33445
public_key = "8CD5A9BF0A6CE358BA36F7A653F99FA6B258FF756E490F52C1F98CC420F78858"
}
)
================================================
FILE: other/bootstrap_daemon/tox-bootstrapd.service
================================================
[Unit]
Description=Tox DHT Bootstrap Daemon
After=network.target
[Service]
Type=forking
RuntimeDirectory=tox-bootstrapd
RuntimeDirectoryMode=750
PIDFile=/var/run/tox-bootstrapd/tox-bootstrapd.pid
WorkingDirectory=/var/lib/tox-bootstrapd
ExecStart=/usr/local/bin/tox-bootstrapd --config /etc/tox-bootstrapd.conf
User=tox-bootstrapd
Group=tox-bootstrapd
#CapabilityBoundingSet=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
================================================
FILE: other/bootstrap_daemon/tox-bootstrapd.sh
================================================
#! /bin/sh
### BEGIN INIT INFO
# Provides: tox-bootstrapd
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Starts the Tox DHT bootstrapping server daemon
# Description: Starts the Tox DHT bootstrapping server daemon
### END INIT INFO
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Tox DHT bootstrap daemon"
NAME=tox-bootstrapd
DAEMON=/usr/local/bin/$NAME
CFGFILE=/etc/$NAME.conf
DAEMON_ARGS="--config $CFGFILE"
PIDDIR=/var/run/$NAME
PIDFILE=$PIDDIR/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
USER=tox-bootstrapd
GROUP=tox-bootstrapd
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
if [ ! -d $PIDDIR ]
then
mkdir $PIDDIR
fi
chown $USER:$GROUP $PIDDIR
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test --chuid $USER > /dev/null || return 1
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid $USER -- $DAEMON_ARGS || return 2
}
#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
start-stop-daemon --stop --quiet --retry 5 --pidfile $PIDFILE --name $NAME --chuid $USER
RETVAL="$?"
[ "$RETVAL" = 2 ] && return 2
# Many daemons don't delete their pidfiles when they exit.
rm -f $PIDFILE
return "$RETVAL"
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc -p $PIDFILE "$DAEMON" "$NAME" && exit 0 || exit $?
;;
restart)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|status|restart}" >&2
exit 3
;;
esac
exit 0
================================================
FILE: other/bootstrap_node_packets.c
================================================
/* bootstrap_node_packets.c
*
* Special bootstrap node only packets.
*
* Include it in your bootstrap node and use: bootstrap_set_callbacks() to enable.
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#include "bootstrap_node_packets.h"
#define INFO_REQUEST_PACKET_LENGTH 78
static uint32_t bootstrap_version;
static uint8_t bootstrap_motd[MAX_MOTD_LENGTH];
static uint16_t bootstrap_motd_length;
/* To request this packet just send a packet of length INFO_REQUEST_PACKET_LENGTH
* with the first byte being BOOTSTRAP_INFO_PACKET_ID
*/
static int handle_info_request(void *object, IP_Port source, const uint8_t *packet, uint16_t length)
{
if (length != INFO_REQUEST_PACKET_LENGTH)
return 1;
uint8_t data[1 + sizeof(bootstrap_version) + MAX_MOTD_LENGTH];
data[0] = BOOTSTRAP_INFO_PACKET_ID;
memcpy(data + 1, &bootstrap_version, sizeof(bootstrap_version));
uint16_t len = 1 + sizeof(bootstrap_version) + bootstrap_motd_length;
memcpy(data + 1 + sizeof(bootstrap_version), bootstrap_motd, bootstrap_motd_length);
if (sendpacket(object, source, data, len) == len)
return 0;
return 1;
}
int bootstrap_set_callbacks(Networking_Core *net, uint32_t version, uint8_t *motd, uint16_t motd_length)
{
if (motd_length > MAX_MOTD_LENGTH)
return -1;
bootstrap_version = htonl(version);
memcpy(bootstrap_motd, motd, motd_length);
bootstrap_motd_length = motd_length;
networking_registerhandler(net, BOOTSTRAP_INFO_PACKET_ID, &handle_info_request, net);
return 0;
}
================================================
FILE: other/bootstrap_node_packets.h
================================================
/* bootstrap_node_packets.h
*
* Special bootstrap node only packets.
*
* Include it in your bootstrap node and use: bootstrap_set_callbacks() to enable.
*
* Copyright (C) 2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef BOOTSTRAP_NODE_PACKETS_H
#define BOOTSTRAP_NODE_PACKETS_H
#include "../toxcore/network.h"
#define MAX_MOTD_LENGTH 256 /* I recommend you use a maximum of 96 bytes. The hard maximum is this though. */
int bootstrap_set_callbacks(Networking_Core *net, uint32_t version, uint8_t *motd, uint16_t motd_length);
#endif // BOOTSTRAP_NODE_PACKETS_H
================================================
FILE: other/fun/bootstrap_node_info.py
================================================
#!/usr/bin/env python
"""
Copyright (c) 2014 by nurupo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from socket import *
import sys
if sys.version_info[0] == 2:
print("This script requires Python 3+ in order to run.")
sys.exit(1)
def printHelp():
print("Usage: " + sys.argv[0] + " ")
print(" Example: " + sys.argv[0] + " ipv4 192.210.149.121 33445")
print(" Example: " + sys.argv[0] + " ipv4 23.226.230.47 33445")
print(" Example: " + sys.argv[0] + " ipv4 biribiri.org 33445")
print(" Example: " + sys.argv[0] + " ipv4 cerberus.zodiaclabs.org 33445")
print(" Example: " + sys.argv[0] + " ipv6 2604:180:1::3ded:b280 33445")
print("")
print("Return values:")
print(" 0 - received info reply from a node")
print(" 1 - incorrect command line arguments")
print(" 2 - didn't receive any reply from a node")
print(" 3 - received a malformed/unexpected reply")
if len(sys.argv) != 4:
printHelp()
sys.exit(1)
protocol = sys.argv[1]
ip = sys.argv[2]
port = int(sys.argv[3])
INFO_PACKET_ID = b"\xF0" # https://github.com/irungentoo/toxcore/blob/4940c4c62b6014d1f0586aa6aca7bf6e4ecfcf29/toxcore/network.h#L128
INFO_REQUEST_PACKET_LENGTH = 78 # https://github.com/irungentoo/toxcore/blob/881b2d900d1998981fb6b9938ec66012d049635f/other/bootstrap_node_packets.c#L28
# first byte is INFO_REQUEST_ID, other bytes don't matter as long as reqest's length matches INFO_REQUEST_LENGTH
INFO_REQUEST_PACKET = INFO_PACKET_ID + ( b"0" * (INFO_REQUEST_PACKET_LENGTH - len(INFO_PACKET_ID)) )
PACKET_ID_LENGTH = len(INFO_PACKET_ID)
VERSION_LENGTH = 4 # https://github.com/irungentoo/toxcore/blob/881b2d900d1998981fb6b9938ec66012d049635f/other/bootstrap_node_packets.c#L44
MAX_MOTD_LENGTH = 256 # https://github.com/irungentoo/toxcore/blob/881b2d900d1998981fb6b9938ec66012d049635f/other/bootstrap_node_packets.c#L26
MAX_INFO_RESPONSE_PACKET_LENGTH = PACKET_ID_LENGTH + VERSION_LENGTH + MAX_MOTD_LENGTH
SOCK_TIMEOUT_SECONDS = 1.0
sock = None
if protocol == "ipv4":
sock = socket(AF_INET, SOCK_DGRAM)
elif protocol == "ipv6":
sock = socket(AF_INET6, SOCK_DGRAM)
else:
print("Invalid first argument")
printHelp()
sys.exit(1)
sock.sendto(INFO_REQUEST_PACKET, (ip, port))
sock.settimeout(SOCK_TIMEOUT_SECONDS)
try:
data, addr = sock.recvfrom(MAX_INFO_RESPONSE_PACKET_LENGTH)
except timeout:
print("The DHT bootstrap node didn't reply in " + str(SOCK_TIMEOUT_SECONDS) + " sec.")
print("The likely reason for that is that the DHT bootstrap node is either offline or has no info set.")
sys.exit(2)
packetId = data[:PACKET_ID_LENGTH]
if packetId != INFO_PACKET_ID:
print("Bad response, first byte should be", INFO_PACKET_ID, "but got", packetId, "(", data, ")")
print("Are you sure that you are pointing the script at a Tox DHT bootstrap node and that the script is up to date?")
sys.exit(3)
version = int.from_bytes(data[PACKET_ID_LENGTH:PACKET_ID_LENGTH + VERSION_LENGTH], byteorder='big')
motd = data[PACKET_ID_LENGTH + VERSION_LENGTH:].decode("utf-8")
print("Version: " + str(version))
print("MOTD: " + motd)
sys.exit(0)
================================================
FILE: other/fun/cracker.c
================================================
/* Public key cracker.
*
* Can be used to find public keys starting with specific hex (ABCD) for example.
*
* NOTE: There's probably a way to make this faster.
*
* Usage: ./cracker ABCDEF
*
* Will try to find a public key starting with: ABCDEF
*/
#include "../../testing/misc_tools.c"
#include
/* NaCl includes*/
#include
#include
/* Sodium include*/
//#include
void print_key(uint8_t *client_id)
{
uint32_t j;
for (j = 0; j < 32; j++) {
printf("%02hhX", client_id[j]);
}
}
int main(int argc, char *argv[])
{
if (argc < 2) {
printf("usage: ./cracker public_key(or beginning of one in hex format)\n");
return 0;
}
long long unsigned int num_tries = 0;
uint32_t len = strlen(argv[1]) / 2;
unsigned char *key = hex_string_to_bin(argv[1]);
uint8_t pub_key[32], priv_key[32], c_key[32];
if (len > 32)
len = 32;
memcpy(c_key, key, len);
free(key);
randombytes(priv_key, 32);
while (1) {
crypto_scalarmult_curve25519_base(pub_key, priv_key);
uint32_t i;
if (memcmp(c_key, pub_key, len) == 0)
break;
for (i = 32; i != 0; --i) {
priv_key[i - 1] += 1;
if (priv_key[i - 1] != 0)
break;
}
++num_tries;
}
printf("Public key:\n");
print_key(pub_key);
printf("\nPrivate key:\n");
print_key(priv_key);
printf("\n %llu keys tried\n", num_tries);
return 0;
}
================================================
FILE: other/fun/make-funny-savefile.py
================================================
#!/usr/bin/python
"""
Generate a new (and empty) save file with predefined keys. Used to play
with externally generated keys.
(c) 2015 Alexandre Erwin Ittner
Distributed under the GNU GPL v3 or later, WITHOUT ANY WARRANTY. See the
file "COPYING" for license information.
Usage:
./make-funny-savefile.py
The new user profile will be saved to .
The keys must be an hex-encoded valid key pair generated by any means
(eg. strkey.c); username may be anything. A random nospam value will be
generated.
Once the savefile is done, load it in your favorite client to get some
DHT nodes, set status and status messages, add friends, etc.
Example (of course, do not try using this key for anything real):
./make-funny-savefile.py 123411DC8B1A4760B648E0C7243B65F01069E4858F45C612CE1A6F673B603830 CC39440CFC063E4A95B7F2FB2580210558BE5C073AFC1C9604D431CCA3132238 "Test user" test.tox
"""
PUBLIC_KEY_LENGTH = 32
PRIVATE_KEY_LENGTH = 32
# Constants taken from messenger.c
MESSENGER_STATE_COOKIE_GLOBAL = 0x15ed1b1f
MESSENGER_STATE_COOKIE_TYPE = 0x01ce
MESSENGER_STATE_TYPE_NOSPAMKEYS = 1
MESSENGER_STATE_TYPE_DHT = 2
MESSENGER_STATE_TYPE_FRIENDS = 3
MESSENGER_STATE_TYPE_NAME = 4
MESSENGER_STATE_TYPE_STATUSMESSAGE = 5
MESSENGER_STATE_TYPE_STATUS = 6
MESSENGER_STATE_TYPE_TCP_RELAY = 10
MESSENGER_STATE_TYPE_PATH_NODE = 11
STATUS_MESSAGE = "New user".encode("utf-8")
import sys
import struct
import os
def abort(msg):
print(msg)
exit(1)
if len(sys.argv) != 5:
abort("Usage: %s " % (sys.argv[0]))
try:
public_key = sys.argv[1].decode("hex")
except:
abort("Bad public key")
try:
private_key = sys.argv[2].decode("hex")
except:
abort("Bad private key")
if len(public_key) != PUBLIC_KEY_LENGTH:
abort("Public key with wrong length")
if len(private_key) != PRIVATE_KEY_LENGTH:
abort("Private key with wrong length")
user_name = sys.argv[3].encode("utf-8")
if len(user_name) > 32:
abort("User name too long (for this script, at least)")
out_file_name = sys.argv[4]
nospam = os.urandom(4)
def make_subheader(h_type, h_length):
return (
struct.pack("
#include
#include "../../testing/misc_tools.c" // hex_string_to_bin
int load_file(char *filename, char **result)
{
int size = 0;
FILE *f = fopen(filename, "rb");
if (f == NULL) {
*result = NULL;
return -1; // -1 means file opening fail
}
fseek(f, 0, SEEK_END);
size = ftell(f);
fseek(f, 0, SEEK_SET);
*result = (char *)malloc(size + 1);
if (size != fread(*result, sizeof(char), size, f)) {
free(*result);
fclose(f);
return -2; // -2 means file reading fail
}
fclose(f);
(*result)[size] = 0;
return size;
}
int main(int argc, char *argv[])
{
unsigned char pk[crypto_sign_ed25519_PUBLICKEYBYTES];
unsigned char sk[crypto_sign_ed25519_SECRETKEYBYTES];
if (argc == 2 && argv[1][0] == 'g') {
crypto_sign_ed25519_keypair(pk, sk);
printf("Public key:\n");
int i;
for (i = 0; i < crypto_sign_ed25519_PUBLICKEYBYTES; i++) {
printf("%02hhX", pk[i]);
}
printf("\nSecret key:\n");
for (i = 0; i < crypto_sign_ed25519_SECRETKEYBYTES; i++) {
printf("%02hhX", sk[i]);
}
printf("\n");
}
if (argc == 5 && argv[1][0] == 's') {
unsigned char *secret_key = hex_string_to_bin(argv[2]);
char *data;
int size = load_file(argv[3], &data);
if (size < 0)
goto fail;
unsigned long long smlen;
char *sm = malloc(size + crypto_sign_ed25519_BYTES * 2);
crypto_sign_ed25519(sm, &smlen, data, size, secret_key);
free(secret_key);
if (smlen - size != crypto_sign_ed25519_BYTES)
goto fail;
FILE *f = fopen(argv[4], "wb");
if (f == NULL)
goto fail;
memcpy(sm + smlen, sm, crypto_sign_ed25519_BYTES); // Move signature from beginning to end of file.
if (fwrite(sm + (smlen - size), 1, smlen, f) != smlen)
goto fail;
fclose(f);
printf("Signed successfully.\n");
}
if (argc == 4 && argv[1][0] == 'c') {
unsigned char *public_key = hex_string_to_bin(argv[2]);
char *data;
int size = load_file(argv[3], &data);
if (size < 0)
goto fail;
char *signe = malloc(size + crypto_sign_ed25519_BYTES);
memcpy(signe, data + size - crypto_sign_ed25519_BYTES,
crypto_sign_ed25519_BYTES); // Move signature from end to beginning of file.
memcpy(signe + crypto_sign_ed25519_BYTES, data, size - crypto_sign_ed25519_BYTES);
unsigned long long smlen;
char *m = malloc(size);
unsigned long long mlen;
if (crypto_sign_ed25519_open(m, &mlen, signe, size, public_key) == -1) {
printf("Failed checking sig.\n");
goto fail;
}
printf("Checked successfully.\n");
}
return 0;
fail:
printf("FAIL\n");
return 1;
}
================================================
FILE: other/fun/strkey.c
================================================
/* strkey -- String in Public Key
*
* Generates Tox's key pairs, checking if a certain string is in the public key.
*
* Requires sodium or nacl library.
*
* There seem to be some problems with the code working on Windows -- it works
* when built in debug mode with MinGW 4.8, but it doesn't work correctly when
* built in release.
*
* Usage: strkey
*
* Offset - an integer specifying exact byte offset position of the string you
* are looking for within a public key. When offset is negative, the program
* just looks for the desired string being somewhere, doesn't matter where, in
* the public key.
*
* String - a hex string that you want to have in your public key. It must have
* an even number of letters, since every two hexes map to a single byte of
* the public key.
*
* Examples:
* strkey 0 0123
* Looks for a public key that begins with "0123".
*
* strkey 1 0123
* Looks for a public key that has "0123" starting at its second byte, i.e. "XX0123...".
*
* strkey 2 0123
* Looks for a public key that has "0123" starting at its third byte, i.e. "XXXX0123...".
* (each two hexes represent a single byte of a public key)
*
* strkey -1 AF57CC
* Looks for a public key that contains "AF57CC", regardless of its position.
*
* To compile with gcc and sodium: gcc strkey.c -o strkey -lsodium
*/
#include
#include
#include
#define PRINT_TRIES_COUNT
void print_key(unsigned char *key)
{
size_t i;
for (i = 0; i < crypto_box_PUBLICKEYBYTES; i++) {
if (key[i] < 16) {
fprintf(stdout, "0");
}
fprintf(stdout, "%hhX", key[i]);
}
}
int main(int argc, char *argv[])
{
unsigned char public_key[crypto_box_PUBLICKEYBYTES]; // null terminator
unsigned char secret_key[crypto_box_SECRETKEYBYTES];
int offset = 0;
size_t len;
unsigned char desired_bin[crypto_box_PUBLICKEYBYTES]; // null terminator
if (argc == 3) {
offset = atoi(argv[1]);
char *desired_hex = argv[2];
len = strlen(desired_hex);
if (len % 2 != 0) {
fprintf(stderr, "Desired key should have an even number of letters\n");
exit(1);
}
size_t block_length = (offset < 0 ? 0 : offset) + len/2;
if (block_length > crypto_box_PUBLICKEYBYTES) {
fprintf(stderr, "The given key with the given offset exceed public key's length\n");
exit(1);
}
// convert hex to bin
char *pos = desired_hex;
size_t i;
for (i = 0; i < len; pos += 2) {
sscanf(pos, "%2hhx", &desired_bin[i]);
++i;
}
} else {
fprintf(stdout, "Usage: executable \n");
exit(1);
}
len /= 2;
#ifdef PRINT_TRIES_COUNT
long long unsigned int tries = 0;
#endif
if (offset < 0) {
int found = 0;
do {
#ifdef PRINT_TRIES_COUNT
tries ++;
#endif
crypto_box_keypair(public_key, secret_key);
int i;
for (i = 0; i <= crypto_box_PUBLICKEYBYTES - len; i ++) {
if (memcmp(public_key + i, desired_bin, len) == 0) {
found = 1;
break;
}
}
} while (!found);
} else {
unsigned char *p = public_key + offset;
do {
#ifdef PRINT_TRIES_COUNT
tries ++;
#endif
crypto_box_keypair(public_key, secret_key);
} while (memcmp(p, desired_bin, len) != 0);
}
fprintf(stdout, "Public key: ");
print_key(public_key);
fprintf(stdout, "\n");
fprintf(stdout, "Private key: ");
print_key(secret_key);
fprintf(stdout, "\n");
#ifdef PRINT_TRIES_COUNT
fprintf(stdout, "Found the key pair on %llu try.\n", tries);
#endif
return 0;
}
================================================
FILE: other/osx_build_script_toxcore.sh
================================================
#!/usr/bin/env bash
# written by Lubo Diakov
# hard coded toxcore directory, replace with other path or variable as needed
cd ~/Downloads/toxcore
echo "Now working in:"`pwd`
# must have working git binary, and have done git clone at least once before
git pull
echo "If git pull responds: Already up-to-date. you can cancel the build"
echo "by typing anything except y or Y below"
read -p "Continue with build? (enter y to continue): " Last_Chance
# blah blah
if [[ $Last_Chance = [Yy] ]]; then echo "Continuing!";
else echo "Aborted!"; exit
fi
sleep 3
# if libsodium is built with macports, link it from /opt/local/ to /usr/local
if [ ! -L "/usr/local/lib/libsodium.dylib" ]; then
# Control will enter here if $DIRECTORY doesn't exist.
ln -s /opt/local/lib/libsodium.dylib /usr/local/lib/libsodium.dylib
fi
echo "The symlink /usr/local/lib/libsodium.dylib exists."
sleep 3
# replace ppc, i386 as needed.
./configure CC="gcc -arch ppc -arch i386" CXX="g++ -arch ppc -arch i386" CPP="gcc -E" CXXCPP="g++ -E"
# get rid of prior builds, start clean
make clean
make
echo ""
echo "Sudo is required for make install only, all other steps run without it."
echo "Please type your sudo password below for make install:"
sudo make install
exit
================================================
FILE: super_donators/LittleVulpix
================================================
Thank you to all the tox-devs for the idea of a privacy-oriented chat protocol.
Thank you for seeing it through so that we have tox in its current state. It has ways to go, but it's already pretty good!
It is my dream that it will one day be as popular (or even more so) than the other protocols.
Thank you to everyone who is actively participating in maintaining and fixing things to make tox better today than it was yesterday!
And finally a big thanks to the community that stayed together even through semi-tough times and united to fund the campaign!
Working filetransfers are my 'gift' to you all! Thanks to irungentoo for making it happen.
"I'm commander Shepard and this is my favourite chat protocol on the Internet."
@LittleVulpix, 2015.
================================================
FILE: super_donators/grencez_tok5.c
================================================
/* Though it may look bleak at times,
* this ring will stabilize to have one token,
* and Tox will be the one true chat protocol!
* -- Alex P. Klinkhamer (grencez)
*/
#include
#include
#include
int main(int i, char** msg)
{
int j, fd[4], xpd, xid;
if (--i<1) return 1;
srand(getpid());
pipe(fd);
while (xid=rand()%5, --i>0) {
pipe(&fd[2]);
j = (0==fork() ? 0 : 1);
close(fd[j]);
fd[j] = fd[j+2];
close(fd[3-j]);
if (j==0) break;
}
#define SendSc() write(fd[1], &xid, sizeof(xid))
#define RecvPd() read(fd[0], &xpd, sizeof(xpd))
#define A(g,v) if (g) {xid=v; puts(msg[i+1]); fflush(stdout); SendSc();}
SendSc();
while (RecvPd(), 1) {
sleep(1);
if (i==0) {
A( xpd==0 && xid==0 , 1 );
A( xpd==1 && xid<=1 , 2 );
A( xpd> 1 && xid> 1 , 0 );
continue;
}
A( xpd==0 && xid> 1 , xid/4 );
A( xpd==1 && xid!=1 , 1 );
A( xpd==2 && xid<=1 , 2+xid );
A( xpd>=3 && xid<=1 , 4 );
}
return 0;
}
================================================
FILE: super_donators/sir@cmpwn.com
================================================
#!/bin/bash
# Run ./sir@cmpwn.com
# Arrow keys or wasd to move
c=`tput cols`;L=`tput lines`
let x=$c/2;let y=$L/2;d=0;le=3;t="$y;$x";i=0;j=0;S=0
A(){ let i=($RANDOM%$c);let j=($RANDOM%$L);};A
B(){ printf $*;};C(){ B "\x1B[$1";};D(){ C "$1H";}
F(){ D "0;0";C 2J;C "?25h";printf "GAME OVER\nSCORE: $S\n";exit;};trap F INT
C ?25l;C 2J;da(){ D "$j;$i";echo "$1";}
G() { for n in $t; do D "$n";echo "$1";done;}
mt(){ t=`echo "$t"|cut -d' ' -f2-`;}
sc(){ D "0;0";echo "Score: $S"; }
gt() { t+=" $y;$x";};ct() { for n in $t; do [ "$y;$x" == "$n" ]&&F;done;}
M() { case $d in 0)let y--;;1)let x--;;2)let y++;;3)let x++;;esac
let x%=$c;let y%=$L;ct;[ "$y$x" == "$j$i" ]&&{ let le++;A;let S++;}
l=`tr -dc ' '<<<"$t"|wc -c`;gt;[ $l -gt $le ]&&mt;}
ky() { k=$1;read -sN1 -t 0.01 k1;read -sN1 -t 0.01 k2;read -sN1 -t 0.01 k3
k+=${k1}${k2}${k3};case $k in w|$'\e[A'|$'\e0A')d=0;;a|$'\e[D'|$'\e0D')d=1;;
s|$'\e[B'|$'\e0B')d=2;;d|$'\e[C'|$'\e0C')d=3;;esac;}
while :;do da ' ';G ' ';M;da "@";G "#";sc;read -s -n 1 -t 0.1 k && ky "$k";done
================================================
FILE: testing/DHT_test.c
================================================
/* DHT test
* A file with a main that runs our DHT for testing.
*
* Compile with: gcc -O2 -Wall -D VANILLA_NACL -o test ../core/Lossless_UDP.c ../core/network.c ../core/net_crypto.c ../core/Messenger.c ../nacl/build/${HOSTNAME%.*}/lib/amd64/{cpucycles.o,libnacl.a,randombytes.o} DHT_test.c
*
* Command line arguments are the ip, port and public key of a node.
* EX: ./test 127.0.0.1 33445 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
*
* The test will then ask you for the id (in hex format) of the friend you wish to add
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
//#include "../core/network.h"
#include "../toxcore/DHT.h"
#include "../toxcore/friend_requests.h"
#include "misc_tools.c"
#include
//Sleep function (x = milliseconds)
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
#define c_sleep(x) Sleep(1*x)
#else
#include
#include
#define c_sleep(x) usleep(1000*x)
#endif
#define PORT 33445
uint8_t zeroes_cid[crypto_box_PUBLICKEYBYTES];
void print_client_id(uint8_t *public_key)
{
uint32_t j;
for (j = 0; j < crypto_box_PUBLICKEYBYTES; j++) {
printf("%02hhX", public_key[j]);
}
}
void print_hardening(Hardening *h)
{
printf("Hardening:\n");
printf("routes_requests_ok: %hhu\n", h->routes_requests_ok);
printf("routes_requests_timestamp: %llu\n", (long long unsigned int)h->routes_requests_timestamp);
printf("routes_requests_pingedid: ");
print_client_id(h->routes_requests_pingedid);
printf("\nsend_nodes_ok: %hhu\n", h->send_nodes_ok);
printf("send_nodes_timestamp: %llu\n", (long long unsigned int)h->send_nodes_timestamp);
printf("send_nodes_pingedid: ");
print_client_id(h->send_nodes_pingedid);
printf("\ntesting_requests: %hhu\n", h->testing_requests);
printf("testing_timestamp: %llu\n", (long long unsigned int)h->testing_timestamp);
printf("testing_pingedid: ");
print_client_id(h->testing_pingedid);
printf("\n\n");
}
void print_assoc(IPPTsPng *assoc, uint8_t ours)
{
IP_Port *ipp = &assoc->ip_port;
printf("\nIP: %s Port: %u", ip_ntoa(&ipp->ip), ntohs(ipp->port));
printf("\nTimestamp: %llu", (long long unsigned int) assoc->timestamp);
printf("\nLast pinged: %llu\n", (long long unsigned int) assoc->last_pinged);
ipp = &assoc->ret_ip_port;
if (ours)
printf("OUR IP: %s Port: %u\n", ip_ntoa(&ipp->ip), ntohs(ipp->port));
else
printf("RET IP: %s Port: %u\n", ip_ntoa(&ipp->ip), ntohs(ipp->port));
printf("Timestamp: %llu\n", (long long unsigned int) assoc->ret_timestamp);
print_hardening(&assoc->hardening);
}
void print_clientlist(DHT *dht)
{
uint32_t i;
printf("___________________CLOSE________________________________\n");
for (i = 0; i < LCLIENT_LIST; i++) {
Client_data *client = &dht->close_clientlist[i];
if (public_key_cmp(client->public_key, zeroes_cid) == 0)
continue;
printf("ClientID: ");
print_client_id(client->public_key);
print_assoc(&client->assoc4, 1);
print_assoc(&client->assoc6, 1);
}
}
void print_friendlist(DHT *dht)
{
uint32_t i, k;
IP_Port p_ip;
printf("_________________FRIENDS__________________________________\n");
for (k = 0; k < dht->num_friends; k++) {
printf("FRIEND %u\n", k);
printf("ID: ");
print_client_id(dht->friends_list[k].public_key);
int friendok = DHT_getfriendip(dht, dht->friends_list[k].public_key, &p_ip);
printf("\nIP: %s:%u (%d)", ip_ntoa(&p_ip.ip), ntohs(p_ip.port), friendok);
printf("\nCLIENTS IN LIST:\n\n");
for (i = 0; i < MAX_FRIEND_CLIENTS; i++) {
Client_data *client = &dht->friends_list[k].client_list[i];
if (public_key_cmp(client->public_key, zeroes_cid) == 0)
continue;
printf("ClientID: ");
print_client_id(client->public_key);
print_assoc(&client->assoc4, 0);
print_assoc(&client->assoc6, 0);
}
}
}
void printpacket(uint8_t *data, uint32_t length, IP_Port ip_port)
{
uint32_t i;
printf("UNHANDLED PACKET RECEIVED\nLENGTH:%u\nCONTENTS:\n", length);
printf("--------------------BEGIN-----------------------------\n");
for (i = 0; i < length; i++) {
if (data[i] < 16)
printf("0");
printf("%hhX", data[i]);
}
printf("\n--------------------END-----------------------------\n\n\n");
}
int main(int argc, char *argv[])
{
if (argc < 4) {
printf("Usage: %s [--ipv4|--ipv6] ip port public_key\n", argv[0]);
exit(0);
}
/* let user override default by cmdline */
uint8_t ipv6enabled = TOX_ENABLE_IPV6_DEFAULT; /* x */
int argvoffset = cmdline_parsefor_ipv46(argc, argv, &ipv6enabled);
if (argvoffset < 0)
exit(1);
//memcpy(self_client_id, "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", 32);
/* initialize networking */
/* bind to ip 0.0.0.0:PORT */
IP ip;
ip_init(&ip, ipv6enabled);
DHT *dht = new_DHT(new_networking(ip, PORT));
printf("OUR ID: ");
uint32_t i;
for (i = 0; i < 32; i++) {
if (dht->self_public_key[i] < 16)
printf("0");
printf("%hhX", dht->self_public_key[i]);
}
char temp_id[128];
printf("\nEnter the public_key of the friend you wish to add (32 bytes HEX format):\n");
if (!fgets(temp_id, sizeof(temp_id), stdin))
exit(0);
if ((strlen(temp_id) > 0) && (temp_id[strlen(temp_id) - 1] == '\n'))
temp_id[strlen(temp_id) - 1] = '\0';
uint8_t *bin_id = hex_string_to_bin(temp_id);
DHT_addfriend(dht, bin_id, 0, 0, 0, 0);
free(bin_id);
perror("Initialization");
uint16_t port = htons(atoi(argv[argvoffset + 2]));
unsigned char *binary_string = hex_string_to_bin(argv[argvoffset + 3]);
int res = DHT_bootstrap_from_address(dht, argv[argvoffset + 1], ipv6enabled, port, binary_string);
free(binary_string);
if (!res) {
printf("Failed to convert \"%s\" into an IP address. Exiting...\n", argv[argvoffset + 1]);
return 1;
}
/*
IP_Port ip_port;
uint8_t data[MAX_UDP_PACKET_SIZE];
uint32_t length;
*/
while (1) {
do_DHT(dht);
/* slvrTODO:
while(receivepacket(&ip_port, data, &length) != -1) {
if(DHT_handlepacket(data, length, ip_port) && friendreq_handlepacket(data, length, ip_port)) {
//unhandled packet
printpacket(data, length, ip_port);
} else {
printf("Received handled packet with length: %u\n", length);
}
}
*/
networking_poll(dht->net);
print_clientlist(dht);
print_friendlist(dht);
c_sleep(300);
}
return 0;
}
================================================
FILE: testing/Makefile.inc
================================================
if BUILD_NTOX
bin_PROGRAMS += nTox
nTox_SOURCES = ../testing/nTox.h \
../testing/nTox.c
nTox_CFLAGS = $(LIBSODIUM_CFLAGS) \
$(NACL_CFLAGS) \
$(NCURSES_CFLAGS)
nTox_LDADD = $(LIBSODIUM_LDFLAGS) \
$(NAC_LDFLAGS) \
libtoxcore.la \
$(LIBSODIUM_LIBS) \
$(NACL_OBJECTS) \
$(NACL_LIBS) \
$(NCURSES_LIBS) \
$(WINSOCK2_LIBS)
endif
if BUILD_TESTING
noinst_PROGRAMS += DHT_test \
Messenger_test \
dns3_test
DHT_test_SOURCES = ../testing/DHT_test.c
DHT_test_CFLAGS = $(LIBSODIUM_CFLAGS) \
$(NACL_CFLAGS)
DHT_test_LDADD = $(LIBSODIUM_LDFLAGS) \
$(NACL_LDFLAGS) \
libtoxcore.la \
$(LIBSODIUM_LIBS) \
$(NACL_OBJECTS) \
$(NACL_LIBS) \
$(WINSOCK2_LIBS)
Messenger_test_SOURCES = \
../testing/Messenger_test.c
Messenger_test_CFLAGS = $(LIBSODIUM_CFLAGS) \
$(NACL_CFLAGS)
Messenger_test_LDADD = $(LIBSODIUM_LDFLAGS) \
$(NACL_LDFLAGS) \
libtoxcore.la \
$(LIBSODIUM_LIBS) \
$(NACL_OBJECTS) \
$(NACL_LIBS) \
$(WINSOCK2_LIBS)
dns3_test_SOURCES = \
../testing/dns3_test.c
dns3_test_CFLAGS = \
$(LIBSODIUM_CFLAGS) \
$(NACL_CFLAGS)
dns3_test_LDADD = \
$(LIBSODIUM_LDFLAGS) \
$(NACL_LDFLAGS) \
libtoxdns.la \
libtoxcore.la \
$(LIBSODIUM_LIBS) \
$(NACL_OBJECTS) \
$(NACL_LIBS) \
$(WINSOCK2_LIBS)
if !WIN32
noinst_PROGRAMS += tox_sync
tox_sync_SOURCES = ../testing/tox_sync.c
tox_sync_CFLAGS = $(LIBSODIUM_CFLAGS) \
$(NACL_CFLAGS)
tox_sync_LDADD = $(LIBSODIUM_LDFLAGS) \
$(NACL_LDFLAGS) \
libtoxcore.la \
$(LIBSODIUM_LIBS) \
$(NACL_OBJECTS) \
$(NACL_LIBS)
noinst_PROGRAMS += tox_shell
tox_shell_SOURCES = ../testing/tox_shell.c
tox_shell_CFLAGS = $(LIBSODIUM_CFLAGS) \
$(NACL_CFLAGS)
tox_shell_LDADD = $(LIBSODIUM_LDFLAGS) \
$(NACL_LDFLAGS) \
libtoxcore.la \
$(LIBSODIUM_LIBS) \
$(NACL_OBJECTS) \
$(NACL_LIBS) \
-lutil
noinst_PROGRAMS += irc_syncbot
irc_syncbot_SOURCES = ../testing/irc_syncbot.c
irc_syncbot_CFLAGS = $(LIBSODIUM_CFLAGS) \
$(NACL_CFLAGS)
irc_syncbot_LDADD = $(LIBSODIUM_LDFLAGS) \
$(NACL_LDFLAGS) \
libtoxcore.la \
$(LIBSODIUM_LIBS) \
$(NACL_OBJECTS) \
$(NACL_LIBS)
endif
EXTRA_DIST += $(top_srcdir)/testing/misc_tools.c
endif
================================================
FILE: testing/Messenger_test.c
================================================
/* Messenger test
*
* This program adds a friend and accepts all friend requests with the proper message.
*
* It tries sending a message to the added friend.
*
* If it receives a message from a friend it replies back.
*
*
* This is how I compile it: gcc -O2 -Wall -D VANILLA_NACL -o test ../core/Lossless_UDP.c ../core/network.c ../core/net_crypto.c ../core/Messenger.c ../core/DHT.c ../nacl/build/${HOSTNAME%.*}/lib/amd64/{cpucycles.o,libnacl.a,randombytes.o} Messenger_test.c
*
*
* Command line arguments are the ip, port and public_key of a node (for bootstrapping).
*
* EX: ./test 127.0.0.1 33445 CDCFD319CE3460824B33BE58FD86B8941C9585181D8FBD7C79C5721D7C2E9F7C
*
* Or the argument can be the path to the save file.
*
* EX: ./test Save.bak
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "../toxcore/Messenger.h"
#include "misc_tools.c"
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
#define c_sleep(x) Sleep(1*x)
#else
#include
#include
#define c_sleep(x) usleep(1000*x)
#define PORT 33445
#endif
void print_message(Messenger *m, uint32_t friendnumber, unsigned int type, const uint8_t *string, size_t length,
void *userdata)
{
printf("Message with length %lu received from %u: %s \n", length, friendnumber, string);
m_send_message_generic(m, friendnumber, type, (uint8_t *)"Test1", 6, 0);
}
/* FIXME needed as print_request has to match the interface expected by
* networking_requesthandler and so cannot take a Messenger * */
static Messenger *m;
void print_request(Messenger *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata)
{
printf("Friend request received from: \n");
printf("ClientID: ");
uint32_t j;
for (j = 0; j < 32; j++) {
if (public_key[j] < 16)
printf("0");
printf("%hhX", public_key[j]);
}
printf("\nOf length: %lu with data: %s \n", length, data);
if (length != sizeof("Install Gentoo")) {
return;
}
if (memcmp(data , "Install Gentoo", sizeof("Install Gentoo")) == 0 )
//if the request contained the message of peace the person is obviously a friend so we add him.
{
printf("Friend request accepted.\n");
m_addfriend_norequest(m, public_key);
}
}
int main(int argc, char *argv[])
{
/* let user override default by cmdline */
uint8_t ipv6enabled = TOX_ENABLE_IPV6_DEFAULT; /* x */
int argvoffset = cmdline_parsefor_ipv46(argc, argv, &ipv6enabled);
if (argvoffset < 0)
exit(1);
/* with optional --ipvx, now it can be 1-4 arguments... */
if ((argc != argvoffset + 2) && (argc != argvoffset + 4)) {
printf("Usage: %s [--ipv4|--ipv6] ip port public_key (of the DHT bootstrap node)\n", argv[0]);
printf("or\n");
printf(" %s [--ipv4|--ipv6] Save.bak (to read Save.bak as state file)\n", argv[0]);
exit(0);
}
Messenger_Options options = {0};
options.ipv6enabled = ipv6enabled;
m = new_messenger(&options, 0);
if ( !m ) {
fputs("Failed to allocate messenger datastructure\n", stderr);
exit(0);
}
if (argc == argvoffset + 4) {
uint16_t port = htons(atoi(argv[argvoffset + 2]));
uint8_t *bootstrap_key = hex_string_to_bin(argv[argvoffset + 3]);
int res = DHT_bootstrap_from_address(m->dht, argv[argvoffset + 1],
ipv6enabled, port, bootstrap_key);
free(bootstrap_key);
if (!res) {
printf("Failed to convert \"%s\" into an IP address. Exiting...\n", argv[argvoffset + 1]);
exit(1);
}
} else {
FILE *file = fopen(argv[argvoffset + 1], "rb");
if ( file == NULL ) {
printf("Failed to open \"%s\" - does it exist?\n", argv[argvoffset + 1]);
return 1;
}
int read;
uint8_t buffer[128000];
read = fread(buffer, 1, 128000, file);
printf("Messenger loaded: %i\n", messenger_load(m, buffer, read));
fclose(file);
}
m_callback_friendrequest(m, print_request, NULL);
m_callback_friendmessage(m, print_message, NULL);
printf("OUR ID: ");
uint32_t i;
uint8_t address[FRIEND_ADDRESS_SIZE];
getaddress(m, address);
for (i = 0; i < FRIEND_ADDRESS_SIZE; i++) {
if (address[i] < 16)
printf("0");
printf("%hhX", address[i]);
}
setname(m, (uint8_t *)"Anon", 5);
char temp_hex_id[128];
printf("\nEnter the address of the friend you wish to add (38 bytes HEX format):\n");
if (!fgets(temp_hex_id, sizeof(temp_hex_id), stdin))
exit(0);
if ((strlen(temp_hex_id) > 0) && (temp_hex_id[strlen(temp_hex_id) - 1] == '\n'))
temp_hex_id[strlen(temp_hex_id) - 1] = '\0';
uint8_t *bin_id = hex_string_to_bin(temp_hex_id);
int num = m_addfriend(m, bin_id, (uint8_t *)"Install Gentoo", sizeof("Install Gentoo"));
free(bin_id);
perror("Initialization");
while (1) {
uint8_t name[128];
getname(m, num, name);
printf("%s\n", name);
m_send_message_generic(m, num, MESSAGE_NORMAL, (uint8_t *)"Test", 5, 0);
do_messenger(m);
c_sleep(30);
FILE *file = fopen("Save.bak", "wb");
if ( file == NULL ) {
return 1;
}
uint8_t *buffer = malloc(messenger_size(m));
messenger_save(m, buffer);
size_t write_result = fwrite(buffer, 1, messenger_size(m), file);
if (write_result < messenger_size(m)) {
return 1;
}
free(buffer);
fclose(file);
}
kill_messenger(m);
}
================================================
FILE: testing/av_test.c
================================================
/** av_test.c
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
* Compile with (Linux only; in newly created directory toxcore/dir_name):
* gcc -o av_test ../toxav/av_test.c ../build/.libs/libtox*.a -lopencv_core \
* -lopencv_highgui -lopencv_imgproc -lsndfile -pthread -lvpx -lopus -lsodium -lportaudio
*/
#include "../toxav/toxav.h"
#include "../toxcore/tox.h"
#include "../toxcore/util.h"
#include "../toxcore/network.h" /* current_time_monotonic() */
/* Playing audio data */
#include
/* Reading audio */
#include
/* Reading and Displaying video data */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define c_sleep(x) usleep(1000*x)
#define CLIP(X) ((X) > 255 ? 255 : (X) < 0 ? 0 : X)
// RGB -> YUV
#define RGB2Y(R, G, B) CLIP((( 66 * (R) + 129 * (G) + 25 * (B) + 128) >> 8) + 16)
#define RGB2U(R, G, B) CLIP(((-38 * (R) - 74 * (G) + 112 * (B) + 128) >> 8) + 128)
#define RGB2V(R, G, B) CLIP(((112 * (R) - 94 * (G) - 18 * (B) + 128) >> 8) + 128)
// YUV -> RGB
#define C(Y) ((Y) - 16 )
#define D(U) ((U) - 128 )
#define E(V) ((V) - 128 )
#define YUV2R(Y, U, V) CLIP((298 * C(Y) + 409 * E(V) + 128) >> 8)
#define YUV2G(Y, U, V) CLIP((298 * C(Y) - 100 * D(U) - 208 * E(V) + 128) >> 8)
#define YUV2B(Y, U, V) CLIP((298 * C(Y) + 516 * D(U) + 128) >> 8)
#define TEST_TRANSFER_A 0
#define TEST_TRANSFER_V 1
typedef struct {
bool incoming;
uint32_t state;
pthread_mutex_t arb_mutex[1];
RingBuffer *arb; /* Audio ring buffer */
} CallControl;
struct toxav_thread_data {
ToxAV *AliceAV;
ToxAV *BobAV;
int32_t sig;
};
const char *vdout = "AV Test"; /* Video output */
PaStream *adout = NULL; /* Audio output */
typedef struct {
uint16_t size;
int16_t data[];
} frame;
void *pa_write_thread (void *d)
{
/* The purpose of this thread is to make sure Pa_WriteStream will not block
* toxav_iterate thread
*/
CallControl *cc = d;
while (Pa_IsStreamActive(adout)) {
frame *f;
pthread_mutex_lock(cc->arb_mutex);
if (rb_read(cc->arb, (void **)&f)) {
pthread_mutex_unlock(cc->arb_mutex);
Pa_WriteStream(adout, f->data, f->size);
free(f);
} else {
pthread_mutex_unlock(cc->arb_mutex);
c_sleep(10);
}
}
}
/**
* Callbacks
*/
void t_toxav_call_cb(ToxAV *av, uint32_t friend_number, bool audio_enabled, bool video_enabled, void *user_data)
{
printf("Handling CALL callback\n");
((CallControl *)user_data)->incoming = true;
}
void t_toxav_call_state_cb(ToxAV *av, uint32_t friend_number, uint32_t state, void *user_data)
{
printf("Handling CALL STATE callback: %d\n", state);
((CallControl *)user_data)->state = state;
}
void t_toxav_receive_video_frame_cb(ToxAV *av, uint32_t friend_number,
uint16_t width, uint16_t height,
uint8_t const *y, uint8_t const *u, uint8_t const *v,
int32_t ystride, int32_t ustride, int32_t vstride,
void *user_data)
{
ystride = abs(ystride);
ustride = abs(ustride);
vstride = abs(vstride);
uint16_t *img_data = malloc(height * width * 6);
unsigned long int i, j;
for (i = 0; i < height; ++i) {
for (j = 0; j < width; ++j) {
uint8_t *point = (uint8_t *) img_data + 3 * ((i * width) + j);
int yx = y[(i * ystride) + j];
int ux = u[((i / 2) * ustride) + (j / 2)];
int vx = v[((i / 2) * vstride) + (j / 2)];
point[0] = YUV2R(yx, ux, vx);
point[1] = YUV2G(yx, ux, vx);
point[2] = YUV2B(yx, ux, vx);
}
}
CvMat mat = cvMat(height, width, CV_8UC3, img_data);
CvSize sz = {.height = height, .width = width};
IplImage *header = cvCreateImageHeader(sz, 1, 3);
IplImage *img = cvGetImage(&mat, header);
cvShowImage(vdout, img);
free(img_data);
}
void t_toxav_receive_audio_frame_cb(ToxAV *av, uint32_t friend_number,
int16_t const *pcm,
size_t sample_count,
uint8_t channels,
uint32_t sampling_rate,
void *user_data)
{
CallControl *cc = user_data;
frame *f = malloc(sizeof(uint16_t) + sample_count * sizeof(int16_t) * channels);
memcpy(f->data, pcm, sample_count * sizeof(int16_t) * channels);
f->size = sample_count;
pthread_mutex_lock(cc->arb_mutex);
free(rb_write(cc->arb, f));
pthread_mutex_unlock(cc->arb_mutex);
}
void t_toxav_bit_rate_status_cb(ToxAV *av, uint32_t friend_number,
uint32_t audio_bit_rate, uint32_t video_bit_rate,
void *user_data)
{
printf ("Suggested bit rates: audio: %d video: %d\n", audio_bit_rate, video_bit_rate);
}
void t_accept_friend_request_cb(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata)
{
if (length == 7 && memcmp("gentoo", data, 7) == 0) {
assert(tox_friend_add_norequest(m, public_key, NULL) != (uint32_t) ~0);
}
}
/**
*/
void initialize_tox(Tox **bootstrap, ToxAV **AliceAV, CallControl *AliceCC, ToxAV **BobAV, CallControl *BobCC)
{
Tox *Alice;
Tox *Bob;
struct Tox_Options opts;
tox_options_default(&opts);
opts.end_port = 0;
opts.ipv6_enabled = false;
{
TOX_ERR_NEW error;
opts.start_port = 33445;
*bootstrap = tox_new(&opts, &error);
assert(error == TOX_ERR_NEW_OK);
opts.start_port = 33455;
Alice = tox_new(&opts, &error);
assert(error == TOX_ERR_NEW_OK);
opts.start_port = 33465;
Bob = tox_new(&opts, &error);
assert(error == TOX_ERR_NEW_OK);
}
printf("Created 3 instances of Tox\n");
printf("Preparing network...\n");
long long unsigned int cur_time = time(NULL);
uint32_t to_compare = 974536;
uint8_t address[TOX_ADDRESS_SIZE];
tox_callback_friend_request(Alice, t_accept_friend_request_cb, &to_compare);
tox_self_get_address(Alice, address);
assert(tox_friend_add(Bob, address, (uint8_t *)"gentoo", 7, NULL) != (uint32_t) ~0);
uint8_t off = 1;
while (1) {
tox_iterate(*bootstrap);
tox_iterate(Alice);
tox_iterate(Bob);
if (tox_self_get_connection_status(*bootstrap) &&
tox_self_get_connection_status(Alice) &&
tox_self_get_connection_status(Bob) && off) {
printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time);
off = 0;
}
if (tox_friend_get_connection_status(Alice, 0, NULL) == TOX_CONNECTION_UDP &&
tox_friend_get_connection_status(Bob, 0, NULL) == TOX_CONNECTION_UDP)
break;
c_sleep(20);
}
TOXAV_ERR_NEW rc;
*AliceAV = toxav_new(Alice, &rc);
assert(rc == TOXAV_ERR_NEW_OK);
*BobAV = toxav_new(Bob, &rc);
assert(rc == TOXAV_ERR_NEW_OK);
/* Alice */
toxav_callback_call(*AliceAV, t_toxav_call_cb, AliceCC);
toxav_callback_call_state(*AliceAV, t_toxav_call_state_cb, AliceCC);
toxav_callback_bit_rate_status(*AliceAV, t_toxav_bit_rate_status_cb, AliceCC);
toxav_callback_video_receive_frame(*AliceAV, t_toxav_receive_video_frame_cb, AliceCC);
toxav_callback_audio_receive_frame(*AliceAV, t_toxav_receive_audio_frame_cb, AliceCC);
/* Bob */
toxav_callback_call(*BobAV, t_toxav_call_cb, BobCC);
toxav_callback_call_state(*BobAV, t_toxav_call_state_cb, BobCC);
toxav_callback_bit_rate_status(*BobAV, t_toxav_bit_rate_status_cb, BobCC);
toxav_callback_video_receive_frame(*BobAV, t_toxav_receive_video_frame_cb, BobCC);
toxav_callback_audio_receive_frame(*BobAV, t_toxav_receive_audio_frame_cb, BobCC);
printf("Created 2 instances of ToxAV\n");
printf("All set after %llu seconds!\n", time(NULL) - cur_time);
}
int iterate_tox(Tox *bootstrap, ToxAV *AliceAV, ToxAV *BobAV)
{
tox_iterate(bootstrap);
tox_iterate(toxav_get_tox(AliceAV));
tox_iterate(toxav_get_tox(BobAV));
return MIN(tox_iteration_interval(toxav_get_tox(AliceAV)), tox_iteration_interval(toxav_get_tox(BobAV)));
}
void *iterate_toxav (void *data)
{
struct toxav_thread_data *data_cast = data;
#if defined TEST_TRANSFER_V && TEST_TRANSFER_V == 1
cvNamedWindow(vdout, CV_WINDOW_AUTOSIZE);
#endif
while (data_cast->sig == 0) {
toxav_iterate(data_cast->AliceAV);
toxav_iterate(data_cast->BobAV);
int rc = MIN(toxav_iteration_interval(data_cast->AliceAV), toxav_iteration_interval(data_cast->BobAV));
printf("\rIteration interval: %d ", rc);
fflush(stdout);
#if defined TEST_TRANSFER_V && TEST_TRANSFER_V == 1
if (!rc)
rc = 1;
cvWaitKey(rc);
#else
c_sleep(rc);
#endif
}
data_cast->sig = 1;
#if defined TEST_TRANSFER_V && TEST_TRANSFER_V == 1
cvDestroyWindow(vdout);
#endif
pthread_exit(NULL);
}
int send_opencv_img(ToxAV *av, uint32_t friend_number, const IplImage *img)
{
int32_t strides[3] = { 1280, 640, 640 };
uint8_t *planes[3] = {
malloc(img->height * img->width),
malloc(img->height * img->width / 4),
malloc(img->height * img->width / 4),
};
int x_chroma_shift = 1;
int y_chroma_shift = 1;
int x, y;
for (y = 0; y < img->height; ++y) {
for (x = 0; x < img->width; ++x) {
uint8_t r = img->imageData[(x + y * img->width) * 3 + 0];
uint8_t g = img->imageData[(x + y * img->width) * 3 + 1];
uint8_t b = img->imageData[(x + y * img->width) * 3 + 2];
planes[0][x + y * strides[0]] = RGB2Y(r, g, b);
if (!(x % (1 << x_chroma_shift)) && !(y % (1 << y_chroma_shift))) {
const int i = x / (1 << x_chroma_shift);
const int j = y / (1 << y_chroma_shift);
planes[1][i + j * strides[1]] = RGB2U(r, g, b);
planes[2][i + j * strides[2]] = RGB2V(r, g, b);
}
}
}
int rc = toxav_video_send_frame(av, friend_number, img->width, img->height,
planes[0], planes[1], planes[2], NULL);
free(planes[0]);
free(planes[1]);
free(planes[2]);
return rc;
}
int print_audio_devices()
{
int i = 0;
for (i = 0; i < Pa_GetDeviceCount(); ++i) {
const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
if (info)
printf("%d) %s\n", i, info->name);
}
return 0;
}
int print_help (const char *name)
{
printf("Usage: %s -[a:v:o:dh]\n"
"-a audio input file\n"
"-b audio frame duration\n"
"-v video input file\n"
"-x video frame duration\n"
"-o output audio device index\n"
"-d print output audio devices\n"
"-h print this help\n", name);
return 0;
}
int main (int argc, char **argv)
{
freopen("/dev/zero", "w", stderr);
Pa_Initialize();
struct stat st;
/* AV files for testing */
const char *af_name = NULL;
const char *vf_name = NULL;
long audio_out_dev_idx = -1;
int32_t audio_frame_duration = 20;
int32_t video_frame_duration = 10;
/* Parse settings */
CHECK_ARG:
switch (getopt(argc, argv, "a:b:v:x:o:dh")) {
case 'a':
af_name = optarg;
goto CHECK_ARG;
case 'b': {
char *d;
audio_frame_duration = strtol(optarg, &d, 10);
if (*d) {
printf("Invalid value for argument: 'b'");
exit(1);
}
goto CHECK_ARG;
}
case 'v':
vf_name = optarg;
goto CHECK_ARG;
case 'x': {
char *d;
video_frame_duration = strtol(optarg, &d, 10);
if (*d) {
printf("Invalid value for argument: 'x'");
exit(1);
}
goto CHECK_ARG;
}
case 'o': {
char *d;
audio_out_dev_idx = strtol(optarg, &d, 10);
if (*d) {
printf("Invalid value for argument: 'o'");
exit(1);
}
goto CHECK_ARG;
}
case 'd':
return print_audio_devices();
case 'h':
return print_help(argv[0]);
case '?':
exit(1);
case -1:
;
}
{ /* Check files */
if (!af_name) {
printf("Required audio input file!\n");
exit(1);
}
if (!vf_name) {
printf("Required video input file!\n");
exit(1);
}
/* Check for files */
if (stat(af_name, &st) != 0 || !S_ISREG(st.st_mode)) {
printf("%s doesn't seem to be a regular file!\n", af_name);
exit(1);
}
if (stat(vf_name, &st) != 0 || !S_ISREG(st.st_mode)) {
printf("%s doesn't seem to be a regular file!\n", vf_name);
exit(1);
}
}
if (audio_out_dev_idx < 0)
audio_out_dev_idx = Pa_GetDefaultOutputDevice();
const PaDeviceInfo *audio_dev = Pa_GetDeviceInfo(audio_out_dev_idx);
if (!audio_dev) {
fprintf(stderr, "Device under index: %ld invalid", audio_out_dev_idx);
return 1;
}
printf("Using audio device: %s\n", audio_dev->name);
printf("Using audio file: %s\n", af_name);
printf("Using video file: %s\n", vf_name);
/* START TOX NETWORK */
Tox *bootstrap;
ToxAV *AliceAV;
ToxAV *BobAV;
CallControl AliceCC;
CallControl BobCC;
initialize_tox(&bootstrap, &AliceAV, &AliceCC, &BobAV, &BobCC);
if (TEST_TRANSFER_A) {
SNDFILE *af_handle;
SF_INFO af_info;
printf("\nTrying audio enc/dec...\n");
memset(&AliceCC, 0, sizeof(CallControl));
memset(&BobCC, 0, sizeof(CallControl));
pthread_mutex_init(AliceCC.arb_mutex, NULL);
pthread_mutex_init(BobCC.arb_mutex, NULL);
AliceCC.arb = rb_new(16);
BobCC.arb = rb_new(16);
{ /* Call */
TOXAV_ERR_CALL rc;
toxav_call(AliceAV, 0, 48, 0, &rc);
if (rc != TOXAV_ERR_CALL_OK) {
printf("toxav_call failed: %d\n", rc);
exit(1);
}
}
while (!BobCC.incoming)
iterate_tox(bootstrap, AliceAV, BobAV);
{ /* Answer */
TOXAV_ERR_ANSWER rc;
toxav_answer(BobAV, 0, 48, 0, &rc);
if (rc != TOXAV_ERR_ANSWER_OK) {
printf("toxav_answer failed: %d\n", rc);
exit(1);
}
}
while (AliceCC.state == 0)
iterate_tox(bootstrap, AliceAV, BobAV);
/* Open audio file */
af_handle = sf_open(af_name, SFM_READ, &af_info);
if (af_handle == NULL) {
printf("Failed to open the file.\n");
exit(1);
}
int16_t PCM[5760];
time_t start_time = time(NULL);
time_t expected_time = af_info.frames / af_info.samplerate + 2;
/* Start decode thread */
struct toxav_thread_data data = {
.AliceAV = AliceAV,
.BobAV = BobAV,
.sig = 0
};
pthread_t dect;
pthread_create(&dect, NULL, iterate_toxav, &data);
pthread_detach(dect);
int frame_size = (af_info.samplerate * audio_frame_duration / 1000) * af_info.channels;
struct PaStreamParameters output;
output.device = audio_out_dev_idx;
output.channelCount = af_info.channels;
output.sampleFormat = paInt16;
output.suggestedLatency = audio_dev->defaultHighOutputLatency;
output.hostApiSpecificStreamInfo = NULL;
PaError err = Pa_OpenStream(&adout, NULL, &output, af_info.samplerate, frame_size, paNoFlag, NULL, NULL);
assert(err == paNoError);
err = Pa_StartStream(adout);
assert(err == paNoError);
// toxav_audio_bit_rate_set(AliceAV, 0, 64, false, NULL);
/* Start write thread */
pthread_t t;
pthread_create(&t, NULL, pa_write_thread, &BobCC);
pthread_detach(t);
printf("Sample rate %d\n", af_info.samplerate);
while (start_time + expected_time > time(NULL) ) {
uint64_t enc_start_time = current_time_monotonic();
int64_t count = sf_read_short(af_handle, PCM, frame_size);
if (count > 0) {
TOXAV_ERR_SEND_FRAME rc;
if (toxav_audio_send_frame(AliceAV, 0, PCM, count / af_info.channels, af_info.channels, af_info.samplerate,
&rc) == false) {
printf("Error sending frame of size %ld: %d\n", count, rc);
}
}
iterate_tox(bootstrap, AliceAV, BobAV);
c_sleep(abs(audio_frame_duration - (current_time_monotonic() - enc_start_time) - 1));
}
printf("Played file in: %lu; stopping stream...\n", time(NULL) - start_time);
Pa_StopStream(adout);
sf_close(af_handle);
{ /* Hangup */
TOXAV_ERR_CALL_CONTROL rc;
toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc);
if (rc != TOXAV_ERR_CALL_CONTROL_OK) {
printf("toxav_call_control failed: %d\n", rc);
exit(1);
}
}
iterate_tox(bootstrap, AliceAV, BobAV);
assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED);
/* Stop decode thread */
data.sig = -1;
while (data.sig != 1)
pthread_yield();
pthread_mutex_destroy(AliceCC.arb_mutex);
pthread_mutex_destroy(BobCC.arb_mutex);
void *f = NULL;
while (rb_read(AliceCC.arb, &f))
free(f);
while (rb_read(BobCC.arb, &f))
free(f);
printf("Success!");
}
if (TEST_TRANSFER_V) {
printf("\nTrying video enc/dec...\n");
memset(&AliceCC, 0, sizeof(CallControl));
memset(&BobCC, 0, sizeof(CallControl));
{ /* Call */
TOXAV_ERR_CALL rc;
toxav_call(AliceAV, 0, 0, 2000, &rc);
if (rc != TOXAV_ERR_CALL_OK) {
printf("toxav_call failed: %d\n", rc);
exit(1);
}
}
while (!BobCC.incoming)
iterate_tox(bootstrap, AliceAV, BobAV);
{ /* Answer */
TOXAV_ERR_ANSWER rc;
toxav_answer(BobAV, 0, 0, 5000, &rc);
if (rc != TOXAV_ERR_ANSWER_OK) {
printf("toxav_answer failed: %d\n", rc);
exit(1);
}
}
iterate_tox(bootstrap, AliceAV, BobAV);
/* Start decode thread */
struct toxav_thread_data data = {
.AliceAV = AliceAV,
.BobAV = BobAV,
.sig = 0
};
pthread_t dect;
pthread_create(&dect, NULL, iterate_toxav, &data);
pthread_detach(dect);
CvCapture *capture = cvCreateFileCapture(vf_name);
if (!capture) {
printf("Failed to open video file: %s\n", vf_name);
exit(1);
}
// toxav_video_bit_rate_set(AliceAV, 0, 5000, false, NULL);
time_t start_time = time(NULL);
while (start_time + 90 > time(NULL)) {
IplImage *frame = cvQueryFrame(capture );
if (!frame)
break;
send_opencv_img(AliceAV, 0, frame);
iterate_tox(bootstrap, AliceAV, BobAV);
c_sleep(10);
}
cvReleaseCapture(&capture);
{ /* Hangup */
TOXAV_ERR_CALL_CONTROL rc;
toxav_call_control(AliceAV, 0, TOXAV_CALL_CONTROL_CANCEL, &rc);
if (rc != TOXAV_ERR_CALL_CONTROL_OK) {
printf("toxav_call_control failed: %d\n", rc);
exit(1);
}
}
iterate_tox(bootstrap, AliceAV, BobAV);
assert(BobCC.state == TOXAV_FRIEND_CALL_STATE_FINISHED);
/* Stop decode thread */
printf("Stopping decode thread\n");
data.sig = -1;
while (data.sig != 1)
pthread_yield();
printf("Success!");
}
Tox *Alice = toxav_get_tox(AliceAV);
Tox *Bob = toxav_get_tox(BobAV);
toxav_kill(BobAV);
toxav_kill(AliceAV);
tox_kill(Bob);
tox_kill(Alice);
tox_kill(bootstrap);
printf("\nTest successful!\n");
Pa_Terminate();
return 0;
}
================================================
FILE: testing/dns3_test.c
================================================
#include "../toxdns/toxdns.h"
#include "../toxcore/tox.h"
#include "../toxcore/network.h"
#include "misc_tools.c"
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
#define c_sleep(x) Sleep(1*x)
#else
#define c_sleep(x) usleep(1000*x)
#endif
uint32_t create_packet(uint8_t *packet, uint8_t *string, uint8_t str_len, uint8_t id)
{
memset(packet, 0, str_len + 13 + 16);
packet[0] = id;
packet[1] = rand();
packet[5] = 1;
packet[11] = 1;
packet[12] = '.';
memcpy(packet + 13, string, str_len);
uint32_t i, c = 0;
for (i = str_len + 12; i != 11; --i) {
if (packet[i] == '.') {
packet[i] = c;
c = 0;
} else {
++c;
}
}
packet[str_len + 13 + 2] = 16;
packet[str_len + 13 + 4] = 1;
packet[str_len + 13 + 7] = 0x29;
packet[str_len + 13 + 8] = 16;
packet[str_len + 13 + 12] = 0x80;
return str_len + 13 + 16;
}
int main(int argc, char *argv[])
{
if (argc < 4) {
printf("Usage: %s domain domain_public_key queried_username\nEX: %s utox.org D3154F65D28A5B41A05D4AC7E4B39C6B1C233CC857FB365C56E8392737462A12 username\n",
argv[0], argv[0]);
exit(0);
}
IP ip = {0};
ip.family = AF_INET;
sock_t sock = socket(ip.family, SOCK_DGRAM, IPPROTO_UDP);
if (!sock_valid(sock))
return -1;
if (!addr_resolve_or_parse_ip(argv[1], &ip, 0))
return -1;
struct sockaddr_in target;
size_t addrsize = sizeof(struct sockaddr_in);
target.sin_family = AF_INET;
target.sin_addr = ip.ip4.in_addr;
target.sin_port = htons(53);
uint8_t string[1024] = {0};
void *d = tox_dns3_new(hex_string_to_bin(argv[2]));
unsigned int i;
uint32_t request_id;
/*
for (i = 0; i < 255; ++i) {
tox_generate_dns3_string(d, string, sizeof(string), &request_id, string, i);
printf("%s\n", string);
}*/
int len = tox_generate_dns3_string(d, string + 1, sizeof(string) - 1, &request_id, (uint8_t *)argv[3], strlen(argv[3]));
if (len == -1)
return -1;
string[0] = '_';
memcpy(string + len + 1, "._tox.", sizeof("._tox."));
memcpy((char *)(string + len + 1 + sizeof("._tox.") - 1), argv[1], strlen(argv[1]));
uint8_t packet[512];
uint8_t id = rand();
uint32_t p_len = create_packet(packet, string, strlen((char *)string), id);
if (sendto(sock, (char *) packet, p_len, 0, (struct sockaddr *)&target, addrsize) != p_len)
return -1;
uint8_t buffer[512] = {};
int r_len = recv(sock, buffer, sizeof(buffer), 0);
if (r_len < (int)p_len)
return -1;
for (i = r_len - 1; i != 0 && buffer[i] != '='; --i);
uint8_t tox_id[TOX_ADDRESS_SIZE];
if (tox_decrypt_dns3_TXT(d, tox_id, buffer + i + 1, r_len - (i + 1), request_id) != 0)
return -1;
printf("The Tox id for username %s is:\n", argv[3]);
//unsigned int i;
for (i = 0; i < TOX_ADDRESS_SIZE; ++i) {
printf("%02hhX", tox_id[i]);
}
printf("\n");
return 0;
}
================================================
FILE: testing/irc_syncbot.c
================================================
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__MACH__)
#define MSG_NOSIGNAL 0
#endif
//IRC name and channel.
#define IRC_NAME "Tox_syncbot"
#define IRC_CHANNEL "#tox-real-ontopic"
//IRC server ip and port.
uint8_t ip[4] = {127, 0, 0, 1};
uint16_t port = 6667;
#define SILENT_TIMEOUT 20
int sock;
#define SERVER_CONNECT "NICK "IRC_NAME"\nUSER "IRC_NAME" 8 * :"IRC_NAME"\n"
#define CHANNEL_JOIN "JOIN "IRC_CHANNEL"\n"
/* In toxcore/network.c */
uint64_t current_time_monotonic(void);
uint64_t get_monotime_sec(void)
{
return current_time_monotonic() / 1000;
}
int reconnect(void)
{
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
printf("error socket\n");
return -1;
}
struct sockaddr_storage addr = {0};
size_t addrsize;
struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
addrsize = sizeof(struct sockaddr_in);
addr4->sin_family = AF_INET;
memcpy(&addr4->sin_addr, ip, 4);
addr4->sin_port = htons(port);
if (connect(sock, (struct sockaddr *)&addr, addrsize) != 0) {
printf("error connect\n");
return -1;
}
send(sock, SERVER_CONNECT, sizeof(SERVER_CONNECT) - 1, MSG_NOSIGNAL);
return sock;
}
#include "../toxcore/tox.h"
#include "misc_tools.c"
int current_group = -1;
static void callback_group_invite(Tox *tox, int fid, uint8_t type, const uint8_t *data, uint16_t length, void *userdata)
{
if (current_group == -1)
current_group = tox_join_groupchat(tox, fid, data, length);
}
void callback_friend_message(Tox *tox, uint32_t fid, TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length,
void *userdata)
{
if (length == 1 && *message == 'c') {
if (tox_del_groupchat(tox, current_group) == 0)
current_group = -1;
}
if (length == 1 && *message == 'i') {
tox_invite_friend(tox, fid, current_group);
}
if (length == 1 && *message == 'j' && sock >= 0) {
send(sock, CHANNEL_JOIN, sizeof(CHANNEL_JOIN) - 1, MSG_NOSIGNAL);
}
}
static void copy_groupmessage(Tox *tox, int groupnumber, int friendgroupnumber, const uint8_t *message, uint16_t length,
void *userdata)
{
if (tox_group_peernumber_is_ours(tox, groupnumber, friendgroupnumber))
return;
uint8_t name[TOX_MAX_NAME_LENGTH];
int namelen = tox_group_peername(tox, groupnumber, friendgroupnumber, name);
if (namelen == 0 || namelen == -1) {
memcpy(name, "", 9);
namelen = 9;
}
uint8_t sendbuf[2048];
uint16_t send_len = 0;
memcpy(sendbuf, "PRIVMSG "IRC_CHANNEL" :", sizeof("PRIVMSG "IRC_CHANNEL" :"));
send_len += sizeof("PRIVMSG "IRC_CHANNEL" :") - 1;
memcpy(sendbuf + send_len, name, namelen);
send_len += namelen;
sendbuf[send_len] = ':';
send_len += 1;
sendbuf[send_len] = ' ';
send_len += 1;
memcpy(sendbuf + send_len, message, length);
send_len += length;
unsigned int i;
for (i = 0; i < send_len; ++i) {
if (sendbuf[i] == '\n')
sendbuf[i] = '|';
if (sendbuf[i] == 0)
sendbuf[i] = ' ';
}
sendbuf[send_len] = '\n';
send_len += 1;
if (sock >= 0)
send(sock, sendbuf, send_len, MSG_NOSIGNAL);
}
void send_irc_group(Tox *tox, uint8_t *msg, uint16_t len)
{
if (len > 1350 || len == 0 || len == 1)
return;
--len;
if (*msg != ':')
return;
uint8_t req[len];
unsigned int i;
unsigned int spaces = 0;
for (i = 0; i < (len - 1); ++i) {
if (msg[i + 1] == ' ') {
++spaces;
} else {
if (spaces >= 3 && msg[i + 1] == ':') {
break;
}
}
req[i] = msg[i + 1];
}
unsigned int req_len = i;
req[i] = 0;
uint8_t message[len];
uint16_t length = 0;
uint8_t *pmsg = (uint8_t *)strstr((char *)req, " PRIVMSG");
if (pmsg == NULL)
return;
uint8_t *dt = req;
for (dt = req, i = 0; dt != pmsg && *dt != '!'; ++dt, ++i) {
message[i] = *dt;
++length;
}
message[length] = ':';
length += 1;
message[length] = ' ';
length += 1;
if ((req_len + 2) >= len)
return;
memcpy(message + length, msg + req_len + 2, len - (req_len + 2));
length += len - (req_len + 2);
tox_group_message_send(tox, current_group, message, length);
}
Tox *init_tox(int argc, char *argv[])
{
uint8_t ipv6enabled = 1; /* x */
int argvoffset = cmdline_parsefor_ipv46(argc, argv, &ipv6enabled);
if (argvoffset < 0)
exit(1);
/* with optional --ipvx, now it can be 1-4 arguments... */
if ((argc != argvoffset + 2) && (argc != argvoffset + 4)) {
printf("Usage: %s [--ipv4|--ipv6] ip port public_key (of the DHT bootstrap node)\n", argv[0]);
exit(0);
}
Tox *tox = tox_new(0, 0);
if (!tox)
exit(1);
tox_self_set_name(tox, (uint8_t *)IRC_NAME, sizeof(IRC_NAME) - 1, 0);
tox_callback_friend_message(tox, &callback_friend_message, 0);
tox_callback_group_invite(tox, &callback_group_invite, 0);
tox_callback_group_message(tox, ©_groupmessage, 0);
tox_callback_group_action(tox, ©_groupmessage, 0);
char temp_id[128];
printf("\nEnter the address of irc_syncbots master (38 bytes HEX format):\n");
if (scanf("%s", temp_id) != 1) {
exit (1);
}
uint16_t port = atoi(argv[argvoffset + 2]);
unsigned char *binary_string = hex_string_to_bin(argv[argvoffset + 3]);
tox_bootstrap(tox, argv[argvoffset + 1], port, binary_string, 0);
free(binary_string);
uint8_t *bin_id = hex_string_to_bin(temp_id);
uint32_t num = tox_friend_add(tox, bin_id, (uint8_t *)"Install Gentoo", sizeof("Install Gentoo") - 1, 0);
free(bin_id);
if (num == UINT32_MAX) {
printf("\nSomething went wrong when adding friend.\n");
exit(1);
}
return tox;
}
int main(int argc, char *argv[])
{
Tox *tox = init_tox(argc, argv);
sock = reconnect();
if (sock < 0)
return 1;
uint64_t last_get = get_monotime_sec();
int connected = 0, ping_sent = 0;
while (1) {
int count = 0;
ioctl(sock, FIONREAD, &count);
if (count > 0) {
last_get = get_monotime_sec();
ping_sent = 0;
uint8_t data[count + 1];
data[count] = 0;
recv(sock, data, count, MSG_NOSIGNAL);
printf("%s", data);
if (!connected)
connected = 1;
if (count > 6 && data[0] == 'P' && data[1] == 'I' && data[2] == 'N' && data[3] == 'G') {
data[1] = 'O';
unsigned int i;
for (i = 0; i < count; ++i) {
if (data[i] == '\n') {
++i;
break;
}
}
send(sock, data, i, MSG_NOSIGNAL);
}
unsigned int i, p_i = 0;
for (i = 1; data[0] == ':' && i < count; ++i) {
if (data[i] == ' ') {
if (i + 5 < count && memcmp(data + i, " 404 ", 5) == 0) {
connected = 1;
}
break;
}
if (data[i] == ':')
break;
}
for (i = 0; i < count; ++i) {
if (data[i] == '\n' && i != 0) {
send_irc_group(tox, data + p_i, i - p_i);
p_i = i + 1;
}
}
}
if (connected == 1) {
send(sock, CHANNEL_JOIN, sizeof(CHANNEL_JOIN) - 1, MSG_NOSIGNAL);
connected = 2;
}
if (!ping_sent && last_get + (SILENT_TIMEOUT / 2) < get_monotime_sec()) {
unsigned int p_s = sizeof("PING :test\n") - 1;
if (send(sock, "PING :test\n", p_s, MSG_NOSIGNAL) == p_s)
ping_sent = 1;
}
int error = 0;
socklen_t len = sizeof (error);
if (sock < 0 || last_get + SILENT_TIMEOUT < get_monotime_sec()
|| getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len ) != 0) {
close(sock);
printf("reconnect\n");
sock = reconnect();
if (sock >= 0) {
last_get = get_monotime_sec();
connected = 0;
ping_sent = 0;
}
}
tox_iterate(tox);
usleep(1000 * 50);
}
return 0;
}
================================================
FILE: testing/misc_tools.c
================================================
/* misc_tools.c
*
* Miscellaneous functions and data structures for doing random things.
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include
#include
#include
#ifdef DEBUG
#include
#endif // DEBUG
// You are responsible for freeing the return value!
uint8_t *hex_string_to_bin(char *hex_string)
{
// byte is represented by exactly 2 hex digits, so lenth of binary string
// is half of that of the hex one. only hex string with even length
// valid. the more proper implementation would be to check if strlen(hex_string)
// is odd and return error code if it is. we assume strlen is even. if it's not
// then the last byte just won't be written in 'ret'.
size_t i, len = strlen(hex_string) / 2;
uint8_t *ret = malloc(len);
char *pos = hex_string;
for (i = 0; i < len; ++i, pos += 2)
sscanf(pos, "%2hhx", &ret[i]);
return ret;
}
int cmdline_parsefor_ipv46(int argc, char **argv, uint8_t *ipv6enabled)
{
int argvoffset = 0, argi;
for (argi = 1; argi < argc; argi++)
if (!strncasecmp(argv[argi], "--ipv", 5)) {
if (argv[argi][5] && !argv[argi][6]) {
char c = argv[argi][5];
if (c == '4')
*ipv6enabled = 0;
else if (c == '6')
*ipv6enabled = 1;
else {
printf("Invalid argument: %s. Try --ipv4 or --ipv6!\n", argv[argi]);
return -1;
}
} else {
printf("Invalid argument: %s. Try --ipv4 or --ipv6!\n", argv[argi]);
return -1;
}
if (argvoffset != argi - 1) {
printf("Argument must come first: %s.\n", argv[argi]);
return -1;
}
argvoffset++;
}
return argvoffset;
};
================================================
FILE: testing/nTox.c
================================================
/* nTox.c
*
* Textual frontend for Tox.
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
#define _WIN32_WINNT 0x501
#include
#include
#else
#include
#include
#include
#include
#include
#endif
#include
#include "nTox.h"
#include "misc_tools.c"
#include
#include
#include
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
#define c_sleep(x) Sleep(1*x)
#else
#include
#define c_sleep(x) usleep(1000*x)
#endif
char lines[HISTORY][STRING_LENGTH];
uint8_t flag[HISTORY];
char input_line[STRING_LENGTH];
/* wrap: continuation mark */
const size_t wrap_cont_len = 3;
const char wrap_cont_str[] = "\n+ ";
#define STRING_LENGTH_WRAPPED (STRING_LENGTH + 16 * (wrap_cont_len + 1))
/* documented: fdmnlsahxgiztq(c[rfg]) */
/* undocumented: d (tox_do()) */
/* 251+1 characters */
char *help_main =
"[i] Available main commands:\n+ "
"/x (to print one's own id)|"
"/s status (to change status, e.g. AFK)|"
"/n nick (to change your nickname)|"
"/q (to quit)|"
"/cr (to reset conversation)|"
"/h friend (for friend related commands)|"
"/h group (for group related commands)";
/* 190+1 characters */
char *help_friend1 =
"[i] Available friend commands (1/2):\n+ "
"/l list (to list friends)|"
"/r friend no. (to remove from the friend list)|"
"/f ID (to send a friend request)|"
"/a request no. (to accept a friend request)";
/* 187+1 characters */
char *help_friend2 =
"[i] Available friend commands (2/2):\n+ "
"/m friend no. message (to send a message)|"
"/t friend no. filename (to send a file to a friend)|"
"/cf friend no. (to talk to that friend per default)";
/* 253+1 characters */
char *help_group =
"[i] Available group commands:\n+ "
"/g (to create a group)|"
"/i friend no. group no. (to invite a friend to a group)|"
"/z group no. message (to send a message to a group)|"
"/p group no. (to list a group's peers)|"
"/cg group no. (to talk to that group per default)";
int x, y;
int conversation_default = 0;
typedef struct {
uint8_t id[TOX_PUBLIC_KEY_SIZE];
uint8_t accepted;
} Friend_request;
Friend_request pending_requests[256];
uint8_t num_requests = 0;
#define NUM_FILE_SENDERS 64
typedef struct {
FILE *file;
uint32_t friendnum;
uint32_t filenumber;
} File_Sender;
File_Sender file_senders[NUM_FILE_SENDERS];
uint8_t numfilesenders;
void tox_file_chunk_request(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, size_t length,
void *user_data)
{
unsigned int i;
for (i = 0; i < NUM_FILE_SENDERS; ++i) {
/* This is slow */
if (file_senders[i].file && file_senders[i].friendnum == friend_number && file_senders[i].filenumber == file_number) {
if (length == 0) {
fclose(file_senders[i].file);
file_senders[i].file = 0;
char msg[512];
sprintf(msg, "[t] %u file transfer: %u completed", file_senders[i].friendnum, file_senders[i].filenumber);
new_lines(msg);
break;
}
fseek(file_senders[i].file, position, SEEK_SET);
uint8_t data[length];
int len = fread(data, 1, length, file_senders[i].file);
tox_file_send_chunk(tox, friend_number, file_number, position, data, len, 0);
break;
}
}
}
uint32_t add_filesender(Tox *m, uint16_t friendnum, char *filename)
{
FILE *tempfile = fopen(filename, "rb");
if (tempfile == 0)
return -1;
fseek(tempfile, 0, SEEK_END);
uint64_t filesize = ftell(tempfile);
fseek(tempfile, 0, SEEK_SET);
uint32_t filenum = tox_file_send(m, friendnum, TOX_FILE_KIND_DATA, filesize, 0, (uint8_t *)filename,
strlen(filename), 0);
if (filenum == -1)
return -1;
file_senders[numfilesenders].file = tempfile;
file_senders[numfilesenders].friendnum = friendnum;
file_senders[numfilesenders].filenumber = filenum;
++numfilesenders;
return filenum;
}
#define FRADDR_TOSTR_CHUNK_LEN 8
#define FRADDR_TOSTR_BUFSIZE (TOX_ADDRESS_SIZE * 2 + TOX_ADDRESS_SIZE / FRADDR_TOSTR_CHUNK_LEN + 1)
static void fraddr_to_str(uint8_t *id_bin, char *id_str)
{
uint32_t i, delta = 0, pos_extra, sum_extra = 0;
for (i = 0; i < TOX_ADDRESS_SIZE; i++) {
sprintf(&id_str[2 * i + delta], "%02hhX", id_bin[i]);
if ((i + 1) == TOX_PUBLIC_KEY_SIZE)
pos_extra = 2 * (i + 1) + delta;
if (i >= TOX_PUBLIC_KEY_SIZE)
sum_extra |= id_bin[i];
if (!((i + 1) % FRADDR_TOSTR_CHUNK_LEN)) {
id_str[2 * (i + 1) + delta] = ' ';
delta++;
}
}
id_str[2 * i + delta] = 0;
if (!sum_extra)
id_str[pos_extra] = 0;
}
void get_id(Tox *m, char *data)
{
sprintf(data, "[i] ID: ");
int offset = strlen(data);
uint8_t address[TOX_ADDRESS_SIZE];
tox_self_get_address(m, address);
fraddr_to_str(address, data + offset);
}
int getfriendname_terminated(Tox *m, int friendnum, char *namebuf)
{
tox_friend_get_name(m, friendnum, (uint8_t *)namebuf, NULL);
int res = tox_friend_get_name_size(m, friendnum, NULL);
if (res >= 0)
namebuf[res] = 0;
else
namebuf[0] = 0;
return res;
}
void new_lines_mark(char *line, uint8_t special)
{
int i = 0;
for (i = HISTORY - 1; i > 0; i--) {
strncpy(lines[i], lines[i - 1], STRING_LENGTH - 1);
flag[i] = flag[i - 1];
}
strncpy(lines[0], line, STRING_LENGTH - 1);
flag[i] = special;
do_refresh();
}
void new_lines(char *line)
{
new_lines_mark(line, 0);
}
const char ptrn_friend[] = "[i] Friend %i: %s\n+ id: %s";
const int id_str_len = TOX_ADDRESS_SIZE * 2 + 3;
void print_friendlist(Tox *m)
{
new_lines("[i] Friend List:");
char name[TOX_MAX_NAME_LENGTH + 1];
uint8_t fraddr_bin[TOX_ADDRESS_SIZE];
char fraddr_str[FRADDR_TOSTR_BUFSIZE];
/* account for the longest name and the longest "base" string and number (int) and id_str */
char fstring[TOX_MAX_NAME_LENGTH + strlen(ptrn_friend) + 21 + id_str_len];
uint32_t i = 0;
while (getfriendname_terminated(m, i, name) != -1) {
if (tox_friend_get_public_key(m, i, fraddr_bin, NULL))
fraddr_to_str(fraddr_bin, fraddr_str);
else
sprintf(fraddr_str, "???");
if (strlen(name) <= 0) {
sprintf(fstring, ptrn_friend, i, "No name?", fraddr_str);
} else {
sprintf(fstring, ptrn_friend, i, (uint8_t *)name, fraddr_str);
}
i++;
new_lines(fstring);
}
if (i == 0)
new_lines("+ no friends! D:");
}
static int fmtmsg_tm_mday = -1;
static void print_formatted_message(Tox *m, char *message, int friendnum, uint8_t outgoing)
{
char name[TOX_MAX_NAME_LENGTH + 1];
getfriendname_terminated(m, friendnum, name);
char msg[100 + strlen(message) + strlen(name) + 1];
time_t rawtime;
struct tm *timeinfo;
time ( &rawtime );
timeinfo = localtime ( &rawtime );
/* assume that printing the date once a day is enough */
if (fmtmsg_tm_mday != timeinfo->tm_mday) {
fmtmsg_tm_mday = timeinfo->tm_mday;
/* strftime(msg, 100, "Today is %a %b %d %Y.", timeinfo); */
/* %x is the locale's preferred date format */
strftime(msg, 100, "Today is %x.", timeinfo);
new_lines(msg);
}
char time[64];
/* strftime(time, 64, "%I:%M:%S %p", timeinfo); */
/* %X is the locale's preferred time format */
strftime(time, 64, "%X", timeinfo);
if (outgoing) {
/* tgt: friend */
sprintf(msg, "[%d] %s =>{%s} %s", friendnum, time, name, message);
} else {
/* src: friend */
sprintf(msg, "[%d] %s <%s>: %s", friendnum, time, name, message);
}
new_lines(msg);
}
/* forward declarations */
static int save_data(Tox *m);
void print_groupchatpeers(Tox *m, int groupnumber);
void line_eval(Tox *m, char *line)
{
if (line[0] == '/') {
char inpt_command = line[1];
char prompt[STRING_LENGTH + 2] = "> ";
int prompt_offset = 3;
strcat(prompt, line);
new_lines(prompt);
if (inpt_command == 'f') { // add friend command: /f ID
int i, delta = 0;
char temp_id[128];
for (i = 0; i < 128; i++) {
temp_id[i - delta] = line[i + prompt_offset];
if ((temp_id[i - delta] == ' ') || (temp_id[i - delta] == '+'))
delta++;
}
unsigned char *bin_string = hex_string_to_bin(temp_id);
TOX_ERR_FRIEND_ADD error;
uint32_t num = tox_friend_add(m, bin_string, (uint8_t *)"Install Gentoo", sizeof("Install Gentoo"), &error);
free(bin_string);
char numstring[100];
switch (error) {
case TOX_ERR_FRIEND_ADD_TOO_LONG:
sprintf(numstring, "[i] Message is too long.");
break;
case TOX_ERR_FRIEND_ADD_NO_MESSAGE:
sprintf(numstring, "[i] Please add a message to your request.");
break;
case TOX_ERR_FRIEND_ADD_OWN_KEY:
sprintf(numstring, "[i] That appears to be your own ID.");
break;
case TOX_ERR_FRIEND_ADD_ALREADY_SENT:
sprintf(numstring, "[i] Friend request already sent.");
break;
case TOX_ERR_FRIEND_ADD_BAD_CHECKSUM:
sprintf(numstring, "[i] Address has a bad checksum.");
break;
case TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM:
sprintf(numstring, "[i] New nospam set.");
break;
case TOX_ERR_FRIEND_ADD_MALLOC:
sprintf(numstring, "[i] malloc error.");
break;
case TOX_ERR_FRIEND_ADD_NULL:
sprintf(numstring, "[i] message was NULL.");
break;
case TOX_ERR_FRIEND_ADD_OK:
sprintf(numstring, "[i] Added friend as %d.", num);
save_data(m);
break;
}
new_lines(numstring);
} else if (inpt_command == 'd') {
tox_iterate(m);
} else if (inpt_command == 'm') { //message command: /m friendnumber messsage
char *posi[1];
int num = strtoul(line + prompt_offset, posi, 0);
if (**posi != 0) {
if (tox_friend_send_message(m, num, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *) *posi + 1, strlen(*posi + 1), NULL) < 1) {
char sss[256];
sprintf(sss, "[i] could not send message to friend num %u", num);
new_lines(sss);
} else {
print_formatted_message(m, *posi + 1, num, 1);
}
} else
new_lines("Error, bad input.");
} else if (inpt_command == 'n') {
uint8_t name[TOX_MAX_NAME_LENGTH];
size_t i, len = strlen(line);
for (i = 3; i < len; i++) {
if (line[i] == 0 || line[i] == '\n') break;
name[i - 3] = line[i];
}
name[i - 3] = 0;
tox_self_set_name(m, name, i - 2, NULL);
char numstring[100];
sprintf(numstring, "[i] changed nick to %s", (char *)name);
new_lines(numstring);
} else if (inpt_command == 'l') {
print_friendlist(m);
} else if (inpt_command == 's') {
uint8_t status[TOX_MAX_STATUS_MESSAGE_LENGTH];
size_t i, len = strlen(line);
for (i = 3; i < len; i++) {
if (line[i] == 0 || line[i] == '\n') break;
status[i - 3] = line[i];
}
status[i - 3] = 0;
tox_self_set_status_message(m, status, strlen((char *)status), NULL);
char numstring[100];
sprintf(numstring, "[i] changed status to %s", (char *)status);
new_lines(numstring);
} else if (inpt_command == 'a') { // /a #: accept
uint8_t numf = atoi(line + 3);
char numchar[100];
if (numf >= num_requests || pending_requests[numf].accepted) {
sprintf(numchar, "[i] you either didn't receive that request or you already accepted it");
new_lines(numchar);
} else {
uint32_t num = tox_friend_add_norequest(m, pending_requests[numf].id, NULL);
if (num != UINT32_MAX) {
pending_requests[numf].accepted = 1;
sprintf(numchar, "[i] friend request %u accepted as friend no. %d", numf, num);
new_lines(numchar);
save_data(m);
} else {
sprintf(numchar, "[i] failed to add friend");
new_lines(numchar);
}
}
} else if (inpt_command == 'r') { // /r #: remove friend
uint8_t numf = atoi(line + 3);
if (!tox_friend_exists(m, numf)) {
char err[64];
sprintf(err, "You don't have a friend %i.", numf);
new_lines(err);
return;
}
char msg[128 + TOX_MAX_NAME_LENGTH];
char fname[TOX_MAX_NAME_LENGTH ];
getfriendname_terminated(m, numf, fname);
sprintf(msg, "Are you sure you want to delete friend %i: %s? (y/n)", numf, fname);
input_line[0] = 0;
new_lines(msg);
int c;
do {
c = getchar();
} while ((c != 'y') && (c != 'n') && (c != EOF));
if (c == 'y') {
int res = tox_friend_delete(m, numf, NULL);
if (res)
sprintf(msg, "[i] [%i: %s] is no longer your friend", numf, fname);
else
sprintf(msg, "[i] failed to remove friend");
new_lines(msg);
}
} else if (inpt_command == 'h') { //help
if (line[2] == ' ') {
if (line[3] == 'f') {
new_lines_mark(help_friend1, 1);
new_lines_mark(help_friend2, 1);
return;
} else if (line[3] == 'g') {
new_lines_mark(help_group, 1);
return;
}
}
new_lines_mark(help_main, 1);
} else if (inpt_command == 'x') { //info
char idstring[200];
get_id(m, idstring);
new_lines(idstring);
} else if (inpt_command == 'g') { //create new group chat
char msg[256];
sprintf(msg, "[g] Created new group chat with number: %u", tox_add_groupchat(m));
new_lines(msg);
} else if (inpt_command == 'i') { //invite friendnum to groupnum
char *posi[1];
int friendnumber = strtoul(line + prompt_offset, posi, 0);
int groupnumber = strtoul(*posi + 1, NULL, 0);
char msg[256];
sprintf(msg, "[g] Invited friend number %u to group number %u, returned: %u (0 means success)", friendnumber,
groupnumber, tox_invite_friend(m, friendnumber, groupnumber));
new_lines(msg);
} else if (inpt_command == 'z') { //send message to groupnum
char *posi[1];
int groupnumber = strtoul(line + prompt_offset, posi, 0);
if (**posi != 0) {
int res = tox_group_message_send(m, groupnumber, (uint8_t *)*posi + 1, strlen(*posi + 1));
if (res == 0) {
char msg[32 + STRING_LENGTH];
sprintf(msg, "[g] #%u: YOU: %s", groupnumber, *posi + 1);
new_lines(msg);
} else {
char msg[128];
sprintf(msg, "[i] could not send message to group no. %u: %i", groupnumber, res);
new_lines(msg);
}
}
} else if (inpt_command == 't') {
char *posi[1];
int friendnum = strtoul(line + prompt_offset, posi, 0);
if (**posi != 0) {
char msg[512];
sprintf(msg, "[t] Sending file %s to friendnum %u filenumber is %i (-1 means failure)", *posi + 1, friendnum,
add_filesender(m, friendnum, *posi + 1));
new_lines(msg);
}
} else if (inpt_command == 'q') { //exit
save_data(m);
endwin();
tox_kill(m);
exit(EXIT_SUCCESS);
} else if (inpt_command == 'c') { //set conversation partner
if (line[2] == 'r') {
if (conversation_default != 0) {
conversation_default = 0;
new_lines("[i] default conversation reset");
} else
new_lines("[i] default conversation wasn't set, nothing to do");
} else if (line[3] != ' ') {
new_lines("[i] invalid command");
} else {
int num = atoi(line + 4);
/* zero is also returned for not-a-number */
if (!num && strcmp(line + 4, "0"))
num = -1;
if (num < 0)
new_lines("[i] invalid command parameter");
else if (line[2] == 'f') {
conversation_default = num + 1;
char buffer[128];
sprintf(buffer, "[i] default conversation is now to friend %i", num);
new_lines(buffer);
} else if (line[2] == 'g') {
char buffer[128];
conversation_default = - (num + 1);
sprintf(buffer, "[i] default conversation is now to group %i", num);
new_lines(buffer);
} else
new_lines("[i] invalid command");
}
} else if (inpt_command == 'p') { //list peers
char *posi = NULL;
int group_number = strtoul(line + prompt_offset, &posi, 0);
if (posi != NULL) {
char msg[64];
int peer_cnt = tox_group_number_peers(m, group_number);
if (peer_cnt < 0) {
new_lines("[g] Invalid group number.");
} else if (peer_cnt == 0) {
sprintf(msg, "[g] #%i: No peers in group.", group_number);
new_lines(msg);
} else {
sprintf(msg, "[g] #%i: Group has %i peers. Names:", group_number, peer_cnt);
new_lines(msg);
print_groupchatpeers(m, group_number);
}
}
} else {
new_lines("[i] invalid command");
}
} else {
if (conversation_default != 0) {
if (conversation_default > 0) {
int friendnumber = conversation_default - 1;
uint32_t res = tox_friend_send_message(m, friendnumber, TOX_MESSAGE_TYPE_NORMAL, (uint8_t *)line, strlen(line), NULL);
if (res == 0) {
char sss[128];
sprintf(sss, "[i] could not send message to friend no. %u", friendnumber);
new_lines(sss);
} else
print_formatted_message(m, line, friendnumber, 1);
} else {
int groupnumber = - conversation_default - 1;
int res = tox_group_message_send(m, groupnumber, (uint8_t *)line, strlen(line));
if (res == 0) {
char msg[32 + STRING_LENGTH];
sprintf(msg, "[g] #%u: YOU: %s", groupnumber, line);
new_lines(msg);
} else {
char msg[128];
sprintf(msg, "[i] could not send message to group no. %u: %i", groupnumber, res);
new_lines(msg);
}
}
} else
new_lines("[i] invalid input: neither command nor in conversation");
}
}
/* basic wrap, ignores embedded '\t', '\n' or '|'
* inserts continuation markers if there's enough space left,
* otherwise turns spaces into newlines if possible */
void wrap(char output[STRING_LENGTH_WRAPPED], char input[STRING_LENGTH], int line_width)
{
size_t i, len = strlen(input);
if ((line_width < 4) || (len < (size_t)line_width)) {
/* if line_width ridiculously tiny, it's not worth the effort */
strcpy(output, input);
return;
}
/* how much can we shift? */
size_t delta_is = 0, delta_remain = STRING_LENGTH_WRAPPED - len - 1;
/* if the line is very very short, don't insert continuation markers,
* as they would use up too much of the line */
if ((size_t)line_width < 2 * wrap_cont_len)
delta_remain = 0;
for (i = line_width; i < len; i += line_width) {
/* look backward for a space to expand/turn into a new line */
size_t k = i;
size_t m = i - line_width;
while (input[k] != ' ' && k > m) {
k--;
}
if (k > m) {
if (delta_remain > wrap_cont_len) {
/* replace space with continuation, then
* set the pos. after the space as new line start
* (i.e. space is being "eaten") */
memcpy(output + m + delta_is, input + m, k - m);
strcpy(output + k + delta_is, wrap_cont_str);
delta_remain -= wrap_cont_len - 1;
delta_is += wrap_cont_len - 1;
i = k + 1;
} else {
/* no more space to push forward: replace the space,
* use its pos. + 1 as starting point for the next line */
memcpy(output + m + delta_is, input + m, k - m);
output[k + delta_is] = '\n';
i = k + 1;
}
} else {
/* string ends right here:
* don't add a continuation marker with nothing following */
if (i == len - 1)
break;
/* nothing found backwards */
if (delta_remain > wrap_cont_len) {
/* break at the end of the line,
* i.e. in the middle of the word at the border */
memcpy(output + m + delta_is, input + m, line_width);
strcpy(output + i + delta_is, wrap_cont_str);
delta_remain -= wrap_cont_len;
delta_is += wrap_cont_len;
} else {
/* no more space to push, no space to convert:
* just copy the whole line and move on;
* means the line count calc'ed will be off */
memcpy(output + m + delta_is, input + m, line_width);
}
}
}
i -= line_width;
memcpy(output + i + delta_is, input + i, len - i);
output[len + delta_is] = 0;
}
/*
* extended wrap, honors '\n', accepts '|' as "break here when necessary"
* marks wrapped lines with "+ " in front, which does expand output
* does NOT honor '\t': would require a lot more work (and tab width isn't always 8)
*/
void wrap_bars(char output[STRING_LENGTH_WRAPPED], char input[STRING_LENGTH], size_t line_width)
{
size_t len = strlen(input);
size_t ipos, opos = 0;
size_t bar_avail = 0, space_avail = 0, nl_got = 0; /* in opos */
for (ipos = 0; ipos < len; ipos++) {
if (opos - nl_got < line_width) {
/* not yet at the limit */
char c = input[ipos];
if (c == ' ')
space_avail = opos;
output[opos++] = input[ipos];
if (opos >= STRING_LENGTH_WRAPPED) {
opos = STRING_LENGTH_WRAPPED - 1;
break;
}
if (c == '|') {
output[opos - 1] = ' ';
bar_avail = opos;
if (opos + 2 >= STRING_LENGTH_WRAPPED) {
opos = STRING_LENGTH_WRAPPED - 1;
break;
}
output[opos++] = '|';
output[opos++] = ' ';
}
if (c == '\n')
nl_got = opos;
continue;
} else {
/* at the limit */
if (bar_avail > nl_got) {
/* overwrite */
memcpy(output + bar_avail - 1, wrap_cont_str, wrap_cont_len);
nl_got = bar_avail;
ipos--;
continue;
}
if (space_avail > nl_got) {
if (opos + wrap_cont_len - 1 >= STRING_LENGTH_WRAPPED) {
opos = STRING_LENGTH_WRAPPED - 1;
break;
}
/* move forward by 2 characters */
memmove(output + space_avail + 3, output + space_avail + 1, opos - (space_avail + 1));
memcpy(output + space_avail, wrap_cont_str, wrap_cont_len);
nl_got = space_avail + 1;
opos += 2;
ipos--;
continue;
}
char c = input[ipos];
if ((c == '|') || (c == ' ') || (c == '\n')) {
if (opos + wrap_cont_len >= STRING_LENGTH_WRAPPED) {
opos = STRING_LENGTH_WRAPPED - 1;
break;
}
memcpy(output + opos, wrap_cont_str, wrap_cont_len);
nl_got = opos;
opos += wrap_cont_len;
}
output[opos++] = input[ipos];
if (opos >= STRING_LENGTH_WRAPPED) {
opos = STRING_LENGTH_WRAPPED - 1;
break;
}
continue;
}
}
if (opos >= STRING_LENGTH_WRAPPED)
opos = STRING_LENGTH_WRAPPED - 1;
output[opos] = 0;
}
int count_lines(char *string)
{
size_t i, len = strlen(string);
int count = 1;
for (i = 0; i < len; i++) {
if (string[i] == '\n')
count++;
}
return count;
}
char *appender(char *str, const char c)
{
size_t len = strlen(str);
if (len < STRING_LENGTH) {
str[len + 1] = str[len];
str[len] = c;
}
return str;
}
void do_refresh()
{
int count = 0;
char wrap_output[STRING_LENGTH_WRAPPED];
int i;
for (i = 0; i < HISTORY; i++) {
if (flag[i])
wrap_bars(wrap_output, lines[i], x);
else
wrap(wrap_output, lines[i], x);
int L = count_lines(wrap_output);
count = count + L;
if (count < y) {
move(y - 1 - count, 0);
printw("%s", wrap_output);
clrtoeol();
}
}
move(y - 1, 0);
clrtoeol();
printw(">> ");
printw("%s", input_line);
clrtoeol();
refresh();
}
void print_request(Tox *m, const uint8_t *public_key, const uint8_t *data, size_t length, void *userdata)
{
new_lines("[i] received friend request with message:");
new_lines((char *)data);
char numchar[100];
sprintf(numchar, "[i] accept request with /a %u", num_requests);
new_lines(numchar);
memcpy(pending_requests[num_requests].id, public_key, TOX_PUBLIC_KEY_SIZE);
pending_requests[num_requests].accepted = 0;
++num_requests;
do_refresh();
}
void print_message(Tox *m, uint32_t friendnumber, TOX_MESSAGE_TYPE type, const uint8_t *string, size_t length,
void *userdata)
{
/* ensure null termination */
uint8_t null_string[length + 1];
memcpy(null_string, string, length);
null_string[length] = 0;
print_formatted_message(m, (char *)null_string, friendnumber, 0);
}
void print_nickchange(Tox *m, uint32_t friendnumber, const uint8_t *string, size_t length, void *userdata)
{
char name[TOX_MAX_NAME_LENGTH + 1];
if (getfriendname_terminated(m, friendnumber, name) != -1) {
char msg[100 + length];
if (name[0] != 0)
sprintf(msg, "[i] [%d] %s is now known as %s.", friendnumber, name, string);
else
sprintf(msg, "[i] [%d] Friend's name is %s.", friendnumber, string);
new_lines(msg);
}
}
void print_statuschange(Tox *m, uint32_t friendnumber, const uint8_t *string, size_t length, void *userdata)
{
char name[TOX_MAX_NAME_LENGTH + 1];
if (getfriendname_terminated(m, friendnumber, name) != -1) {
char msg[100 + length + strlen(name) + 1];
if (name[0] != 0)
sprintf(msg, "[i] [%d] %s's status changed to %s.", friendnumber, name, string);
else
sprintf(msg, "[i] [%d] Their status changed to %s.", friendnumber, string);
new_lines(msg);
}
}
static char *data_file_name = NULL;
static Tox *load_data()
{
FILE *data_file = fopen(data_file_name, "r");
if (data_file) {
fseek(data_file, 0, SEEK_END);
size_t size = ftell(data_file);
rewind(data_file);
uint8_t data[size];
if (fread(data, sizeof(uint8_t), size, data_file) != size) {
fputs("[!] could not read data file!\n", stderr);
fclose(data_file);
return 0;
}
struct Tox_Options options;
tox_options_default(&options);
options.savedata_type = TOX_SAVEDATA_TYPE_TOX_SAVE;
options.savedata_data = data;
options.savedata_length = size;
Tox *m = tox_new(&options, NULL);
if (fclose(data_file) < 0) {
perror("[!] fclose failed");
/* we got it open and the expected data read... let it be ok */
/* return 0; */
}
return m;
}
return tox_new(NULL, NULL);
}
static int save_data(Tox *m)
{
FILE *data_file = fopen(data_file_name, "w");
if (!data_file) {
perror("[!] load_key");
return 0;
}
int res = 1;
size_t size = tox_get_savedata_size(m);
uint8_t data[size];
tox_get_savedata(m, data);
if (fwrite(data, sizeof(uint8_t), size, data_file) != size) {
fputs("[!] could not write data file (1)!", stderr);
res = 0;
}
if (fclose(data_file) < 0) {
perror("[!] could not write data file (2)");
res = 0;
}
return res;
}
static int save_data_file(Tox *m, char *path)
{
data_file_name = path;
if (save_data(m))
return 1;
return 0;
}
void print_help(char *prog_name)
{
printf("nTox %.1f - Command-line tox-core client\n", 0.1);
printf("Usage: %s [--ipv4|--ipv6] IP PORT KEY [-f keyfile]\n", prog_name);
puts("Options: (order IS relevant)");
puts(" --ipv4 / --ipv6 [Optional] Support IPv4 only or IPv4 & IPv6.");
puts(" IP PORT KEY [REQUIRED] A node to connect to (IP/Port) and its key.");
puts(" -f keyfile [Optional] Specify a keyfile to read from and write to.");
}
void print_invite(Tox *m, int friendnumber, uint8_t type, const uint8_t *data, uint16_t length, void *userdata)
{
char msg[256];
if (type == TOX_GROUPCHAT_TYPE_TEXT) {
sprintf(msg, "[i] received group chat invite from: %u, auto accepting and joining. group number: %u", friendnumber,
tox_join_groupchat(m, friendnumber, data, length));
} else {
sprintf(msg, "[i] Group chat invite received of type %u that could not be accepted by ntox.", type);
}
new_lines(msg);
}
void print_groupchatpeers(Tox *m, int groupnumber)
{
int num = tox_group_number_peers(m, groupnumber);
if (num < 0)
return;
if (!num) {
new_lines("[g]+ no peers left in group.");
return;
}
uint8_t names[num][TOX_MAX_NAME_LENGTH];
uint16_t lengths[num];
tox_group_get_names(m, groupnumber, names, lengths, num);
int i;
char numstr[16];
char header[] = "[g]+ ";
size_t header_len = strlen(header);
char msg[STRING_LENGTH];
strcpy(msg, header);
size_t len_total = header_len;
for (i = 0; i < num; ++i) {
size_t len_name = lengths[i];
size_t len_num = sprintf(numstr, "%i: ", i);
if (len_num + len_name + len_total + 3 >= STRING_LENGTH) {
new_lines_mark(msg, 1);
strcpy(msg, header);
len_total = header_len;
}
strcpy(msg + len_total, numstr);
len_total += len_num;
memcpy(msg + len_total, (char *)names[i], len_name);
len_total += len_name;
if (i < num - 1) {
strcpy(msg + len_total, "|");
len_total++;
}
}
new_lines_mark(msg, 1);
}
void print_groupmessage(Tox *m, int groupnumber, int peernumber, const uint8_t *message, uint16_t length,
void *userdata)
{
char msg[256 + length];
uint8_t name[TOX_MAX_NAME_LENGTH] = {0};
int len = tox_group_peername(m, groupnumber, peernumber, name);
//print_groupchatpeers(m, groupnumber);
if (len <= 0)
name[0] = 0;
if (name[0] != 0)
sprintf(msg, "[g] %u: %u <%s>: %s", groupnumber, peernumber, name, message);
else
sprintf(msg, "[g] #%u: %u Unknown: %s", groupnumber, peernumber, message);
new_lines(msg);
}
void print_groupnamelistchange(Tox *m, int groupnumber, int peernumber, uint8_t change, void *userdata)
{
char msg[256];
if (change == TOX_CHAT_CHANGE_PEER_ADD) {
sprintf(msg, "[g] #%i: New peer %i.", groupnumber, peernumber);
new_lines(msg);
} else if (change == TOX_CHAT_CHANGE_PEER_DEL) {
/* if peer was the last in list, it simply dropped,
* otherwise it was overwritten by the last peer
*
* adjust output
*/
int peers_total = tox_group_number_peers(m, groupnumber);
if (peers_total == peernumber) {
sprintf(msg, "[g] #%i: Peer %i left.", groupnumber, peernumber);
new_lines(msg);
} else {
uint8_t peername[TOX_MAX_NAME_LENGTH] = {0};
int len = tox_group_peername(m, groupnumber, peernumber, peername);
if (len <= 0)
peername[0] = 0;
sprintf(msg, "[g] #%i: Peer %i left. Former peer [%i: <%s>] is now peer %i.", groupnumber, peernumber,
peers_total, peername, peernumber);
new_lines(msg);
}
} else if (change == TOX_CHAT_CHANGE_PEER_NAME) {
uint8_t peername[TOX_MAX_NAME_LENGTH] = {0};
int len = tox_group_peername(m, groupnumber, peernumber, peername);
if (len <= 0)
peername[0] = 0;
sprintf(msg, "[g] #%i: Peer %i's name changed: %s", groupnumber, peernumber, peername);
new_lines(msg);
} else {
sprintf(msg, "[g] #%i: Name list changed (peer %i, change %i?):", groupnumber, peernumber, change);
new_lines(msg);
print_groupchatpeers(m, groupnumber);
}
}
void file_request_accept(Tox *tox, uint32_t friend_number, uint32_t file_number, uint32_t type, uint64_t file_size,
const uint8_t *filename, size_t filename_length, void *user_data)
{
if (type != TOX_FILE_KIND_DATA) {
new_lines("Refused invalid file type.");
tox_file_control(tox, friend_number, file_number, TOX_FILE_CONTROL_CANCEL, 0);
return;
}
char msg[512];
sprintf(msg, "[t] %u is sending us: %s of size %llu", friend_number, filename, (long long unsigned int)file_size);
new_lines(msg);
if (tox_file_control(tox, friend_number, file_number, TOX_FILE_CONTROL_RESUME, 0)) {
sprintf(msg, "Accepted file transfer. (saving file as: %u.%u.bin)", friend_number, file_number);
new_lines(msg);
} else
new_lines("Could not accept file transfer.");
}
void file_print_control(Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control,
void *user_data)
{
char msg[512] = {0};
sprintf(msg, "[t] control %u received", control);
new_lines(msg);
if (control == TOX_FILE_CONTROL_CANCEL) {
unsigned int i;
for (i = 0; i < NUM_FILE_SENDERS; ++i) {
/* This is slow */
if (file_senders[i].file && file_senders[i].friendnum == friend_number && file_senders[i].filenumber == file_number) {
fclose(file_senders[i].file);
file_senders[i].file = 0;
char msg[512];
sprintf(msg, "[t] %u file transfer: %u cancelled", file_senders[i].friendnum, file_senders[i].filenumber);
new_lines(msg);
}
}
}
}
void write_file(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, const uint8_t *data,
size_t length, void *user_data)
{
if (length == 0) {
char msg[512];
sprintf(msg, "[t] %u file transfer: %u completed", friendnumber, filenumber);
new_lines(msg);
return;
}
char filename[256];
sprintf(filename, "%u.%u.bin", friendnumber, filenumber);
FILE *pFile = fopen(filename, "r+b");
if (pFile == NULL)
pFile = fopen(filename, "wb");
fseek(pFile, position, SEEK_SET);
if (fwrite(data, length, 1, pFile) != 1)
new_lines("Error writing to file");
fclose(pFile);
}
void print_online(Tox *tox, uint32_t friendnumber, TOX_CONNECTION status, void *userdata)
{
if (status)
printf("\nOther went online.\n");
else {
printf("\nOther went offline.\n");
unsigned int i;
for (i = 0; i < NUM_FILE_SENDERS; ++i)
if (file_senders[i].file != 0 && file_senders[i].friendnum == friendnumber) {
fclose(file_senders[i].file);
file_senders[i].file = 0;
}
}
}
char timeout_getch(Tox *m)
{
char c;
int slpval = tox_iteration_interval(m);
fd_set fds;
FD_ZERO(&fds);
FD_SET(0, &fds);
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = slpval * 1000;
c = ERR;
int n = select(1, &fds, NULL, NULL, &tv);
if (n < 0) {
new_lines("select error: maybe interupted");
} else if (n == 0) {
} else {
c = getch();
}
return c;
}
int main(int argc, char *argv[])
{
/* minimalistic locale support (i.e. when printing dates) */
setlocale(LC_ALL, "");
if (argc < 4) {
if ((argc == 2) && !strcmp(argv[1], "-h")) {
print_help(argv[0]);
exit(0);
}
printf("Usage: %s [--ipv4|--ipv6] IP PORT KEY [-f keyfile] (or %s -h for help)\n", argv[0], argv[0]);
exit(0);
}
/* let user override default by cmdline */
uint8_t ipv6enabled = 1; /* x */
int argvoffset = cmdline_parsefor_ipv46(argc, argv, &ipv6enabled);
if (argvoffset < 0)
exit(1);
int on = 0;
char *filename = "data";
char idstring[200] = {0};
Tox *m;
/* [-f keyfile] MUST be last two arguments, no point in walking over the list
* especially not a good idea to accept it anywhere in the middle */
if (argc > argvoffset + 3)
if (!strcmp(argv[argc - 2], "-f"))
filename = argv[argc - 1];
data_file_name = filename;
m = load_data();
if ( !m ) {
fputs("Failed to allocate Messenger datastructure", stderr);
exit(0);
}
save_data_file(m, filename);
tox_callback_friend_request(m, print_request, NULL);
tox_callback_friend_message(m, print_message, NULL);
tox_callback_friend_name(m, print_nickchange, NULL);
tox_callback_friend_status_message(m, print_statuschange, NULL);
tox_callback_group_invite(m, print_invite, NULL);
tox_callback_group_message(m, print_groupmessage, NULL);
tox_callback_file_recv_chunk(m, write_file, NULL);
tox_callback_file_recv_control(m, file_print_control, NULL);
tox_callback_file_recv(m, file_request_accept, NULL);
tox_callback_file_chunk_request(m, tox_file_chunk_request, NULL);
tox_callback_group_namelist_change(m, print_groupnamelistchange, NULL);
tox_callback_friend_connection_status(m, print_online, NULL);
initscr();
noecho();
raw();
getmaxyx(stdscr, y, x);
new_lines("/h for list of commands");
get_id(m, idstring);
new_lines(idstring);
strcpy(input_line, "");
uint16_t port = atoi(argv[argvoffset + 2]);
unsigned char *binary_string = hex_string_to_bin(argv[argvoffset + 3]);
int res = tox_bootstrap(m, argv[argvoffset + 1], port, binary_string, NULL);
if (!res) {
printf("Failed to convert \"%s\" into an IP address. Exiting...\n", argv[argvoffset + 1]);
endwin();
exit(1);
}
nodelay(stdscr, TRUE);
new_lines("[i] change username with /n");
uint8_t name[TOX_MAX_NAME_LENGTH + 1];
tox_self_get_name(m, name);
uint16_t namelen = tox_self_get_name_size(m);
name[namelen] = 0;
if (namelen > 0) {
char whoami[128 + TOX_MAX_NAME_LENGTH];
snprintf(whoami, sizeof(whoami), "[i] your current username is: %s", name);
new_lines(whoami);
}
time_t timestamp0 = time(NULL);
while (1) {
if (on == 0) {
if (tox_self_get_connection_status(m)) {
new_lines("[i] connected to DHT");
on = 1;
} else {
time_t timestamp1 = time(NULL);
if (timestamp0 + 10 < timestamp1) {
timestamp0 = timestamp1;
tox_bootstrap(m, argv[argvoffset + 1], port, binary_string, NULL);
}
}
}
tox_iterate(m);
do_refresh();
int c = timeout_getch(m);
if (c == ERR || c == 27)
continue;
getmaxyx(stdscr, y, x);
if ((c == 0x0d) || (c == 0x0a)) {
line_eval(m, input_line);
strcpy(input_line, "");
} else if (c == 8 || c == 127) {
input_line[strlen(input_line) - 1] = '\0';
} else if (isalnum(c) || ispunct(c) || c == ' ') {
appender(input_line, (char) c);
}
}
free(binary_string);
save_data_file(m, filename);
tox_kill(m);
endwin();
return 0;
}
================================================
FILE: testing/nTox.h
================================================
/* nTox.h
*
*Textual frontend for Tox.
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef NTOX_H
#define NTOX_H
/*
* module actually exports nothing for the outside
*/
#include
#include
#include "../toxcore/tox.h"
#define STRING_LENGTH 256
#define HISTORY 50
void new_lines(char *line);
void do_refresh();
#endif
================================================
FILE: testing/tox_shell.c
================================================
/* Tox Shell
*
* Proof of concept ssh like server software using tox.
*
* Command line arguments are the ip, port and public_key of a node (for bootstrapping).
*
* EX: ./test 127.0.0.1 33445 CDCFD319CE3460824B33BE58FD86B8941C9585181D8FBD7C79C5721D7C2E9F7C
*
*
* Copyright (C) 2014 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "../toxcore/tox.h"
#include "misc_tools.c"
#if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
#include
#elif defined(__FreeBSD__) || defined(__DragonFly__)
#include
#else
#include
#endif
#include
#include
#define c_sleep(x) usleep(1000*x)
void print_online(Tox *tox, uint32_t friendnumber, TOX_CONNECTION status, void *userdata)
{
if (status)
printf("\nOther went online.\n");
else
printf("\nOther went offline.\n");
}
void print_message(Tox *tox, uint32_t friendnumber, TOX_MESSAGE_TYPE type, const uint8_t *string, size_t length,
void *userdata)
{
int master = *((int *)userdata);
write(master, string, length);
write(master, "\n", 1);
}
int main(int argc, char *argv[])
{
uint8_t ipv6enabled = 1; /* x */
int argvoffset = cmdline_parsefor_ipv46(argc, argv, &ipv6enabled);
if (argvoffset < 0)
exit(1);
/* with optional --ipvx, now it can be 1-4 arguments... */
if ((argc != argvoffset + 2) && (argc != argvoffset + 4)) {
printf("Usage: %s [--ipv4|--ipv6] ip port public_key (of the DHT bootstrap node)\n", argv[0]);
exit(0);
}
int *master = malloc(sizeof(int));
int ret = forkpty(master, NULL, NULL, NULL);
if (ret == -1) {
printf("fork failed\n");
return 1;
}
if (ret == 0) {
execl("/bin/sh", "sh", NULL);
return 0;
}
int flags = fcntl(*master, F_GETFL, 0);
int r = fcntl(*master, F_SETFL, flags | O_NONBLOCK);
if (r < 0) {
printf("error setting flags\n");
}
Tox *tox = tox_new(0, 0);
tox_callback_friend_connection_status(tox, print_online, NULL);
tox_callback_friend_message(tox, print_message, master);
uint16_t port = atoi(argv[argvoffset + 2]);
unsigned char *binary_string = hex_string_to_bin(argv[argvoffset + 3]);
int res = tox_bootstrap(tox, argv[argvoffset + 1], port, binary_string, 0);
free(binary_string);
if (!res) {
printf("Failed to convert \"%s\" into an IP address. Exiting...\n", argv[argvoffset + 1]);
exit(1);
}
uint8_t address[TOX_ADDRESS_SIZE];
tox_self_get_address(tox, address);
uint32_t i;
for (i = 0; i < TOX_ADDRESS_SIZE; i++) {
printf("%02X", address[i]);
}
char temp_id[128];
printf("\nEnter the address of the other id you want to sync with (38 bytes HEX format):\n");
if (scanf("%s", temp_id) != 1) {
return 1;
}
uint8_t *bin_id = hex_string_to_bin(temp_id);
uint32_t num = tox_friend_add(tox, bin_id, (uint8_t *)"Install Gentoo", sizeof("Install Gentoo"), 0);
free(bin_id);
if (num == UINT32_MAX) {
printf("\nSomething went wrong when adding friend.\n");
return 1;
}
uint8_t notconnected = 1;
while (1) {
if (tox_self_get_connection_status(tox) && notconnected) {
printf("\nDHT connected.\n");
notconnected = 0;
}
while (tox_friend_get_connection_status(tox, num, 0)) {
uint8_t buf[TOX_MAX_MESSAGE_LENGTH];
ret = read(*master, buf, sizeof(buf));
if (ret <= 0)
break;
tox_friend_send_message(tox, num, TOX_MESSAGE_TYPE_NORMAL, buf, ret, 0);
}
tox_iterate(tox);
c_sleep(1);
}
return 0;
}
================================================
FILE: testing/tox_sync.c
================================================
/* Tox Sync
*
* Proof of concept bittorrent sync like software using tox, syncs two directories.
*
* Command line arguments are the ip, port and public_key of a node (for bootstrapping) and the folder to sync.
*
* EX: ./test 127.0.0.1 33445 CDCFD319CE3460824B33BE58FD86B8941C9585181D8FBD7C79C5721D7C2E9F7C ./sync_folder/
*
* NOTE: for security purposes, both tox sync instances must manually add each other as friend for it to work.
*
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "../toxcore/tox.h"
#include "misc_tools.c"
#include
#define c_sleep(x) usleep(1000*x)
#include
#include
#include
#include
#define NUM_FILE_SENDERS 256
typedef struct {
FILE *file;
uint32_t friendnum;
uint32_t filenumber;
} File_t;
File_t file_senders[NUM_FILE_SENDERS];
File_t file_recv[NUM_FILE_SENDERS];
uint8_t numfilesenders;
void tox_file_chunk_request(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, size_t length,
void *user_data)
{
unsigned int i;
for (i = 0; i < NUM_FILE_SENDERS; ++i) {
/* This is slow */
if (file_senders[i].file && file_senders[i].friendnum == friend_number && file_senders[i].filenumber == file_number) {
if (length == 0) {
fclose(file_senders[i].file);
file_senders[i].file = 0;
printf("[t] %u file transfer: %u completed\n", file_senders[i].friendnum, file_senders[i].filenumber);
break;
}
fseek(file_senders[i].file, position, SEEK_SET);
uint8_t data[length];
int len = fread(data, 1, length, file_senders[i].file);
tox_file_send_chunk(tox, friend_number, file_number, position, data, len, 0);
break;
}
}
}
uint32_t add_filesender(Tox *m, uint16_t friendnum, char *filename)
{
FILE *tempfile = fopen(filename, "rb");
if (tempfile == 0)
return -1;
fseek(tempfile, 0, SEEK_END);
uint64_t filesize = ftell(tempfile);
fseek(tempfile, 0, SEEK_SET);
uint32_t filenum = tox_file_send(m, friendnum, TOX_FILE_KIND_DATA, filesize, 0, (uint8_t *)filename,
strlen(filename), 0);
if (filenum == -1)
return -1;
file_senders[numfilesenders].file = tempfile;
file_senders[numfilesenders].friendnum = friendnum;
file_senders[numfilesenders].filenumber = filenum;
++numfilesenders;
return filenum;
}
void kill_filesender(Tox *m, uint32_t filenum)
{
uint32_t i;
for (i = 0; i < NUM_FILE_SENDERS; ++i)
if (file_senders[i].file != 0 && file_senders[i].filenumber == filenum) {
fclose(file_senders[i].file);
file_senders[i].file = 0;
}
}
int not_sending()
{
uint32_t i;
for (i = 0; i < NUM_FILE_SENDERS; ++i)
if (file_senders[i].file != 0)
return 0;
return 1;
}
static char path[1024];
void file_request_accept(Tox *tox, uint32_t friend_number, uint32_t file_number, uint32_t type, uint64_t file_size,
const uint8_t *filename, size_t filename_length, void *user_data)
{
if (type != TOX_FILE_KIND_DATA) {
printf("Refused invalid file type.");
tox_file_control(tox, friend_number, file_number, TOX_FILE_CONTROL_CANCEL, 0);
return;
}
char fullpath[1024];
uint32_t i;
uint16_t rm = 0;
for (i = 0; i < strlen((char *)filename); ++i) {
if (filename[i] == '/')
rm = i;
}
if (path[strlen(path) - 1] == '/')
sprintf(fullpath, "%s%s", path, filename + rm + 1);
else
sprintf(fullpath, "%s/%s", path, filename + rm + 1);
FILE *tempfile = fopen(fullpath, "rb");
if (tempfile != 0) {
fclose(tempfile);
tox_file_control(tox, friend_number, file_number, TOX_FILE_CONTROL_CANCEL, 0);
return;
}
uint8_t file_index = (file_number >> 16) - 1;
file_recv[file_index].file = fopen(fullpath, "wb");
if (file_recv[file_index].file == 0) {
tox_file_control(tox, friend_number, file_number, TOX_FILE_CONTROL_CANCEL, 0);
return;
}
if (tox_file_control(tox, friend_number, file_number, TOX_FILE_CONTROL_RESUME, 0)) {
printf("Accepted file transfer. (file: %s)\n", fullpath);
}
}
void file_print_control(Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control,
void *user_data)
{
if (file_number < (1 << 15) && (control == TOX_FILE_CONTROL_CANCEL)) {
kill_filesender(tox, file_number);
return;
}
if (file_number > (1 << 15) && (control == TOX_FILE_CONTROL_CANCEL)) {
uint8_t file_index = (file_number >> 16) - 1;
fclose(file_recv[file_index].file);
printf("File closed\n");
file_recv[file_index].file = 0;
return;
}
}
void write_file(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, const uint8_t *data,
size_t length, void *user_data)
{
uint8_t file_index = (filenumber >> 16) - 1;
if (length == 0) {
fclose(file_recv[file_index].file);
printf("File closed\n");
file_recv[file_index].file = 0;
printf("%u file transfer: %u completed\n", friendnumber, filenumber);
return;
}
if (file_recv[file_index].file != 0) {
fseek(file_recv[file_index].file, position, SEEK_SET);
if (fwrite(data, length, 1, file_recv[file_index].file) != 1)
printf("Error writing data\n");
}
}
void print_online(Tox *tox, uint32_t friendnumber, TOX_CONNECTION status, void *userdata)
{
if (status)
printf("\nOther went online.\n");
else {
printf("\nOther went offline.\n");
unsigned int i;
for (i = 0; i < NUM_FILE_SENDERS; ++i) {
if (file_senders[i].file != 0) {
fclose(file_senders[i].file);
file_senders[i].file = 0;
}
if (file_recv[i].file != 0) {
fclose(file_recv[i].file);
file_recv[i].file = 0;
}
}
}
}
int main(int argc, char *argv[])
{
uint8_t ipv6enabled = 1; /* x */
int argvoffset = cmdline_parsefor_ipv46(argc, argv, &ipv6enabled);
if (argvoffset < 0)
exit(1);
/* with optional --ipvx, now it can be 1-4 arguments... */
if ((argc != argvoffset + 3) && (argc != argvoffset + 5)) {
printf("Usage: %s [--ipv4|--ipv6] ip port public_key (of the DHT bootstrap node) folder (to sync)\n", argv[0]);
exit(0);
}
Tox *tox = tox_new(0, 0);
tox_callback_file_recv_chunk(tox, write_file, NULL);
tox_callback_file_recv_control(tox, file_print_control, NULL);
tox_callback_file_recv(tox, file_request_accept, NULL);
tox_callback_file_chunk_request(tox, tox_file_chunk_request, NULL);
tox_callback_friend_connection_status(tox, print_online, NULL);
uint16_t port = atoi(argv[argvoffset + 2]);
unsigned char *binary_string = hex_string_to_bin(argv[argvoffset + 3]);
int res = tox_bootstrap(tox, argv[argvoffset + 1], port, binary_string, 0);
free(binary_string);
if (!res) {
printf("Failed to convert \"%s\" into an IP address. Exiting...\n", argv[argvoffset + 1]);
exit(1);
}
uint8_t address[TOX_ADDRESS_SIZE];
tox_self_get_address(tox, address);
uint32_t i;
for (i = 0; i < TOX_ADDRESS_SIZE; i++) {
printf("%02X", address[i]);
}
char temp_id[128];
printf("\nEnter the address of the other id you want to sync with (38 bytes HEX format):\n");
if (scanf("%s", temp_id) != 1) {
return 1;
}
uint8_t *bin_id = hex_string_to_bin(temp_id);
uint32_t num = tox_friend_add(tox, bin_id, (uint8_t *)"Install Gentoo", sizeof("Install Gentoo"), 0);
free(bin_id);
if (num == UINT32_MAX) {
printf("\nSomething went wrong when adding friend.\n");
return 1;
}
memcpy(path, argv[argvoffset + 4], strlen(argv[argvoffset + 4]));
DIR *d;
struct dirent *dir;
uint8_t notconnected = 1;
while (1) {
if (tox_self_get_connection_status(tox) && notconnected) {
printf("\nDHT connected.\n");
notconnected = 0;
}
if (not_sending() && tox_friend_get_connection_status(tox, num, 0)) {
d = opendir(path);
if (d) {
while ((dir = readdir(d)) != NULL) {
if (dir->d_type == DT_REG) {
char fullpath[1024];
if (path[strlen(path) - 1] == '/')
sprintf(fullpath, "%s%s", path, dir->d_name);
else
sprintf(fullpath, "%s/%s", path, dir->d_name);
add_filesender(tox, num, fullpath);
}
}
closedir(d);
} else {
printf("\nFailed to open directory.\n");
return 1;
}
}
tox_iterate(tox);
c_sleep(1);
}
return 0;
}
================================================
FILE: tox.spec.in
================================================
Name: @PACKAGE_NAME@
Version: @VERSION@
Release: 1%{?dist}
Summary: All-in-one secure communication platform
License: GPLv3
URL: https://github.com/irungentoo/toxcore
Source0: https://github.com/irungentoo/toxcore/releases/tox-%{version}.tar.gz
BuildRequires: autoconf automake libtool libvpx-devel opus-devel
BuildRequires: libsodium-devel libconfig-devel
%description
With the rise of governmental monitoring programs, Tox, a FOSS initiative, aims to be an easy to use, all-in-one communication platform that ensures their users full privacy and secure message delivery.
%package devel
Summary: Development files for @PACKAGE_NAME@
Requires: %{name} = %{version}-%{release}
%description devel
Development package for @PACKAGE_NAME@
%prep
%setup -q
%build
%configure \
--enable-shared \
--disable-static \
--enable-av \
--disable-ntox \
--disable-daemon \
--disable-testing
make %{?_smp_mflags}
%install
%make_install
# remove la files
find %{buildroot} -name '*.la' -delete -print
# not handling DHT_bootstrap yet
rm -f %{buildroot}%{_bindir}/DHT_bootstrap
%post
/sbin/ldconfig
%postun
/sbin/ldconfig
%files
%defattr(-,root,root)
%doc COPYING README.md
%{_libdir}/libtox*.so.*
%files devel
%defattr(-, root, root)
%{_includedir}/tox/
%{_libdir}/libtox*.so
%{_libdir}/pkgconfig/libtox*.pc
%changelog
* Tue Mar 3 2015 Sergey 'Jin' Bostandzhyan - 0.0.0-1
- initial package
================================================
FILE: toxav/Makefile.inc
================================================
if BUILD_AV
lib_LTLIBRARIES += libtoxav.la
libtoxav_la_include_HEADERS = ../toxav/toxav.h
libtoxav_la_includedir = $(includedir)/tox
libtoxav_la_SOURCES = ../toxav/rtp.h \
../toxav/rtp.c \
../toxav/msi.h \
../toxav/msi.c \
../toxav/group.h \
../toxav/group.c \
../toxav/audio.h \
../toxav/audio.c \
../toxav/video.h \
../toxav/video.c \
../toxav/bwcontroller.h \
../toxav/bwcontroller.c \
../toxav/toxav.h \
../toxav/toxav.c \
../toxav/toxav_old.c
libtoxav_la_CFLAGS = -I../toxcore \
-I../toxav \
$(LIBSODIUM_CFLAGS) \
$(NACL_CFLAGS) \
$(AV_CFLAGS) \
$(PTHREAD_CFLAGS)
libtoxav_la_LDFLAGS = $(TOXAV_LT_LDFLAGS) \
$(LIBSODIUM_LDFLAGS) \
$(NACL_LDFLAGS) \
$(EXTRA_LT_LDFLAGS) \
$(WINSOCK2_LIBS)
libtoxav_la_LIBADD = libtoxcore.la \
$(LIBSODIUM_LIBS) \
$(NACL_LIBS) \
$(PTHREAD_LIBS) \
$(AV_LIBS)
endif
================================================
FILE: toxav/audio.c
================================================
/** audio.c
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include
#include "audio.h"
#include "rtp.h"
#include "../toxcore/logger.h"
static struct JitterBuffer *jbuf_new(uint32_t capacity);
static void jbuf_clear(struct JitterBuffer *q);
static void jbuf_free(struct JitterBuffer *q);
static int jbuf_write(struct JitterBuffer *q, struct RTPMessage *m);
static struct RTPMessage *jbuf_read(struct JitterBuffer *q, int32_t *success);
OpusEncoder *create_audio_encoder (int32_t bit_rate, int32_t sampling_rate, int32_t channel_count);
bool reconfigure_audio_encoder(OpusEncoder **e, int32_t new_br, int32_t new_sr, uint8_t new_ch,
int32_t *old_br, int32_t *old_sr, int32_t *old_ch);
bool reconfigure_audio_decoder(ACSession *ac, int32_t sampling_rate, int8_t channels);
ACSession *ac_new(ToxAV *av, uint32_t friend_number, toxav_audio_receive_frame_cb *cb, void *cb_data)
{
ACSession *ac = calloc(sizeof(ACSession), 1);
if (!ac) {
LOGGER_WARNING("Allocation failed! Application might misbehave!");
return NULL;
}
if (create_recursive_mutex(ac->queue_mutex) != 0) {
LOGGER_WARNING("Failed to create recursive mutex!");
free(ac);
return NULL;
}
int status;
ac->decoder = opus_decoder_create(48000, 2, &status);
if (status != OPUS_OK) {
LOGGER_ERROR("Error while starting audio decoder: %s", opus_strerror(status));
goto BASE_CLEANUP;
}
if (!(ac->j_buf = jbuf_new(3))) {
LOGGER_WARNING("Jitter buffer creaton failed!");
opus_decoder_destroy(ac->decoder);
goto BASE_CLEANUP;
}
/* Initialize encoders with default values */
ac->encoder = create_audio_encoder(48000, 48000, 2);
if (ac->encoder == NULL)
goto DECODER_CLEANUP;
ac->le_bit_rate = 48000;
ac->le_sample_rate = 48000;
ac->le_channel_count = 2;
ac->ld_channel_count = 2;
ac->ld_sample_rate = 48000;
ac->ldrts = 0; /* Make it possible to reconfigure straight away */
/* These need to be set in order to properly
* do error correction with opus */
ac->lp_frame_duration = 120;
ac->lp_sampling_rate = 48000;
ac->lp_channel_count = 1;
ac->av = av;
ac->friend_number = friend_number;
ac->acb.first = cb;
ac->acb.second = cb_data;
return ac;
DECODER_CLEANUP:
opus_decoder_destroy(ac->decoder);
jbuf_free(ac->j_buf);
BASE_CLEANUP:
pthread_mutex_destroy(ac->queue_mutex);
free(ac);
return NULL;
}
void ac_kill(ACSession *ac)
{
if (!ac)
return;
opus_encoder_destroy(ac->encoder);
opus_decoder_destroy(ac->decoder);
jbuf_free(ac->j_buf);
pthread_mutex_destroy(ac->queue_mutex);
LOGGER_DEBUG("Terminated audio handler: %p", ac);
free(ac);
}
void ac_iterate(ACSession *ac)
{
if (!ac)
return;
/* TODO fix this and jitter buffering */
/* Enough space for the maximum frame size (120 ms 48 KHz stereo audio) */
int16_t tmp[5760 * 2];
struct RTPMessage *msg;
int rc = 0;
pthread_mutex_lock(ac->queue_mutex);
while ((msg = jbuf_read(ac->j_buf, &rc)) || rc == 2) {
pthread_mutex_unlock(ac->queue_mutex);
if (rc == 2) {
LOGGER_DEBUG("OPUS correction");
int fs = (ac->lp_sampling_rate * ac->lp_frame_duration) / 1000;
rc = opus_decode(ac->decoder, NULL, 0, tmp, fs, 1);
} else {
/* Get values from packet and decode. */
/* NOTE: This didn't work very well
rc = convert_bw_to_sampling_rate(opus_packet_get_bandwidth(msg->data));
if (rc != -1) {
cs->last_packet_sampling_rate = rc;
} else {
LOGGER_WARNING("Failed to load packet values!");
rtp_free_msg(msg);
continue;
}*/
/* Pick up sampling rate from packet */
memcpy(&ac->lp_sampling_rate, msg->data, 4);
ac->lp_sampling_rate = ntohl(ac->lp_sampling_rate);
ac->lp_channel_count = opus_packet_get_nb_channels(msg->data + 4);
/** NOTE: even though OPUS supports decoding mono frames with stereo decoder and vice versa,
* it didn't work quite well.
*/
if (!reconfigure_audio_decoder(ac, ac->lp_sampling_rate, ac->lp_channel_count)) {
LOGGER_WARNING("Failed to reconfigure decoder!");
free(msg);
continue;
}
rc = opus_decode(ac->decoder, msg->data + 4, msg->len - 4, tmp, 5760, 0);
free(msg);
}
if (rc < 0) {
LOGGER_WARNING("Decoding error: %s", opus_strerror(rc));
} else if (ac->acb.first) {
ac->lp_frame_duration = (rc * 1000) / ac->lp_sampling_rate;
ac->acb.first(ac->av, ac->friend_number, tmp, rc, ac->lp_channel_count,
ac->lp_sampling_rate, ac->acb.second);
}
return;
}
pthread_mutex_unlock(ac->queue_mutex);
}
int ac_queue_message(void *acp, struct RTPMessage *msg)
{
if (!acp || !msg)
return -1;
if ((msg->header.pt & 0x7f) == (rtp_TypeAudio + 2) % 128) {
LOGGER_WARNING("Got dummy!");
free(msg);
return 0;
}
if ((msg->header.pt & 0x7f) != rtp_TypeAudio % 128) {
LOGGER_WARNING("Invalid payload type!");
free(msg);
return -1;
}
ACSession *ac = acp;
pthread_mutex_lock(ac->queue_mutex);
int rc = jbuf_write(ac->j_buf, msg);
pthread_mutex_unlock(ac->queue_mutex);
if (rc == -1) {
LOGGER_WARNING("Could not queue the message!");
free(msg);
return -1;
}
return 0;
}
int ac_reconfigure_encoder(ACSession *ac, int32_t bit_rate, int32_t sampling_rate, uint8_t channels)
{
if (!ac || !reconfigure_audio_encoder(&ac->encoder, bit_rate,
sampling_rate, channels,
&ac->le_bit_rate,
&ac->le_sample_rate,
&ac->le_channel_count))
return -1;
return 0;
}
struct JitterBuffer {
struct RTPMessage **queue;
uint32_t size;
uint32_t capacity;
uint16_t bottom;
uint16_t top;
};
static struct JitterBuffer *jbuf_new(uint32_t capacity)
{
unsigned int size = 1;
while (size <= (capacity * 4)) {
size *= 2;
}
struct JitterBuffer *q;
if (!(q = calloc(sizeof(struct JitterBuffer), 1))) return NULL;
if (!(q->queue = calloc(sizeof(struct RTPMessage *), size))) {
free(q);
return NULL;
}
q->size = size;
q->capacity = capacity;
return q;
}
static void jbuf_clear(struct JitterBuffer *q)
{
for (; q->bottom != q->top; ++q->bottom) {
if (q->queue[q->bottom % q->size]) {
free(q->queue[q->bottom % q->size]);
q->queue[q->bottom % q->size] = NULL;
}
}
}
static void jbuf_free(struct JitterBuffer *q)
{
if (!q) return;
jbuf_clear(q);
free(q->queue);
free(q);
}
static int jbuf_write(struct JitterBuffer *q, struct RTPMessage *m)
{
uint16_t sequnum = m->header.sequnum;
unsigned int num = sequnum % q->size;
if ((uint32_t)(sequnum - q->bottom) > q->size) {
LOGGER_DEBUG("Clearing filled jitter buffer: %p", q);
jbuf_clear(q);
q->bottom = sequnum - q->capacity;
q->queue[num] = m;
q->top = sequnum + 1;
return 0;
}
if (q->queue[num])
return -1;
q->queue[num] = m;
if ((sequnum - q->bottom) >= (q->top - q->bottom))
q->top = sequnum + 1;
return 0;
}
static struct RTPMessage *jbuf_read(struct JitterBuffer *q, int32_t *success)
{
if (q->top == q->bottom) {
*success = 0;
return NULL;
}
unsigned int num = q->bottom % q->size;
if (q->queue[num]) {
struct RTPMessage *ret = q->queue[num];
q->queue[num] = NULL;
++q->bottom;
*success = 1;
return ret;
}
if ((uint32_t)(q->top - q->bottom) > q->capacity) {
++q->bottom;
*success = 2;
return NULL;
}
*success = 0;
return NULL;
}
OpusEncoder *create_audio_encoder (int32_t bit_rate, int32_t sampling_rate, int32_t channel_count)
{
int status = OPUS_OK;
OpusEncoder *rc = opus_encoder_create(sampling_rate, channel_count, OPUS_APPLICATION_VOIP, &status);
if (status != OPUS_OK) {
LOGGER_ERROR("Error while starting audio encoder: %s", opus_strerror(status));
return NULL;
}
status = opus_encoder_ctl(rc, OPUS_SET_BITRATE(bit_rate));
if (status != OPUS_OK) {
LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status));
goto FAILURE;
}
/* Enable in-band forward error correction in codec */
status = opus_encoder_ctl(rc, OPUS_SET_INBAND_FEC(1));
if (status != OPUS_OK) {
LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status));
goto FAILURE;
}
/* Make codec resistant to up to 10% packet loss
* NOTE This could also be adjusted on the fly, rather than hard-coded,
* with feedback from the receiving client.
*/
status = opus_encoder_ctl(rc, OPUS_SET_PACKET_LOSS_PERC(10));
if (status != OPUS_OK) {
LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status));
goto FAILURE;
}
/* Set algorithm to the highest complexity, maximizing compression */
status = opus_encoder_ctl(rc, OPUS_SET_COMPLEXITY(10));
if (status != OPUS_OK) {
LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status));
goto FAILURE;
}
return rc;
FAILURE:
opus_encoder_destroy(rc);
return NULL;
}
bool reconfigure_audio_encoder(OpusEncoder **e, int32_t new_br, int32_t new_sr, uint8_t new_ch,
int32_t *old_br, int32_t *old_sr, int32_t *old_ch)
{
/* Values are checked in toxav.c */
if (*old_sr != new_sr || *old_ch != new_ch) {
OpusEncoder *new_encoder = create_audio_encoder(new_br, new_sr, new_ch);
if (new_encoder == NULL)
return false;
opus_encoder_destroy(*e);
*e = new_encoder;
} else if (*old_br == new_br)
return true; /* Nothing changed */
else {
int status = opus_encoder_ctl(*e, OPUS_SET_BITRATE(new_br));
if (status != OPUS_OK) {
LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(status));
return false;
}
}
*old_br = new_br;
*old_sr = new_sr;
*old_ch = new_ch;
LOGGER_DEBUG ("Reconfigured audio encoder br: %d sr: %d cc:%d", new_br, new_sr, new_ch);
return true;
}
bool reconfigure_audio_decoder(ACSession *ac, int32_t sampling_rate, int8_t channels)
{
if (sampling_rate != ac->ld_sample_rate || channels != ac->ld_channel_count) {
if (current_time_monotonic() - ac->ldrts < 500)
return false;
int status;
OpusDecoder *new_dec = opus_decoder_create(sampling_rate, channels, &status);
if (status != OPUS_OK) {
LOGGER_ERROR("Error while starting audio decoder(%d %d): %s", sampling_rate, channels, opus_strerror(status));
return false;
}
ac->ld_sample_rate = sampling_rate;
ac->ld_channel_count = channels;
ac->ldrts = current_time_monotonic();
opus_decoder_destroy(ac->decoder);
ac->decoder = new_dec;
LOGGER_DEBUG("Reconfigured audio decoder sr: %d cc: %d", sampling_rate, channels);
}
return true;
}
================================================
FILE: toxav/audio.h
================================================
/** audio.h
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef AUDIO_H
#define AUDIO_H
#include
#include
#include "toxav.h"
#include "../toxcore/util.h"
struct RTPMessage;
typedef struct ACSession_s {
/* encoding */
OpusEncoder *encoder;
int32_t le_sample_rate; /* Last encoder sample rate */
int32_t le_channel_count; /* Last encoder channel count */
int32_t le_bit_rate; /* Last encoder bit rate */
/* decoding */
OpusDecoder *decoder;
int32_t lp_channel_count; /* Last packet channel count */
int32_t lp_sampling_rate; /* Last packet sample rate */
int32_t lp_frame_duration; /* Last packet frame duration */
int32_t ld_sample_rate; /* Last decoder sample rate */
int32_t ld_channel_count; /* Last decoder channel count */
uint64_t ldrts; /* Last decoder reconfiguration time stamp */
void *j_buf;
pthread_mutex_t queue_mutex[1];
ToxAV *av;
uint32_t friend_number;
PAIR(toxav_audio_receive_frame_cb *, void *) acb; /* Audio frame receive callback */
} ACSession;
ACSession *ac_new(ToxAV *av, uint32_t friend_number, toxav_audio_receive_frame_cb *cb, void *cb_data);
void ac_kill(ACSession *ac);
void ac_iterate(ACSession *ac);
int ac_queue_message(void *acp, struct RTPMessage *msg);
int ac_reconfigure_encoder(ACSession *ac, int32_t bit_rate, int32_t sampling_rate, uint8_t channels);
#endif /* AUDIO_H */
================================================
FILE: toxav/bwcontroller.c
================================================
/** bwcontroller.c
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include
#include "bwcontroller.h"
#include "../toxcore/logger.h"
#include "../toxcore/util.h"
#define BWC_PACKET_ID 196
#define BWC_SEND_INTERVAL_MS 1000
#define BWC_REFRESH_INTERVAL_MS 10000
#define BWC_AVG_PKT_COUNT 20
/**
*
*/
struct BWController_s {
void (*mcb) (BWController *, uint32_t, float, void *);
void *mcb_data;
Messenger *m;
uint32_t friend_number;
struct {
uint32_t lru; /* Last recv update time stamp */
uint32_t lsu; /* Last sent update time stamp */
uint32_t lfu; /* Last refresh time stamp */
uint32_t lost;
uint32_t recv;
} cycle;
struct {
uint32_t rb_s[BWC_AVG_PKT_COUNT];
RingBuffer *rb;
} rcvpkt; /* To calculate average received packet */
};
int bwc_handle_data(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object);
void send_update(BWController *bwc);
BWController *bwc_new(Messenger *m, uint32_t friendnumber,
void (*mcb) (BWController *, uint32_t, float, void *),
void *udata)
{
BWController *retu = calloc(sizeof(struct BWController_s), 1);
retu->mcb = mcb;
retu->mcb_data = udata;
retu->m = m;
retu->friend_number = friendnumber;
retu->cycle.lsu = retu->cycle.lfu = current_time_monotonic();
retu->rcvpkt.rb = rb_new(BWC_AVG_PKT_COUNT);
/* Fill with zeros */
int i = 0;
for (; i < BWC_AVG_PKT_COUNT; i ++)
rb_write(retu->rcvpkt.rb, retu->rcvpkt.rb_s + i);
m_callback_rtp_packet(m, friendnumber, BWC_PACKET_ID, bwc_handle_data, retu);
return retu;
}
void bwc_kill(BWController *bwc)
{
if (!bwc)
return;
m_callback_rtp_packet(bwc->m, bwc->friend_number, BWC_PACKET_ID, NULL, NULL);
rb_kill(bwc->rcvpkt.rb);
free(bwc);
}
void bwc_feed_avg(BWController *bwc, uint32_t bytes)
{
uint32_t *p;
rb_read(bwc->rcvpkt.rb, (void **) &p);
rb_write(bwc->rcvpkt.rb, p);
*p = bytes;
}
void bwc_add_lost(BWController *bwc, uint32_t bytes)
{
if (!bwc)
return;
if (!bytes) {
uint32_t *t_avg[BWC_AVG_PKT_COUNT], c = 1;
rb_data(bwc->rcvpkt.rb, (void **) t_avg);
int i = 0;
for (; i < BWC_AVG_PKT_COUNT; i ++) {
bytes += *(t_avg[i]);
if (*(t_avg[i]))
c++;
}
bytes /= c;
}
bwc->cycle.lost += bytes;
send_update(bwc);
}
void bwc_add_recv(BWController *bwc, uint32_t bytes)
{
if (!bwc || !bytes)
return;
bwc->cycle.recv += bytes;
send_update(bwc);
}
struct BWCMessage {
uint32_t lost;
uint32_t recv;
};
void send_update(BWController *bwc)
{
if (current_time_monotonic() - bwc->cycle.lfu > BWC_REFRESH_INTERVAL_MS) {
bwc->cycle.lost /= 10;
bwc->cycle.recv /= 10;
bwc->cycle.lfu = current_time_monotonic();
} else if (current_time_monotonic() - bwc->cycle.lsu > BWC_SEND_INTERVAL_MS) {
if (bwc->cycle.lost) {
LOGGER_DEBUG ("%p Sent update rcv: %u lost: %u",
bwc, bwc->cycle.recv, bwc->cycle.lost);
uint8_t p_msg[sizeof(struct BWCMessage) + 1];
struct BWCMessage *b_msg = (struct BWCMessage *)(p_msg + 1);
p_msg[0] = BWC_PACKET_ID;
b_msg->lost = htonl(bwc->cycle.lost);
b_msg->recv = htonl(bwc->cycle.recv);
if (-1 == send_custom_lossy_packet(bwc->m, bwc->friend_number, p_msg, sizeof(p_msg)))
LOGGER_WARNING("BWC send failed (len: %d)! std error: %s", sizeof(p_msg), strerror(errno));
}
bwc->cycle.lsu = current_time_monotonic();
}
}
int on_update (BWController *bwc, struct BWCMessage *msg)
{
LOGGER_DEBUG ("%p Got update from peer", bwc);
/* Peer must respect time boundary */
if (current_time_monotonic() < bwc->cycle.lru + BWC_SEND_INTERVAL_MS) {
LOGGER_DEBUG("%p Rejecting extra update", bwc);
return -1;
}
bwc->cycle.lru = current_time_monotonic();
msg->recv = ntohl(msg->recv);
msg->lost = ntohl(msg->lost);
LOGGER_DEBUG ("recved: %u lost: %u", msg->recv, msg->lost);
if (msg->lost && bwc->mcb)
bwc->mcb(bwc, bwc->friend_number,
((float) (msg->lost) / (msg->recv + msg->lost)),
bwc->mcb_data);
return 0;
}
int bwc_handle_data(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object)
{
if (length - 1 != sizeof(struct BWCMessage))
return -1;
/* NOTE the data is mutable */
return on_update(object, (struct BWCMessage *) (data + 1));
}
================================================
FILE: toxav/bwcontroller.h
================================================
/** bwcontroller.h
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef BWCONROLER_H
#define BWCONROLER_H
#include "../toxcore/Messenger.h"
typedef struct BWController_s BWController;
BWController *bwc_new(Messenger *m, uint32_t friendnumber,
void (*mcb) (BWController *, uint32_t, float, void *),
void *udata);
void bwc_kill(BWController *bwc);
void bwc_feed_avg(BWController *bwc, uint32_t bytes);
void bwc_add_lost(BWController *bwc, uint32_t bytes);
void bwc_add_recv(BWController *bwc, uint32_t bytes);
#endif /* BWCONROLER_H */
================================================
FILE: toxav/group.c
================================================
/** groupav.h
*
* Copyright (C) 2014 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include "group.h"
#include "../toxcore/util.h"
#include "../toxcore/logger.h"
#define GROUP_JBUF_SIZE 6
#define GROUP_JBUF_DEAD_SECONDS 4
typedef struct {
uint16_t sequnum;
uint16_t length;
uint8_t data[];
} Group_Audio_Packet;
typedef struct {
Group_Audio_Packet **queue;
uint32_t size;
uint32_t capacity;
uint16_t bottom;
uint16_t top;
uint64_t last_queued_time;
} Group_JitterBuffer;
static Group_JitterBuffer *create_queue(unsigned int capacity)
{
unsigned int size = 1;
while (size <= capacity) {
size *= 2;
}
Group_JitterBuffer *q;
if (!(q = calloc(sizeof(Group_JitterBuffer), 1))) return NULL;
if (!(q->queue = calloc(sizeof(Group_Audio_Packet *), size))) {
free(q);
return NULL;
}
q->size = size;
q->capacity = capacity;
return q;
}
static void clear_queue(Group_JitterBuffer *q)
{
for (; q->bottom != q->top; ++q->bottom) {
if (q->queue[q->bottom % q->size]) {
free(q->queue[q->bottom % q->size]);
q->queue[q->bottom % q->size] = NULL;
}
}
}
static void terminate_queue(Group_JitterBuffer *q)
{
if (!q) return;
clear_queue(q);
free(q->queue);
free(q);
}
/* Return 0 if packet was queued, -1 if it wasn't.
*/
static int queue(Group_JitterBuffer *q, Group_Audio_Packet *pk)
{
uint16_t sequnum = pk->sequnum;
unsigned int num = sequnum % q->size;
if (!is_timeout(q->last_queued_time, GROUP_JBUF_DEAD_SECONDS)) {
if ((uint32_t)(sequnum - q->bottom) > (1 << 15)) {
/* Drop old packet. */
return -1;
}
}
if ((uint32_t)(sequnum - q->bottom) > q->size) {
clear_queue(q);
q->bottom = sequnum - q->capacity;
q->queue[num] = pk;
q->top = sequnum + 1;
q->last_queued_time = unix_time();
return 0;
}
if (q->queue[num])
return -1;
q->queue[num] = pk;
if ((sequnum - q->bottom) >= (q->top - q->bottom))
q->top = sequnum + 1;
q->last_queued_time = unix_time();
return 0;
}
/* success is 0 when there is nothing to dequeue, 1 when there's a good packet, 2 when there's a lost packet */
static Group_Audio_Packet *dequeue(Group_JitterBuffer *q, int *success)
{
if (q->top == q->bottom) {
*success = 0;
return NULL;
}
unsigned int num = q->bottom % q->size;
if (q->queue[num]) {
Group_Audio_Packet *ret = q->queue[num];
q->queue[num] = NULL;
++q->bottom;
*success = 1;
return ret;
}
if ((uint32_t)(q->top - q->bottom) > q->capacity) {
++q->bottom;
*success = 2;
return NULL;
}
*success = 0;
return NULL;
}
typedef struct {
Group_Chats *g_c;
OpusEncoder *audio_encoder;
unsigned int audio_channels, audio_sample_rate, audio_bitrate;
uint16_t audio_sequnum;
void (*audio_data)(Messenger *m, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples,
uint8_t channels, unsigned int sample_rate, void *userdata);
void *userdata;
} Group_AV;
typedef struct {
Group_JitterBuffer *buffer;
OpusDecoder *audio_decoder;
int decoder_channels;
unsigned int last_packet_samples;
} Group_Peer_AV;
static void kill_group_av(Group_AV *group_av)
{
if (group_av->audio_encoder) {
opus_encoder_destroy(group_av->audio_encoder);
}
free(group_av);
}
static int recreate_encoder(Group_AV *group_av)
{
if (group_av->audio_encoder) {
opus_encoder_destroy(group_av->audio_encoder);
group_av->audio_encoder = NULL;
}
int rc = OPUS_OK;
group_av->audio_encoder = opus_encoder_create(group_av->audio_sample_rate, group_av->audio_channels,
OPUS_APPLICATION_AUDIO, &rc);
if (rc != OPUS_OK) {
LOGGER_ERROR("Error while starting audio encoder: %s", opus_strerror(rc));
group_av->audio_encoder = NULL;
return -1;
}
rc = opus_encoder_ctl(group_av->audio_encoder, OPUS_SET_BITRATE(group_av->audio_bitrate));
if (rc != OPUS_OK) {
LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc));
opus_encoder_destroy(group_av->audio_encoder);
group_av->audio_encoder = NULL;
return -1;
}
rc = opus_encoder_ctl(group_av->audio_encoder, OPUS_SET_COMPLEXITY(10));
if (rc != OPUS_OK) {
LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc));
opus_encoder_destroy(group_av->audio_encoder);
group_av->audio_encoder = NULL;
return -1;
}
return 0;
}
static Group_AV *new_group_av(Group_Chats *g_c, void (*audio_callback)(Messenger *, int, int, const int16_t *,
unsigned int, uint8_t, unsigned int, void *), void *userdata)
{
if (!g_c)
return NULL;
Group_AV *group_av = calloc(1, sizeof(Group_AV));
if (!group_av)
return NULL;
group_av->g_c = g_c;
group_av->audio_data = audio_callback;
group_av->userdata = userdata;
return group_av;
}
static void group_av_peer_new(void *object, int groupnumber, int friendgroupnumber)
{
Group_AV *group_av = object;
Group_Peer_AV *peer_av = calloc(1, sizeof(Group_Peer_AV));
if (!peer_av)
return;
peer_av->buffer = create_queue(GROUP_JBUF_SIZE);
group_peer_set_object(group_av->g_c, groupnumber, friendgroupnumber, peer_av);
}
static void group_av_peer_delete(void *object, int groupnumber, int friendgroupnumber, void *peer_object)
{
Group_Peer_AV *peer_av = peer_object;
if (!peer_av)
return;
if (peer_av->audio_decoder)
opus_decoder_destroy(peer_av->audio_decoder);
terminate_queue(peer_av->buffer);
free(peer_object);
}
static void group_av_groupchat_delete(void *object, int groupnumber)
{
if (object)
kill_group_av(object);
}
static int decode_audio_packet(Group_AV *group_av, Group_Peer_AV *peer_av, int groupnumber, int friendgroupnumber)
{
if (!group_av || !peer_av)
return -1;
int success;
Group_Audio_Packet *pk = dequeue(peer_av->buffer, &success);
if (success == 0)
return -1;
int16_t *out_audio = NULL;
int out_audio_samples = 0;
unsigned int sample_rate = 48000;
if (success == 1) {
int channels = opus_packet_get_nb_channels(pk->data);
if (channels == OPUS_INVALID_PACKET) {
free(pk);
return -1;
}
if (channels != 1 && channels != 2) {
free(pk);
return -1;
}
if (channels != peer_av->decoder_channels) {
if (peer_av->audio_decoder) {
opus_decoder_destroy(peer_av->audio_decoder);
peer_av->audio_decoder = NULL;
}
int rc;
peer_av->audio_decoder = opus_decoder_create(sample_rate, channels, &rc);
if (rc != OPUS_OK) {
LOGGER_ERROR("Error while starting audio decoder: %s", opus_strerror(rc));
free(pk);
return -1;
}
peer_av->decoder_channels = channels;
}
int num_samples = opus_decoder_get_nb_samples(peer_av->audio_decoder, pk->data, pk->length);
out_audio = malloc(num_samples * peer_av->decoder_channels * sizeof(int16_t));
if (!out_audio) {
free(pk);
return -1;
}
out_audio_samples = opus_decode(peer_av->audio_decoder, pk->data, pk->length, out_audio, num_samples, 0);
free(pk);
if (out_audio_samples <= 0)
return -1;
peer_av->last_packet_samples = out_audio_samples;
} else {
if (!peer_av->audio_decoder)
return -1;
if (!peer_av->last_packet_samples)
return -1;
out_audio = malloc(peer_av->last_packet_samples * peer_av->decoder_channels * sizeof(int16_t));
if (!out_audio) {
free(pk);
return -1;
}
out_audio_samples = opus_decode(peer_av->audio_decoder, NULL, 0, out_audio, peer_av->last_packet_samples, 1);
if (out_audio_samples <= 0)
return -1;
}
if (out_audio) {
if (group_av->audio_data)
group_av->audio_data(group_av->g_c->m, groupnumber, friendgroupnumber, out_audio, out_audio_samples,
peer_av->decoder_channels, sample_rate, group_av->userdata);
free(out_audio);
return 0;
}
return -1;
}
static int handle_group_audio_packet(void *object, int groupnumber, int friendgroupnumber, void *peer_object,
const uint8_t *packet, uint16_t length)
{
if (!peer_object || !object || length <= sizeof(uint16_t)) {
return -1;
}
Group_Peer_AV *peer_av = peer_object;
Group_Audio_Packet *pk = calloc(1, sizeof(Group_Audio_Packet) + (length - sizeof(uint16_t)));
if (!pk) {
return -1;
}
uint16_t sequnum;
memcpy(&sequnum, packet, sizeof(sequnum));
pk->sequnum = ntohs(sequnum);
pk->length = length - sizeof(uint16_t);
memcpy(pk->data, packet + sizeof(uint16_t), length - sizeof(uint16_t));
if (queue(peer_av->buffer, pk) == -1) {
free(pk);
return -1;
}
while (decode_audio_packet(object, peer_av, groupnumber, friendgroupnumber) == 0);
return 0;
}
/* Convert groupchat to an A/V groupchat.
*
* return 0 on success.
* return -1 on failure.
*/
static int groupchat_enable_av(Group_Chats *g_c, int groupnumber, void (*audio_callback)(Messenger *, int, int,
const int16_t *, unsigned int, uint8_t, unsigned int, void *), void *userdata)
{
if (groupnumber == -1)
return -1;
Group_AV *group_av = new_group_av(g_c, audio_callback, userdata);
if (group_av == NULL)
return -1;
if (group_set_object(g_c, groupnumber, group_av) == -1
|| callback_groupchat_peer_new(g_c, groupnumber, group_av_peer_new) == -1
|| callback_groupchat_peer_delete(g_c, groupnumber, group_av_peer_delete) == -1
|| callback_groupchat_delete(g_c, groupnumber, group_av_groupchat_delete) == -1) {
kill_group_av(group_av);
return -1;
}
group_lossy_packet_registerhandler(g_c, GROUP_AUDIO_PACKET_ID, &handle_group_audio_packet);
return 0;
}
/* Create a new toxav group.
*
* return group number on success.
* return -1 on failure.
*/
int add_av_groupchat(Group_Chats *g_c, void (*audio_callback)(Messenger *, int, int, const int16_t *, unsigned int,
uint8_t, unsigned int, void *), void *userdata)
{
int groupnumber = add_groupchat(g_c, GROUPCHAT_TYPE_AV);
if (groupnumber == -1) {
return -1;
}
if (groupchat_enable_av(g_c, groupnumber, audio_callback, userdata) == -1) {
del_groupchat(g_c, groupnumber);
return -1;
}
return groupnumber;
}
/* Join a AV group (you need to have been invited first.)
*
* returns group number on success
* returns -1 on failure.
*/
int join_av_groupchat(Group_Chats *g_c, int32_t friendnumber, const uint8_t *data, uint16_t length,
void (*audio_callback)(Messenger *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *),
void *userdata)
{
int groupnumber = join_groupchat(g_c, friendnumber, GROUPCHAT_TYPE_AV, data, length);
if (groupnumber == -1) {
return -1;
}
if (groupchat_enable_av(g_c, groupnumber, audio_callback, userdata) == -1) {
del_groupchat(g_c, groupnumber);
return -1;
}
return groupnumber;
}
/* Send an encoded audio packet to the group chat.
*
* return 0 on success.
* return -1 on failure.
*/
static int send_audio_packet(Group_Chats *g_c, int groupnumber, uint8_t *packet, uint16_t length)
{
if (!length)
return -1;
Group_AV *group_av = group_get_object(g_c, groupnumber);
uint8_t data[1 + sizeof(uint16_t) + length];
data[0] = GROUP_AUDIO_PACKET_ID;
uint16_t sequnum = htons(group_av->audio_sequnum);
memcpy(data + 1, &sequnum, sizeof(sequnum));
memcpy(data + 1 + sizeof(sequnum), packet, length);
if (send_group_lossy_packet(g_c, groupnumber, data, sizeof(data)) == -1)
return -1;
++group_av->audio_sequnum;
return 0;
}
/* Send audio to the group chat.
*
* return 0 on success.
* return -1 on failure.
*/
int group_send_audio(Group_Chats *g_c, int groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels,
unsigned int sample_rate)
{
Group_AV *group_av = group_get_object(g_c, groupnumber);
if (!group_av)
return -1;
if (channels != 1 && channels != 2)
return -1;
if (sample_rate != 8000 && sample_rate != 12000 && sample_rate != 16000 && sample_rate != 24000 && sample_rate != 48000)
return -1;
if (!group_av->audio_encoder || group_av->audio_channels != channels || group_av->audio_sample_rate != sample_rate) {
group_av->audio_channels = channels;
group_av->audio_sample_rate = sample_rate;
if (channels == 1) {
group_av->audio_bitrate = 32000; //TODO: add way of adjusting bitrate
} else {
group_av->audio_bitrate = 64000; //TODO: add way of adjusting bitrate
}
if (recreate_encoder(group_av) == -1)
return -1;
}
uint8_t encoded[1024];
int32_t size = opus_encode(group_av->audio_encoder, pcm, samples, encoded, sizeof(encoded));
if (size <= 0)
return -1;
return send_audio_packet(g_c, groupnumber, encoded, size);
}
================================================
FILE: toxav/group.h
================================================
/** groupav.c
*
* Copyright (C) 2014 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*/
/* Audio encoding/decoding */
#include
#include "../toxcore/group.h"
#define GROUP_AUDIO_PACKET_ID 192
/* Create a new toxav group.
*
* return group number on success.
* return -1 on failure.
*/
int add_av_groupchat(Group_Chats *g_c, void (*audio_callback)(Messenger *, int, int, const int16_t *, unsigned int,
uint8_t, unsigned int, void *), void *userdata);
/* Join a AV group (you need to have been invited first.)
*
* returns group number on success
* returns -1 on failure.
*/
int join_av_groupchat(Group_Chats *g_c, int32_t friendnumber, const uint8_t *data, uint16_t length,
void (*audio_callback)(Messenger *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *),
void *userdata);
/* Send audio to the group chat.
*
* return 0 on success.
* return -1 on failure.
*/
int group_send_audio(Group_Chats *g_c, int groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels,
unsigned int sample_rate);
================================================
FILE: toxav/msi.c
================================================
/** msi.c
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include "msi.h"
#include "../toxcore/logger.h"
#include "../toxcore/util.h"
#include
#include
#include
#include
#include
#define MSI_MAXMSG_SIZE 256
/**
* Protocol:
*
* |id [1 byte]| |size [1 byte]| |data [$size bytes]| |...{repeat}| |0 {end byte}|
*/
typedef enum {
IDRequest = 1,
IDError,
IDCapabilities,
} MSIHeaderID;
typedef enum {
requ_init,
requ_push,
requ_pop,
} MSIRequest;
#define GENERIC_HEADER(header, val_type) \
typedef struct { \
val_type value; \
bool exists; \
} MSIHeader##header
GENERIC_HEADER (Request, MSIRequest);
GENERIC_HEADER (Error, MSIError);
GENERIC_HEADER (Capabilities, uint8_t);
typedef struct {
MSIHeaderRequest request;
MSIHeaderError error;
MSIHeaderCapabilities capabilities;
} MSIMessage;
void msg_init (MSIMessage *dest, MSIRequest request);
int msg_parse_in (MSIMessage *dest, const uint8_t *data, uint16_t length);
uint8_t *msg_parse_header_out (MSIHeaderID id, uint8_t *dest, const void *value, uint8_t value_len, uint16_t *length);
static int send_message (Messenger *m, uint32_t friend_number, const MSIMessage *msg);
int send_error (Messenger *m, uint32_t friend_number, MSIError error);
static int invoke_callback(MSICall *call, MSICallbackID cb);
static MSICall *get_call (MSISession *session, uint32_t friend_number);
MSICall *new_call (MSISession *session, uint32_t friend_number);
void kill_call (MSICall *call);
void on_peer_status(Messenger *m, uint32_t friend_number, uint8_t status, void *data);
void handle_init (MSICall *call, const MSIMessage *msg);
void handle_push (MSICall *call, const MSIMessage *msg);
void handle_pop (MSICall *call, const MSIMessage *msg);
void handle_msi_packet (Messenger *m, uint32_t friend_number, const uint8_t *data, uint16_t length, void *object);
/**
* Public functions
*/
void msi_register_callback (MSISession *session, msi_action_cb *callback, MSICallbackID id)
{
if (!session)
return;
pthread_mutex_lock(session->mutex);
session->callbacks[id] = callback;
pthread_mutex_unlock(session->mutex);
}
MSISession *msi_new (Messenger *m)
{
if (m == NULL) {
LOGGER_ERROR("Could not init session on empty messenger!");
return NULL;
}
MSISession *retu = calloc (sizeof (MSISession), 1);
if (retu == NULL) {
LOGGER_ERROR("Allocation failed! Program might misbehave!");
return NULL;
}
if (create_recursive_mutex(retu->mutex) != 0) {
LOGGER_ERROR("Failed to init mutex! Program might misbehave");
free(retu);
return NULL;
}
retu->messenger = m;
m_callback_msi_packet(m, handle_msi_packet, retu);
/* This is called when remote terminates session */
m_callback_connectionstatus_internal_av(m, on_peer_status, retu);
LOGGER_DEBUG("New msi session: %p ", retu);
return retu;
}
int msi_kill (MSISession *session)
{
if (session == NULL) {
LOGGER_ERROR("Tried to terminate non-existing session");
return -1;
}
m_callback_msi_packet((struct Messenger *) session->messenger, NULL, NULL);
if (pthread_mutex_trylock(session->mutex) != 0) {
LOGGER_ERROR ("Failed to aquire lock on msi mutex");
return -1;
}
if (session->calls) {
MSIMessage msg;
msg_init(&msg, requ_pop);
MSICall *it = get_call(session, session->calls_head);
while (it) {
send_message(session->messenger, it->friend_number, &msg);
MSICall *temp_it = it;
it = it->next;
kill_call(temp_it); /* This will eventually free session->calls */
}
}
pthread_mutex_unlock(session->mutex);
pthread_mutex_destroy(session->mutex);
LOGGER_DEBUG("Terminated session: %p", session);
free (session);
return 0;
}
int msi_invite (MSISession *session, MSICall **call, uint32_t friend_number, uint8_t capabilities)
{
if (!session)
return -1;
LOGGER_DEBUG("Session: %p Inviting friend: %u", session, friend_number);
if (pthread_mutex_trylock(session->mutex) != 0) {
LOGGER_ERROR ("Failed to aquire lock on msi mutex");
return -1;
}
if (get_call(session, friend_number) != NULL) {
LOGGER_ERROR("Already in a call");
pthread_mutex_unlock(session->mutex);
return -1;
}
(*call) = new_call (session, friend_number);
if (*call == NULL) {
pthread_mutex_unlock(session->mutex);
return -1;
}
(*call)->self_capabilities = capabilities;
MSIMessage msg;
msg_init(&msg, requ_init);
msg.capabilities.exists = true;
msg.capabilities.value = capabilities;
send_message ((*call)->session->messenger, (*call)->friend_number, &msg);
(*call)->state = msi_CallRequesting;
LOGGER_DEBUG("Invite sent");
pthread_mutex_unlock(session->mutex);
return 0;
}
int msi_hangup (MSICall *call)
{
if (!call || !call->session)
return -1;
LOGGER_DEBUG("Session: %p Hanging up call with friend: %u", call->session, call->friend_number);
MSISession *session = call->session;
if (pthread_mutex_trylock(session->mutex) != 0) {
LOGGER_ERROR ("Failed to aquire lock on msi mutex");
return -1;
}
if (call->state == msi_CallInactive) {
LOGGER_ERROR("Call is in invalid state!");
pthread_mutex_unlock(session->mutex);
return -1;
}
MSIMessage msg;
msg_init(&msg, requ_pop);
send_message (session->messenger, call->friend_number, &msg);
kill_call(call);
pthread_mutex_unlock(session->mutex);
return 0;
}
int msi_answer (MSICall *call, uint8_t capabilities)
{
if (!call || !call->session)
return -1;
LOGGER_DEBUG("Session: %p Answering call from: %u", call->session, call->friend_number);
MSISession *session = call->session;
if (pthread_mutex_trylock(session->mutex) != 0) {
LOGGER_ERROR ("Failed to aquire lock on msi mutex");
return -1;
}
if (call->state != msi_CallRequested) {
/* Though sending in invalid state will not cause anything wierd
* Its better to not do it like a maniac */
LOGGER_ERROR("Call is in invalid state!");
pthread_mutex_unlock(session->mutex);
return -1;
}
call->self_capabilities = capabilities;
MSIMessage msg;
msg_init(&msg, requ_push);
msg.capabilities.exists = true;
msg.capabilities.value = capabilities;
send_message (session->messenger, call->friend_number, &msg);
call->state = msi_CallActive;
pthread_mutex_unlock(session->mutex);
return 0;
}
int msi_change_capabilities(MSICall *call, uint8_t capabilities)
{
if (!call || !call->session)
return -1;
LOGGER_DEBUG("Session: %p Trying to change capabilities to friend %u", call->session, call->friend_number);
MSISession *session = call->session;
if (pthread_mutex_trylock(session->mutex) != 0) {
LOGGER_ERROR ("Failed to aquire lock on msi mutex");
return -1;
}
if (call->state != msi_CallActive) {
LOGGER_ERROR("Call is in invalid state!");
pthread_mutex_unlock(session->mutex);
return -1;
}
call->self_capabilities = capabilities;
MSIMessage msg;
msg_init(&msg, requ_push);
msg.capabilities.exists = true;
msg.capabilities.value = capabilities;
send_message (call->session->messenger, call->friend_number, &msg);
pthread_mutex_unlock(session->mutex);
return 0;
}
/**
* Private functions
*/
void msg_init(MSIMessage *dest, MSIRequest request)
{
memset(dest, 0, sizeof(*dest));
dest->request.exists = true;
dest->request.value = request;
}
int msg_parse_in (MSIMessage *dest, const uint8_t *data, uint16_t length)
{
/* Parse raw data received from socket into MSIMessage struct */
#define CHECK_SIZE(bytes, constraint, size) \
if ((constraint -= (2 + size)) < 1) { LOGGER_ERROR("Read over length!"); return -1; } \
if (bytes[1] != size) { LOGGER_ERROR("Invalid data size!"); return -1; }
#define CHECK_ENUM_HIGH(bytes, enum_high) /* Assumes size == 1 */ \
if (bytes[2] > enum_high) { LOGGER_ERROR("Failed enum high limit!"); return -1; }
#define SET_UINT8(bytes, header) do { \
header.value = bytes[2]; \
header.exists = true; \
bytes += 3; \
} while(0)
#define SET_UINT16(bytes, header) do { \
memcpy(&header.value, bytes + 2, 2);\
header.exists = true; \
bytes += 4; \
} while(0)
assert(dest);
if (length == 0 || data[length - 1]) { /* End byte must have value 0 */
LOGGER_ERROR("Invalid end byte");
return -1;
}
memset(dest, 0, sizeof(*dest));
const uint8_t *it = data;
int size_constraint = length;
while (*it) {/* until end byte is hit */
switch (*it) {
case IDRequest:
CHECK_SIZE(it, size_constraint, 1);
CHECK_ENUM_HIGH(it, requ_pop);
SET_UINT8(it, dest->request);
break;
case IDError:
CHECK_SIZE(it, size_constraint, 1);
CHECK_ENUM_HIGH(it, msi_EUndisclosed);
SET_UINT8(it, dest->error);
break;
case IDCapabilities:
CHECK_SIZE(it, size_constraint, 1);
SET_UINT8(it, dest->capabilities);
break;
default:
LOGGER_ERROR("Invalid id byte");
return -1;
break;
}
}
if (dest->request.exists == false) {
LOGGER_ERROR("Invalid request field!");
return -1;
}
return 0;
#undef CHECK_SIZE
#undef CHECK_ENUM_HIGH
#undef SET_UINT8
#undef SET_UINT16
}
uint8_t *msg_parse_header_out (MSIHeaderID id, uint8_t *dest, const void *value, uint8_t value_len, uint16_t *length)
{
/* Parse a single header for sending */
assert(dest);
assert(value);
assert(value_len);
*dest = id;
dest ++;
*dest = value_len;
dest ++;
memcpy(dest, value, value_len);
*length += (2 + value_len);
return dest + value_len; /* Set to next position ready to be written */
}
int send_message (Messenger *m, uint32_t friend_number, const MSIMessage *msg)
{
/* Parse and send message */
assert(m);
uint8_t parsed [MSI_MAXMSG_SIZE];
uint8_t *it = parsed;
uint16_t size = 0;
if (msg->request.exists) {
uint8_t cast = msg->request.value;
it = msg_parse_header_out(IDRequest, it, &cast,
sizeof(cast), &size);
} else {
LOGGER_DEBUG("Must have request field");
return -1;
}
if (msg->error.exists) {
uint8_t cast = msg->error.value;
it = msg_parse_header_out(IDError, it, &cast,
sizeof(cast), &size);
}
if (msg->capabilities.exists) {
it = msg_parse_header_out(IDCapabilities, it, &msg->capabilities.value,
sizeof(msg->capabilities.value), &size);
}
if (it == parsed) {
LOGGER_WARNING("Parsing message failed; empty message");
return -1;
}
*it = 0;
size ++;
if (m_msi_packet(m, friend_number, parsed, size)) {
LOGGER_DEBUG("Sent message");
return 0;
}
return -1;
}
int send_error (Messenger *m, uint32_t friend_number, MSIError error)
{
/* Send error message */
assert(m);
LOGGER_DEBUG("Sending error: %d to friend: %d", error, friend_number);
MSIMessage msg;
msg_init(&msg, requ_pop);
msg.error.exists = true;
msg.error.value = error;
send_message (m, friend_number, &msg);
return 0;
}
int invoke_callback(MSICall *call, MSICallbackID cb)
{
assert(call);
if (call->session->callbacks[cb]) {
LOGGER_DEBUG("Invoking callback function: %d", cb);
if (call->session->callbacks[cb] (call->session->av, call) != 0) {
LOGGER_WARNING("Callback state handling failed, sending error");
goto FAILURE;
}
return 0;
}
FAILURE:
/* If no callback present or error happened while handling,
* an error message will be sent to friend
*/
if (call->error == msi_ENone)
call->error = msi_EHandle;
return -1;
}
static MSICall *get_call (MSISession *session, uint32_t friend_number)
{
assert(session);
if (session->calls == NULL || session->calls_tail < friend_number)
return NULL;
return session->calls[friend_number];
}
MSICall *new_call (MSISession *session, uint32_t friend_number)
{
assert(session);
MSICall *rc = calloc(sizeof(MSICall), 1);
if (rc == NULL)
return NULL;
rc->session = session;
rc->friend_number = friend_number;
if (session->calls == NULL) { /* Creating */
session->calls = calloc (sizeof(MSICall *), friend_number + 1);
if (session->calls == NULL) {
free(rc);
return NULL;
}
session->calls_tail = session->calls_head = friend_number;
} else if (session->calls_tail < friend_number) { /* Appending */
void *tmp = realloc(session->calls, sizeof(MSICall *) * (friend_number + 1));
if (tmp == NULL) {
free(rc);
return NULL;
}
session->calls = tmp;
/* Set fields in between to null */
uint32_t i = session->calls_tail + 1;
for (; i < friend_number; i ++)
session->calls[i] = NULL;
rc->prev = session->calls[session->calls_tail];
session->calls[session->calls_tail]->next = rc;
session->calls_tail = friend_number;
} else if (session->calls_head > friend_number) { /* Inserting at front */
rc->next = session->calls[session->calls_head];
session->calls[session->calls_head]->prev = rc;
session->calls_head = friend_number;
}
session->calls[friend_number] = rc;
return rc;
}
void kill_call (MSICall *call)
{
/* Assume that session mutex is locked */
if (call == NULL)
return;
LOGGER_DEBUG("Killing call: %p", call);
MSISession *session = call->session;
MSICall *prev = call->prev;
MSICall *next = call->next;
if (prev)
prev->next = next;
else if (next)
session->calls_head = next->friend_number;
else goto CLEAR_CONTAINER;
if (next)
next->prev = prev;
else if (prev)
session->calls_tail = prev->friend_number;
else goto CLEAR_CONTAINER;
session->calls[call->friend_number] = NULL;
free(call);
return;
CLEAR_CONTAINER:
session->calls_head = session->calls_tail = 0;
free(session->calls);
free(call);
session->calls = NULL;
}
void on_peer_status(Messenger *m, uint32_t friend_number, uint8_t status, void *data)
{
(void)m;
MSISession *session = data;
switch (status) {
case 0: { /* Friend is now offline */
LOGGER_DEBUG("Friend %d is now offline", friend_number);
pthread_mutex_lock(session->mutex);
MSICall *call = get_call(session, friend_number);
if (call == NULL) {
pthread_mutex_unlock(session->mutex);
return;
}
invoke_callback(call, msi_OnPeerTimeout); /* Failure is ignored */
kill_call(call);
pthread_mutex_unlock(session->mutex);
}
break;
default:
break;
}
}
void handle_init (MSICall *call, const MSIMessage *msg)
{
assert(call);
LOGGER_DEBUG("Session: %p Handling 'init' friend: %d", call->session, call->friend_number);
if (!msg->capabilities.exists) {
LOGGER_WARNING("Session: %p Invalid capabilities on 'init'");
call->error = msi_EInvalidMessage;
goto FAILURE;
}
switch (call->state) {
case msi_CallInactive: {
/* Call requested */
call->peer_capabilities = msg->capabilities.value;
call->state = msi_CallRequested;
if (invoke_callback(call, msi_OnInvite) == -1)
goto FAILURE;
}
break;
case msi_CallActive: {
/* If peer sent init while the call is already
* active it's probable that he is trying to
* re-call us while the call is not terminated
* on our side. We can assume that in this case
* we can automatically answer the re-call.
*/
LOGGER_INFO("Friend is recalling us");
MSIMessage msg;
msg_init(&msg, requ_push);
msg.capabilities.exists = true;
msg.capabilities.value = call->self_capabilities;
send_message (call->session->messenger, call->friend_number, &msg);
/* If peer changed capabilities during re-call they will
* be handled accordingly during the next step
*/
}
break;
default: {
LOGGER_WARNING("Session: %p Invalid state on 'init'");
call->error = msi_EInvalidState;
goto FAILURE;
}
break;
}
return;
FAILURE:
send_error(call->session->messenger, call->friend_number, call->error);
kill_call(call);
}
void handle_push (MSICall *call, const MSIMessage *msg)
{
assert(call);
LOGGER_DEBUG("Session: %p Handling 'push' friend: %d", call->session, call->friend_number);
if (!msg->capabilities.exists) {
LOGGER_WARNING("Session: %p Invalid capabilities on 'push'");
call->error = msi_EInvalidMessage;
goto FAILURE;
}
switch (call->state) {
case msi_CallActive: {
/* Only act if capabilities changed */
if (call->peer_capabilities != msg->capabilities.value) {
LOGGER_INFO("Friend is changing capabilities to: %u", msg->capabilities.value);
call->peer_capabilities = msg->capabilities.value;
if (invoke_callback(call, msi_OnCapabilities) == -1)
goto FAILURE;
}
}
break;
case msi_CallRequesting: {
LOGGER_INFO("Friend answered our call");
/* Call started */
call->peer_capabilities = msg->capabilities.value;
call->state = msi_CallActive;
if (invoke_callback(call, msi_OnStart) == -1)
goto FAILURE;
}
break;
/* Pushes during initialization state are ignored */
case msi_CallInactive:
case msi_CallRequested: {
LOGGER_WARNING("Ignoring invalid push");
}
break;
}
return;
FAILURE:
send_error(call->session->messenger, call->friend_number, call->error);
kill_call(call);
}
void handle_pop (MSICall *call, const MSIMessage *msg)
{
assert(call);
LOGGER_DEBUG("Session: %p Handling 'pop', friend id: %d", call->session, call->friend_number);
/* callback errors are ignored */
if (msg->error.exists) {
LOGGER_WARNING("Friend detected an error: %d", msg->error.value);
call->error = msg->error.value;
invoke_callback(call, msi_OnError);
} else switch (call->state) {
case msi_CallInactive: {
LOGGER_ERROR("Handling what should be impossible case");
abort();
}
break;
case msi_CallActive: {
/* Hangup */
LOGGER_INFO("Friend hung up on us");
invoke_callback(call, msi_OnEnd);
}
break;
case msi_CallRequesting: {
/* Reject */
LOGGER_INFO("Friend rejected our call");
invoke_callback(call, msi_OnEnd);
}
break;
case msi_CallRequested: {
/* Cancel */
LOGGER_INFO("Friend canceled call invite");
invoke_callback(call, msi_OnEnd);
}
break;
}
kill_call (call);
}
void handle_msi_packet (Messenger *m, uint32_t friend_number, const uint8_t *data, uint16_t length, void *object)
{
LOGGER_DEBUG("Got msi message");
MSISession *session = object;
MSIMessage msg;
if (msg_parse_in (&msg, data, length) == -1) {
LOGGER_WARNING("Error parsing message");
send_error(m, friend_number, msi_EInvalidMessage);
return;
} else {
LOGGER_DEBUG("Successfully parsed message");
}
pthread_mutex_lock(session->mutex);
MSICall *call = get_call(session, friend_number);
if (call == NULL) {
if (msg.request.value != requ_init) {
send_error(m, friend_number, msi_EStrayMessage);
pthread_mutex_unlock(session->mutex);
return;
}
call = new_call(session, friend_number);
if (call == NULL) {
send_error(m, friend_number, msi_ESystem);
pthread_mutex_unlock(session->mutex);
return;
}
}
switch (msg.request.value) {
case requ_init:
handle_init(call, &msg);
break;
case requ_push:
handle_push(call, &msg);
break;
case requ_pop:
handle_pop(call, &msg); /* always kills the call */
break;
}
pthread_mutex_unlock(session->mutex);
}
================================================
FILE: toxav/msi.h
================================================
/** msi.h
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef MSI_H
#define MSI_H
#include
#include
#include "audio.h"
#include "video.h"
#include "../toxcore/Messenger.h"
/**
* Error codes.
*/
typedef enum {
msi_ENone,
msi_EInvalidMessage,
msi_EInvalidParam,
msi_EInvalidState,
msi_EStrayMessage,
msi_ESystem,
msi_EHandle,
msi_EUndisclosed, /* NOTE: must be last enum otherwise parsing will not work */
} MSIError;
/**
* Supported capabilities
*/
typedef enum {
msi_CapSAudio = 4, /* sending audio */
msi_CapSVideo = 8, /* sending video */
msi_CapRAudio = 16, /* receiving audio */
msi_CapRVideo = 32, /* receiving video */
} MSICapabilities;
/**
* Call state identifiers.
*/
typedef enum {
msi_CallInactive, /* Default */
msi_CallActive,
msi_CallRequesting, /* when sending call invite */
msi_CallRequested, /* when getting call invite */
} MSICallState;
/**
* Callbacks ids that handle the states
*/
typedef enum {
msi_OnInvite, /* Incoming call */
msi_OnStart, /* Call (RTP transmission) started */
msi_OnEnd, /* Call that was active ended */
msi_OnError, /* On protocol error */
msi_OnPeerTimeout, /* Peer timed out; stop the call */
msi_OnCapabilities, /* Peer requested capabilities change */
} MSICallbackID;
/**
* The call struct. Please do not modify outside msi.c
*/
typedef struct MSICall_s {
struct MSISession_s *session; /* Session pointer */
MSICallState state;
uint8_t peer_capabilities; /* Peer capabilities */
uint8_t self_capabilities; /* Self capabilities */
uint16_t peer_vfpsz; /* Video frame piece size */
uint32_t friend_number; /* Index of this call in MSISession */
MSIError error; /* Last error */
void *av_call; /* Pointer to av call handler */
struct MSICall_s *next;
struct MSICall_s *prev;
} MSICall;
/**
* Expected return on success is 0, if any other number is
* returned the call is considered errored and will be handled
* as such which means it will be terminated without any notice.
*/
typedef int msi_action_cb (void *av, MSICall *call);
/**
* Control session struct. Please do not modify outside msi.c
*/
typedef struct MSISession_s {
/* Call handlers */
MSICall **calls;
uint32_t calls_tail;
uint32_t calls_head;
void *av;
Messenger *messenger;
pthread_mutex_t mutex[1];
msi_action_cb *callbacks[7];
} MSISession;
/**
* Start the control session.
*/
MSISession *msi_new(Messenger *m);
/**
* Terminate control session. NOTE: all calls will be freed
*/
int msi_kill(MSISession *session);
/**
* Callback setter.
*/
void msi_register_callback(MSISession *session, msi_action_cb *callback, MSICallbackID id);
/**
* Send invite request to friend_number.
*/
int msi_invite(MSISession *session, MSICall **call, uint32_t friend_number, uint8_t capabilities);
/**
* Hangup call. NOTE: 'call' will be freed
*/
int msi_hangup(MSICall *call);
/**
* Answer call request.
*/
int msi_answer(MSICall *call, uint8_t capabilities);
/**
* Change capabilities of the call.
*/
int msi_change_capabilities(MSICall *call, uint8_t capabilities);
#endif /* MSI_H */
================================================
FILE: toxav/rtp.c
================================================
/** rtp.c
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include "rtp.h"
#include "bwcontroller.h"
#include "../toxcore/logger.h"
#include "../toxcore/util.h"
#include "../toxcore/Messenger.h"
#include
#include
int handle_rtp_packet (Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object);
RTPSession *rtp_new (int payload_type, Messenger *m, uint32_t friendnumber,
BWController *bwc, void *cs,
int (*mcb) (void *, struct RTPMessage *))
{
assert(mcb);
assert(cs);
assert(m);
RTPSession *retu = calloc(1, sizeof(RTPSession));
if (!retu) {
LOGGER_WARNING("Alloc failed! Program might misbehave!");
return NULL;
}
retu->ssrc = random_int();
retu->payload_type = payload_type;
retu->m = m;
retu->friend_number = friendnumber;
/* Also set payload type as prefix */
retu->bwc = bwc;
retu->cs = cs;
retu->mcb = mcb;
if (-1 == rtp_allow_receiving(retu)) {
LOGGER_WARNING("Failed to start rtp receiving mode");
free(retu);
return NULL;
}
return retu;
}
void rtp_kill (RTPSession *session)
{
if (!session)
return;
LOGGER_DEBUG("Terminated RTP session: %p", session);
rtp_stop_receiving (session);
free (session);
}
int rtp_allow_receiving(RTPSession *session)
{
if (session == NULL)
return -1;
if (m_callback_rtp_packet(session->m, session->friend_number, session->payload_type,
handle_rtp_packet, session) == -1) {
LOGGER_WARNING("Failed to register rtp receive handler");
return -1;
}
LOGGER_DEBUG("Started receiving on session: %p", session);
return 0;
}
int rtp_stop_receiving(RTPSession *session)
{
if (session == NULL)
return -1;
m_callback_rtp_packet(session->m, session->friend_number, session->payload_type, NULL, NULL);
LOGGER_DEBUG("Stopped receiving on session: %p", session);
return 0;
}
int rtp_send_data (RTPSession *session, const uint8_t *data, uint16_t length)
{
if (!session) {
LOGGER_WARNING("No session!");
return -1;
}
uint8_t rdata[length + sizeof(struct RTPHeader) + 1];
memset(rdata, 0, sizeof(rdata));
rdata[0] = session->payload_type;
struct RTPHeader *header = (struct RTPHeader *)(rdata + 1);
header->ve = 2;
header->pe = 0;
header->xe = 0;
header->cc = 0;
header->ma = 0;
header->pt = session->payload_type % 128;
header->sequnum = htons(session->sequnum);
header->timestamp = htonl(current_time_monotonic());
header->ssrc = htonl(session->ssrc);
header->cpart = 0;
header->tlen = htons(length);
if (MAX_CRYPTO_DATA_SIZE > length + sizeof(struct RTPHeader) + 1) {
/**
* The lenght is lesser than the maximum allowed lenght (including header)
* Send the packet in single piece.
*/
memcpy(rdata + 1 + sizeof(struct RTPHeader), data, length);
if (-1 == send_custom_lossy_packet(session->m, session->friend_number, rdata, sizeof(rdata)))
LOGGER_WARNING("RTP send failed (len: %d)! std error: %s", sizeof(rdata), strerror(errno));
} else {
/**
* The lenght is greater than the maximum allowed lenght (including header)
* Send the packet in multiple pieces.
*/
uint16_t sent = 0;
uint16_t piece = MAX_CRYPTO_DATA_SIZE - (sizeof(struct RTPHeader) + 1);
while ((length - sent) + sizeof(struct RTPHeader) + 1 > MAX_CRYPTO_DATA_SIZE) {
memcpy(rdata + 1 + sizeof(struct RTPHeader), data + sent, piece);
if (-1 == send_custom_lossy_packet(session->m, session->friend_number,
rdata, piece + sizeof(struct RTPHeader) + 1))
LOGGER_WARNING("RTP send failed (len: %d)! std error: %s",
piece + sizeof(struct RTPHeader) + 1, strerror(errno));
sent += piece;
header->cpart = htons(sent);
}
/* Send remaining */
piece = length - sent;
if (piece) {
memcpy(rdata + 1 + sizeof(struct RTPHeader), data + sent, piece);
if (-1 == send_custom_lossy_packet(session->m, session->friend_number, rdata,
piece + sizeof(struct RTPHeader) + 1))
LOGGER_WARNING("RTP send failed (len: %d)! std error: %s",
piece + sizeof(struct RTPHeader) + 1, strerror(errno));
}
}
session->sequnum ++;
return 0;
}
bool chloss (const RTPSession *session, const struct RTPHeader *header)
{
if (ntohl(header->timestamp) < session->rtimestamp) {
uint16_t hosq, lost = 0;
hosq = ntohs(header->sequnum);
lost = (hosq > session->rsequnum) ?
(session->rsequnum + 65535) - hosq :
session->rsequnum - hosq;
fprintf (stderr, "Lost packet\n");
while (lost --)
bwc_add_lost(session->bwc , 0);
return true;
}
return false;
}
struct RTPMessage *new_message (size_t allocate_len, const uint8_t *data, uint16_t data_length)
{
assert(allocate_len >= data_length);
struct RTPMessage *msg = calloc(sizeof(struct RTPMessage) + (allocate_len - sizeof(struct RTPHeader)), 1);
msg->len = data_length - sizeof(struct RTPHeader);
memcpy(&msg->header, data, data_length);
msg->header.sequnum = ntohs(msg->header.sequnum);
msg->header.timestamp = ntohl(msg->header.timestamp);
msg->header.ssrc = ntohl(msg->header.ssrc);
msg->header.cpart = ntohs(msg->header.cpart);
msg->header.tlen = ntohs(msg->header.tlen);
return msg;
}
int handle_rtp_packet (Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object)
{
(void) m;
(void) friendnumber;
RTPSession *session = object;
data ++;
length--;
if (!session || length < sizeof (struct RTPHeader)) {
LOGGER_WARNING("No session or invalid length of received buffer!");
return -1;
}
const struct RTPHeader *header = (struct RTPHeader *) data;
if (header->pt != session->payload_type % 128) {
LOGGER_WARNING("Invalid payload type with the session");
return -1;
}
if (ntohs(header->cpart) >= ntohs(header->tlen)) {
/* Never allow this case to happen */
return -1;
}
bwc_feed_avg(session->bwc, length);
if (ntohs(header->tlen) == length - sizeof (struct RTPHeader)) {
/* The message is sent in single part */
/* Only allow messages which have arrived in order;
* drop late messages
*/
if (chloss(session, header)) {
return 0;
} else {
/* Message is not late; pick up the latest parameters */
session->rsequnum = ntohs(header->sequnum);
session->rtimestamp = ntohl(header->timestamp);
}
bwc_add_recv(session->bwc, length);
/* Invoke processing of active multiparted message */
if (session->mp) {
if (session->mcb)
session->mcb (session->cs, session->mp);
else
free(session->mp);
session->mp = NULL;
}
/* The message came in the allowed time;
* process it only if handler for the session is present.
*/
if (!session->mcb)
return 0;
return session->mcb (session->cs, new_message(length, data, length));
} else {
/* The message is sent in multiple parts */
if (session->mp) {
/* There are 2 possible situations in this case:
* 1) being that we got the part of already processing message.
* 2) being that we got the part of a new/old message.
*
* We handle them differently as we only allow a single multiparted
* processing message
*/
if (session->mp->header.sequnum == ntohs(header->sequnum) &&
session->mp->header.timestamp == ntohl(header->timestamp)) {
/* First case */
/* Make sure we have enough allocated memory */
if (session->mp->header.tlen - session->mp->len < length - sizeof(struct RTPHeader) ||
session->mp->header.tlen <= ntohs(header->cpart)) {
/* There happened to be some corruption on the stream;
* continue wihtout this part
*/
return 0;
}
memcpy(session->mp->data + ntohs(header->cpart), data + sizeof(struct RTPHeader),
length - sizeof(struct RTPHeader));
session->mp->len += length - sizeof(struct RTPHeader);
bwc_add_recv(session->bwc, length);
if (session->mp->len == session->mp->header.tlen) {
/* Received a full message; now push it for the further
* processing.
*/
if (session->mcb)
session->mcb (session->cs, session->mp);
else
free(session->mp);
session->mp = NULL;
}
} else {
/* Second case */
if (session->mp->header.timestamp > ntohl(header->timestamp))
/* The received message part is from the old message;
* discard it.
*/
return 0;
/* Measure missing parts of the old message */
bwc_add_lost(session->bwc,
(session->mp->header.tlen - session->mp->len) +
/* Must account sizes of rtp headers too */
((session->mp->header.tlen - session->mp->len) /
MAX_CRYPTO_DATA_SIZE) * sizeof(struct RTPHeader) );
/* Push the previous message for processing */
if (session->mcb)
session->mcb (session->cs, session->mp);
else
free(session->mp);
session->mp = NULL;
goto NEW_MULTIPARTED;
}
} else {
/* In this case threat the message as if it was received in order
*/
/* This is also a point for new multiparted messages */
NEW_MULTIPARTED:
/* Only allow messages which have arrived in order;
* drop late messages
*/
if (chloss(session, header)) {
return 0;
} else {
/* Message is not late; pick up the latest parameters */
session->rsequnum = ntohs(header->sequnum);
session->rtimestamp = ntohl(header->timestamp);
}
bwc_add_recv(session->bwc, length);
/* Again, only store message if handler is present
*/
if (session->mcb) {
session->mp = new_message(ntohs(header->tlen) + sizeof(struct RTPHeader), data, length);
/* Reposition data if necessary */
if (ntohs(header->cpart));
memmove(session->mp->data + ntohs(header->cpart), session->mp->data, session->mp->len);
}
}
}
return 0;
}
================================================
FILE: toxav/rtp.h
================================================
/** rtp.h
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef RTP_H
#define RTP_H
#include "bwcontroller.h"
#include "../toxcore/Messenger.h"
#include "stdbool.h"
/**
* Payload type identifier. Also used as rtp callback prefix.
*/
enum {
rtp_TypeAudio = 192,
rtp_TypeVideo,
};
struct RTPHeader {
/* Standard RTP header */
#ifndef WORDS_BIGENDIAN
uint16_t cc: 4; /* Contributing sources count */
uint16_t xe: 1; /* Extra header */
uint16_t pe: 1; /* Padding */
uint16_t ve: 2; /* Version */
uint16_t pt: 7; /* Payload type */
uint16_t ma: 1; /* Marker */
#else
uint16_t ve: 2; /* Version */
uint16_t pe: 1; /* Padding */
uint16_t xe: 1; /* Extra header */
uint16_t cc: 4; /* Contributing sources count */
uint16_t ma: 1; /* Marker */
uint16_t pt: 7; /* Payload type */
#endif
uint16_t sequnum;
uint32_t timestamp;
uint32_t ssrc;
uint32_t csrc[16];
/* Non-standard TOX-specific fields */
uint16_t cpart;/* Data offset of the current part */
uint16_t tlen; /* Total message lenght */
} __attribute__ ((packed));
/* Check alignment */
typedef char __fail_if_misaligned_1 [ sizeof(struct RTPHeader) == 80 ? 1 : -1 ];
struct RTPMessage {
uint16_t len;
struct RTPHeader header;
uint8_t data[];
} __attribute__ ((packed));
/* Check alignment */
typedef char __fail_if_misaligned_2 [ sizeof(struct RTPMessage) == 82 ? 1 : -1 ];
/**
* RTP control session.
*/
typedef struct {
uint8_t payload_type;
uint16_t sequnum; /* Sending sequence number */
uint16_t rsequnum; /* Receiving sequence number */
uint32_t rtimestamp;
uint32_t ssrc;
struct RTPMessage *mp; /* Expected parted message */
Messenger *m;
uint32_t friend_number;
BWController *bwc;
void *cs;
int (*mcb) (void *, struct RTPMessage *msg);
} RTPSession;
RTPSession *rtp_new (int payload_type, Messenger *m, uint32_t friend_num,
BWController *bwc, void *cs,
int (*mcb) (void *, struct RTPMessage *));
void rtp_kill (RTPSession *session);
int rtp_allow_receiving (RTPSession *session);
int rtp_stop_receiving (RTPSession *session);
int rtp_send_data (RTPSession *session, const uint8_t *data, uint16_t length);
#endif /* RTP_H */
================================================
FILE: toxav/toxav.c
================================================
/** toxav.c
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include "msi.h"
#include "rtp.h"
#include "../toxcore/Messenger.h"
#include "../toxcore/logger.h"
#include "../toxcore/util.h"
#include
#include
#include
#define MAX_ENCODE_TIME_US ((1000 / 24) * 1000)
typedef struct ToxAVCall_s {
ToxAV *av;
pthread_mutex_t mutex_audio[1];
PAIR(RTPSession *, ACSession *) audio;
pthread_mutex_t mutex_video[1];
PAIR(RTPSession *, VCSession *) video;
BWController *bwc;
bool active;
MSICall *msi_call;
uint32_t friend_number;
uint32_t audio_bit_rate; /* Sending audio bit rate */
uint32_t video_bit_rate; /* Sending video bit rate */
/** Required for monitoring changes in states */
uint8_t previous_self_capabilities;
pthread_mutex_t mutex[1];
struct ToxAVCall_s *prev;
struct ToxAVCall_s *next;
} ToxAVCall;
struct ToxAV {
Messenger *m;
MSISession *msi;
/* Two-way storage: first is array of calls and second is list of calls with head and tail */
ToxAVCall **calls;
uint32_t calls_tail;
uint32_t calls_head;
pthread_mutex_t mutex[1];
PAIR(toxav_call_cb *, void *) ccb; /* Call callback */
PAIR(toxav_call_state_cb *, void *) scb; /* Call state callback */
PAIR(toxav_audio_receive_frame_cb *, void *) acb; /* Audio frame receive callback */
PAIR(toxav_video_receive_frame_cb *, void *) vcb; /* Video frame receive callback */
PAIR(toxav_bit_rate_status_cb *, void *) bcb; /* Bit rate control callback */
/** Decode time measures */
int32_t dmssc; /** Measure count */
int32_t dmsst; /** Last cycle total */
int32_t dmssa; /** Average decoding time in ms */
uint32_t interval; /** Calculated interval */
};
void callback_bwc (BWController *bwc, uint32_t friend_number, float loss, void *user_data);
int callback_invite(void *toxav_inst, MSICall *call);
int callback_start(void *toxav_inst, MSICall *call);
int callback_end(void *toxav_inst, MSICall *call);
int callback_error(void *toxav_inst, MSICall *call);
int callback_capabilites(void *toxav_inst, MSICall *call);
bool audio_bit_rate_invalid(uint32_t bit_rate);
bool video_bit_rate_invalid(uint32_t bit_rate);
bool invoke_call_state_callback(ToxAV *av, uint32_t friend_number, uint32_t state);
ToxAVCall *call_new(ToxAV *av, uint32_t friend_number, TOXAV_ERR_CALL *error);
ToxAVCall *call_get(ToxAV *av, uint32_t friend_number);
ToxAVCall *call_remove(ToxAVCall *call);
bool call_prepare_transmission(ToxAVCall *call);
void call_kill_transmission(ToxAVCall *call);
uint32_t toxav_version_major(void)
{
return TOXAV_VERSION_MAJOR;
}
uint32_t toxav_version_minor(void)
{
return TOXAV_VERSION_MINOR;
}
uint32_t toxav_version_patch(void)
{
return TOXAV_VERSION_PATCH;
}
bool toxav_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch)
{
return (TOXAV_VERSION_MAJOR == major && /* Force the major version */
(TOXAV_VERSION_MINOR > minor || /* Current minor version must be newer than requested -- or -- */
(TOXAV_VERSION_MINOR == minor && TOXAV_VERSION_PATCH >= patch) /* the patch must be the same or newer */
)
);
}
ToxAV *toxav_new(Tox *tox, TOXAV_ERR_NEW *error)
{
TOXAV_ERR_NEW rc = TOXAV_ERR_NEW_OK;
ToxAV *av = NULL;
if (tox == NULL) {
rc = TOXAV_ERR_NEW_NULL;
goto END;
}
if (((Messenger *)tox)->msi_packet) {
rc = TOXAV_ERR_NEW_MULTIPLE;
goto END;
}
av = calloc (sizeof(ToxAV), 1);
if (av == NULL) {
LOGGER_WARNING("Allocation failed!");
rc = TOXAV_ERR_NEW_MALLOC;
goto END;
}
if (create_recursive_mutex(av->mutex) != 0) {
LOGGER_WARNING("Mutex creation failed!");
rc = TOXAV_ERR_NEW_MALLOC;
goto END;
}
av->m = (Messenger *)tox;
av->msi = msi_new(av->m);
if (av->msi == NULL) {
pthread_mutex_destroy(av->mutex);
rc = TOXAV_ERR_NEW_MALLOC;
goto END;
}
av->interval = 200;
av->msi->av = av;
msi_register_callback(av->msi, callback_invite, msi_OnInvite);
msi_register_callback(av->msi, callback_start, msi_OnStart);
msi_register_callback(av->msi, callback_end, msi_OnEnd);
msi_register_callback(av->msi, callback_error, msi_OnError);
msi_register_callback(av->msi, callback_error, msi_OnPeerTimeout);
msi_register_callback(av->msi, callback_capabilites, msi_OnCapabilities);
END:
if (error)
*error = rc;
if (rc != TOXAV_ERR_NEW_OK) {
free(av);
av = NULL;
}
return av;
}
void toxav_kill(ToxAV *av)
{
if (av == NULL)
return;
pthread_mutex_lock(av->mutex);
/* To avoid possible deadlocks */
while (av->msi && msi_kill(av->msi) != 0) {
pthread_mutex_unlock(av->mutex);
pthread_mutex_lock(av->mutex);
}
/* Msi kill will hang up all calls so just clean these calls */
if (av->calls) {
ToxAVCall *it = call_get(av, av->calls_head);
while (it) {
call_kill_transmission(it);
it = call_remove(it); /* This will eventually free av->calls */
}
}
pthread_mutex_unlock(av->mutex);
pthread_mutex_destroy(av->mutex);
free(av);
}
Tox *toxav_get_tox(const ToxAV *av)
{
return (Tox *) av->m;
}
uint32_t toxav_iteration_interval(const ToxAV *av)
{
/* If no call is active interval is 200 */
return av->calls ? av->interval : 200;
}
void toxav_iterate(ToxAV *av)
{
pthread_mutex_lock(av->mutex);
if (av->calls == NULL) {
pthread_mutex_unlock(av->mutex);
return;
}
uint64_t start = current_time_monotonic();
int32_t rc = 500;
ToxAVCall *i = av->calls[av->calls_head];
for (; i; i = i->next) {
if (i->active) {
pthread_mutex_lock(i->mutex);
pthread_mutex_unlock(av->mutex);
ac_iterate(i->audio.second);
vc_iterate(i->video.second);
if (i->msi_call->self_capabilities & msi_CapRAudio &&
i->msi_call->peer_capabilities & msi_CapSAudio)
rc = MIN(i->audio.second->lp_frame_duration, rc);
if (i->msi_call->self_capabilities & msi_CapRVideo &&
i->msi_call->peer_capabilities & msi_CapSVideo)
rc = MIN(i->video.second->lcfd, (uint32_t) rc);
uint32_t fid = i->friend_number;
pthread_mutex_unlock(i->mutex);
pthread_mutex_lock(av->mutex);
/* In case this call is popped from container stop iteration */
if (call_get(av, fid) != i)
break;
}
}
pthread_mutex_unlock(av->mutex);
av->interval = rc < av->dmssa ? 0 : (rc - av->dmssa);
av->dmsst += current_time_monotonic() - start;
if (++av->dmssc == 3) {
av->dmssa = av->dmsst / 3 + 5 /* NOTE Magic Offset for precission */;
av->dmssc = 0;
av->dmsst = 0;
}
}
bool toxav_call(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate,
TOXAV_ERR_CALL *error)
{
TOXAV_ERR_CALL rc = TOXAV_ERR_CALL_OK;
pthread_mutex_lock(av->mutex);
if ((audio_bit_rate && audio_bit_rate_invalid(audio_bit_rate))
|| (video_bit_rate && video_bit_rate_invalid(video_bit_rate))) {
rc = TOXAV_ERR_CALL_INVALID_BIT_RATE;
goto END;
}
ToxAVCall *call = call_new(av, friend_number, &rc);
if (call == NULL)
goto END;
call->audio_bit_rate = audio_bit_rate;
call->video_bit_rate = video_bit_rate;
call->previous_self_capabilities = msi_CapRAudio | msi_CapRVideo;
call->previous_self_capabilities |= audio_bit_rate > 0 ? msi_CapSAudio : 0;
call->previous_self_capabilities |= video_bit_rate > 0 ? msi_CapSVideo : 0;
if (msi_invite(av->msi, &call->msi_call, friend_number, call->previous_self_capabilities) != 0) {
call_remove(call);
rc = TOXAV_ERR_CALL_SYNC;
goto END;
}
call->msi_call->av_call = call;
END:
pthread_mutex_unlock(av->mutex);
if (error)
*error = rc;
return rc == TOXAV_ERR_CALL_OK;
}
void toxav_callback_call(ToxAV *av, toxav_call_cb *function, void *user_data)
{
pthread_mutex_lock(av->mutex);
av->ccb.first = function;
av->ccb.second = user_data;
pthread_mutex_unlock(av->mutex);
}
bool toxav_answer(ToxAV *av, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate,
TOXAV_ERR_ANSWER *error)
{
pthread_mutex_lock(av->mutex);
TOXAV_ERR_ANSWER rc = TOXAV_ERR_ANSWER_OK;
if (m_friend_exists(av->m, friend_number) == 0) {
rc = TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND;
goto END;
}
if ((audio_bit_rate && audio_bit_rate_invalid(audio_bit_rate))
|| (video_bit_rate && video_bit_rate_invalid(video_bit_rate))
) {
rc = TOXAV_ERR_ANSWER_INVALID_BIT_RATE;
goto END;
}
ToxAVCall *call = call_get(av, friend_number);
if (call == NULL) {
rc = TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING;
goto END;
}
if (!call_prepare_transmission(call)) {
rc = TOXAV_ERR_ANSWER_CODEC_INITIALIZATION;
goto END;
}
call->audio_bit_rate = audio_bit_rate;
call->video_bit_rate = video_bit_rate;
call->previous_self_capabilities = msi_CapRAudio | msi_CapRVideo;
call->previous_self_capabilities |= audio_bit_rate > 0 ? msi_CapSAudio : 0;
call->previous_self_capabilities |= video_bit_rate > 0 ? msi_CapSVideo : 0;
if (msi_answer(call->msi_call, call->previous_self_capabilities) != 0)
rc = TOXAV_ERR_ANSWER_SYNC;
END:
pthread_mutex_unlock(av->mutex);
if (error)
*error = rc;
return rc == TOXAV_ERR_ANSWER_OK;
}
void toxav_callback_call_state(ToxAV *av, toxav_call_state_cb *function, void *user_data)
{
pthread_mutex_lock(av->mutex);
av->scb.first = function;
av->scb.second = user_data;
pthread_mutex_unlock(av->mutex);
}
bool toxav_call_control(ToxAV *av, uint32_t friend_number, TOXAV_CALL_CONTROL control, TOXAV_ERR_CALL_CONTROL *error)
{
pthread_mutex_lock(av->mutex);
TOXAV_ERR_CALL_CONTROL rc = TOXAV_ERR_CALL_CONTROL_OK;
if (m_friend_exists(av->m, friend_number) == 0) {
rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND;
goto END;
}
ToxAVCall *call = call_get(av, friend_number);
if (call == NULL || (!call->active && control != TOXAV_CALL_CONTROL_CANCEL)) {
rc = TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL;
goto END;
}
switch (control) {
case TOXAV_CALL_CONTROL_RESUME: {
/* Only act if paused and had media transfer active before */
if (call->msi_call->self_capabilities == 0 &&
call->previous_self_capabilities) {
if (msi_change_capabilities(call->msi_call,
call->previous_self_capabilities) == -1) {
rc = TOXAV_ERR_CALL_CONTROL_SYNC;
goto END;
}
rtp_allow_receiving(call->audio.first);
rtp_allow_receiving(call->video.first);
} else {
rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION;
goto END;
}
}
break;
case TOXAV_CALL_CONTROL_PAUSE: {
/* Only act if not already paused */
if (call->msi_call->self_capabilities) {
call->previous_self_capabilities = call->msi_call->self_capabilities;
if (msi_change_capabilities(call->msi_call, 0) == -1) {
rc = TOXAV_ERR_CALL_CONTROL_SYNC;
goto END;
}
rtp_stop_receiving(call->audio.first);
rtp_stop_receiving(call->video.first);
} else {
rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION;
goto END;
}
}
break;
case TOXAV_CALL_CONTROL_CANCEL: {
/* Hang up */
pthread_mutex_lock(call->mutex);
if (msi_hangup(call->msi_call) != 0) {
rc = TOXAV_ERR_CALL_CONTROL_SYNC;
pthread_mutex_unlock(call->mutex);
goto END;
}
call->msi_call = NULL;
pthread_mutex_unlock(call->mutex);
/* No mather the case, terminate the call */
call_kill_transmission(call);
call_remove(call);
}
break;
case TOXAV_CALL_CONTROL_MUTE_AUDIO: {
if (call->msi_call->self_capabilities & msi_CapRAudio) {
if (msi_change_capabilities(call->msi_call, call->
msi_call->self_capabilities ^ msi_CapRAudio) == -1) {
rc = TOXAV_ERR_CALL_CONTROL_SYNC;
goto END;
}
rtp_stop_receiving(call->audio.first);
} else {
rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION;
goto END;
}
}
break;
case TOXAV_CALL_CONTROL_UNMUTE_AUDIO: {
if (call->msi_call->self_capabilities ^ msi_CapRAudio) {
if (msi_change_capabilities(call->msi_call, call->
msi_call->self_capabilities | msi_CapRAudio) == -1) {
rc = TOXAV_ERR_CALL_CONTROL_SYNC;
goto END;
}
rtp_allow_receiving(call->audio.first);
} else {
rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION;
goto END;
}
}
break;
case TOXAV_CALL_CONTROL_HIDE_VIDEO: {
if (call->msi_call->self_capabilities & msi_CapRVideo) {
if (msi_change_capabilities(call->msi_call, call->
msi_call->self_capabilities ^ msi_CapRVideo) == -1) {
rc = TOXAV_ERR_CALL_CONTROL_SYNC;
goto END;
}
rtp_stop_receiving(call->video.first);
} else {
rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION;
goto END;
}
}
break;
case TOXAV_CALL_CONTROL_SHOW_VIDEO: {
if (call->msi_call->self_capabilities ^ msi_CapRVideo) {
if (msi_change_capabilities(call->msi_call, call->
msi_call->self_capabilities | msi_CapRVideo) == -1) {
rc = TOXAV_ERR_CALL_CONTROL_SYNC;
goto END;
}
rtp_allow_receiving(call->video.first);
} else {
rc = TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION;
goto END;
}
}
break;
}
END:
pthread_mutex_unlock(av->mutex);
if (error)
*error = rc;
return rc == TOXAV_ERR_CALL_CONTROL_OK;
}
bool toxav_bit_rate_set(ToxAV *av, uint32_t friend_number, int32_t audio_bit_rate,
int32_t video_bit_rate, TOXAV_ERR_BIT_RATE_SET *error)
{
TOXAV_ERR_BIT_RATE_SET rc = TOXAV_ERR_BIT_RATE_SET_OK;
ToxAVCall *call;
if (m_friend_exists(av->m, friend_number) == 0) {
rc = TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND;
goto END;
}
if (audio_bit_rate > 0 && audio_bit_rate_invalid(audio_bit_rate)) {
rc = TOXAV_ERR_BIT_RATE_SET_INVALID_AUDIO_BIT_RATE;
goto END;
}
if (video_bit_rate > 0 && video_bit_rate_invalid(video_bit_rate)) {
rc = TOXAV_ERR_BIT_RATE_SET_INVALID_VIDEO_BIT_RATE;
goto END;
}
pthread_mutex_lock(av->mutex);
call = call_get(av, friend_number);
if (call == NULL || !call->active || call->msi_call->state != msi_CallActive) {
pthread_mutex_unlock(av->mutex);
rc = TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL;
goto END;
}
if (audio_bit_rate >= 0) {
LOGGER_DEBUG("Setting new audio bitrate to: %d", audio_bit_rate);
if (call->audio_bit_rate == audio_bit_rate) {
LOGGER_DEBUG("Audio bitrate already set to: %d", audio_bit_rate);
} else if (audio_bit_rate == 0) {
LOGGER_DEBUG("Turned off audio sending");
if (msi_change_capabilities(call->msi_call, call->msi_call->
self_capabilities ^ msi_CapSAudio) != 0) {
pthread_mutex_unlock(av->mutex);
rc = TOXAV_ERR_BIT_RATE_SET_SYNC;
goto END;
}
/* Audio sending is turned off; notify peer */
call->audio_bit_rate = 0;
} else {
pthread_mutex_lock(call->mutex);
if (call->audio_bit_rate == 0) {
LOGGER_DEBUG("Turned on audio sending");
/* The audio has been turned off before this */
if (msi_change_capabilities(call->msi_call, call->
msi_call->self_capabilities | msi_CapSAudio) != 0) {
pthread_mutex_unlock(call->mutex);
pthread_mutex_unlock(av->mutex);
rc = TOXAV_ERR_BIT_RATE_SET_SYNC;
goto END;
}
} else
LOGGER_DEBUG("Set new audio bit rate %d", audio_bit_rate);
call->audio_bit_rate = audio_bit_rate;
pthread_mutex_unlock(call->mutex);
}
}
if (video_bit_rate >= 0) {
LOGGER_DEBUG("Setting new video bitrate to: %d", video_bit_rate);
if (call->video_bit_rate == video_bit_rate) {
LOGGER_DEBUG("Video bitrate already set to: %d", video_bit_rate);
} else if (video_bit_rate == 0) {
LOGGER_DEBUG("Turned off video sending");
/* Video sending is turned off; notify peer */
if (msi_change_capabilities(call->msi_call, call->msi_call->
self_capabilities ^ msi_CapSVideo) != 0) {
pthread_mutex_unlock(av->mutex);
rc = TOXAV_ERR_BIT_RATE_SET_SYNC;
goto END;
}
call->video_bit_rate = 0;
} else {
pthread_mutex_lock(call->mutex);
if (call->video_bit_rate == 0) {
LOGGER_DEBUG("Turned on video sending");
/* The video has been turned off before this */
if (msi_change_capabilities(call->msi_call, call->
msi_call->self_capabilities | msi_CapSVideo) != 0) {
pthread_mutex_unlock(call->mutex);
pthread_mutex_unlock(av->mutex);
rc = TOXAV_ERR_BIT_RATE_SET_SYNC;
goto END;
}
} else
LOGGER_DEBUG("Set new video bit rate %d", video_bit_rate);
call->video_bit_rate = video_bit_rate;
pthread_mutex_unlock(call->mutex);
}
}
pthread_mutex_unlock(av->mutex);
END:
if (error)
*error = rc;
return rc == TOXAV_ERR_BIT_RATE_SET_OK;
}
void toxav_callback_bit_rate_status(ToxAV *av, toxav_bit_rate_status_cb *function, void *user_data)
{
pthread_mutex_lock(av->mutex);
av->bcb.first = function;
av->bcb.second = user_data;
pthread_mutex_unlock(av->mutex);
}
bool toxav_audio_send_frame(ToxAV *av, uint32_t friend_number, const int16_t *pcm, size_t sample_count,
uint8_t channels, uint32_t sampling_rate, TOXAV_ERR_SEND_FRAME *error)
{
TOXAV_ERR_SEND_FRAME rc = TOXAV_ERR_SEND_FRAME_OK;
ToxAVCall *call;
if (m_friend_exists(av->m, friend_number) == 0) {
rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND;
goto END;
}
if (pthread_mutex_trylock(av->mutex) != 0) {
rc = TOXAV_ERR_SEND_FRAME_SYNC;
goto END;
}
call = call_get(av, friend_number);
if (call == NULL || !call->active || call->msi_call->state != msi_CallActive) {
pthread_mutex_unlock(av->mutex);
rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL;
goto END;
}
if (call->audio_bit_rate == 0 ||
!(call->msi_call->self_capabilities & msi_CapSAudio) ||
!(call->msi_call->peer_capabilities & msi_CapRAudio)) {
pthread_mutex_unlock(av->mutex);
rc = TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED;
goto END;
}
pthread_mutex_lock(call->mutex_audio);
pthread_mutex_unlock(av->mutex);
if (pcm == NULL) {
pthread_mutex_unlock(call->mutex_audio);
rc = TOXAV_ERR_SEND_FRAME_NULL;
goto END;
}
if (channels > 2) {
pthread_mutex_unlock(call->mutex_audio);
rc = TOXAV_ERR_SEND_FRAME_INVALID;
goto END;
}
{ /* Encode and send */
if (ac_reconfigure_encoder(call->audio.second, call->audio_bit_rate * 1000, sampling_rate, channels) != 0) {
pthread_mutex_unlock(call->mutex_audio);
rc = TOXAV_ERR_SEND_FRAME_INVALID;
goto END;
}
uint8_t dest[sample_count + sizeof(sampling_rate)]; /* This is more than enough always */
sampling_rate = htonl(sampling_rate);
memcpy(dest, &sampling_rate, sizeof(sampling_rate));
int vrc = opus_encode(call->audio.second->encoder, pcm, sample_count,
dest + sizeof(sampling_rate), sizeof(dest) - sizeof(sampling_rate));
if (vrc < 0) {
LOGGER_WARNING("Failed to encode frame %s", opus_strerror(vrc));
pthread_mutex_unlock(call->mutex_audio);
rc = TOXAV_ERR_SEND_FRAME_INVALID;
goto END;
}
if (rtp_send_data(call->audio.first, dest, vrc + sizeof(sampling_rate)) != 0) {
LOGGER_WARNING("Failed to send audio packet");
rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED;
}
}
pthread_mutex_unlock(call->mutex_audio);
END:
if (error)
*error = rc;
return rc == TOXAV_ERR_SEND_FRAME_OK;
}
bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y,
const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error)
{
TOXAV_ERR_SEND_FRAME rc = TOXAV_ERR_SEND_FRAME_OK;
ToxAVCall *call;
if (m_friend_exists(av->m, friend_number) == 0) {
rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND;
goto END;
}
if (pthread_mutex_trylock(av->mutex) != 0) {
rc = TOXAV_ERR_SEND_FRAME_SYNC;
goto END;
}
call = call_get(av, friend_number);
if (call == NULL || !call->active || call->msi_call->state != msi_CallActive) {
pthread_mutex_unlock(av->mutex);
rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL;
goto END;
}
if (call->video_bit_rate == 0 ||
!(call->msi_call->self_capabilities & msi_CapSVideo) ||
!(call->msi_call->peer_capabilities & msi_CapRVideo)) {
pthread_mutex_unlock(av->mutex);
rc = TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED;
goto END;
}
pthread_mutex_lock(call->mutex_video);
pthread_mutex_unlock(av->mutex);
if (y == NULL || u == NULL || v == NULL) {
pthread_mutex_unlock(call->mutex_video);
rc = TOXAV_ERR_SEND_FRAME_NULL;
goto END;
}
if (vc_reconfigure_encoder(call->video.second, call->video_bit_rate * 1000, width, height) != 0) {
pthread_mutex_unlock(call->mutex_video);
rc = TOXAV_ERR_SEND_FRAME_INVALID;
goto END;
}
{ /* Encode */
vpx_image_t img;
img.w = img.h = img.d_w = img.d_h = 0;
vpx_img_alloc(&img, VPX_IMG_FMT_I420, width, height, 0);
/* I420 "It comprises an NxM Y plane followed by (N/2)x(M/2) V and U planes."
* http://fourcc.org/yuv.php#IYUV
*/
memcpy(img.planes[VPX_PLANE_Y], y, width * height);
memcpy(img.planes[VPX_PLANE_U], u, (width / 2) * (height / 2));
memcpy(img.planes[VPX_PLANE_V], v, (width / 2) * (height / 2));
int vrc = vpx_codec_encode(call->video.second->encoder, &img,
call->video.second->frame_counter, 1, 0, MAX_ENCODE_TIME_US);
vpx_img_free(&img);
if (vrc != VPX_CODEC_OK) {
pthread_mutex_unlock(call->mutex_video);
LOGGER_ERROR("Could not encode video frame: %s\n", vpx_codec_err_to_string(vrc));
rc = TOXAV_ERR_SEND_FRAME_INVALID;
goto END;
}
}
++call->video.second->frame_counter;
{ /* Send frames */
vpx_codec_iter_t iter = NULL;
const vpx_codec_cx_pkt_t *pkt;
while ((pkt = vpx_codec_get_cx_data(call->video.second->encoder, &iter))) {
if (pkt->kind == VPX_CODEC_CX_FRAME_PKT &&
rtp_send_data(call->video.first, pkt->data.frame.buf, pkt->data.frame.sz) < 0) {
pthread_mutex_unlock(call->mutex_video);
LOGGER_WARNING("Could not send video frame: %s\n", strerror(errno));
rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED;
goto END;
}
}
}
pthread_mutex_unlock(call->mutex_video);
END:
if (error)
*error = rc;
return rc == TOXAV_ERR_SEND_FRAME_OK;
}
void toxav_callback_audio_receive_frame(ToxAV *av, toxav_audio_receive_frame_cb *function, void *user_data)
{
pthread_mutex_lock(av->mutex);
av->acb.first = function;
av->acb.second = user_data;
pthread_mutex_unlock(av->mutex);
}
void toxav_callback_video_receive_frame(ToxAV *av, toxav_video_receive_frame_cb *function, void *user_data)
{
pthread_mutex_lock(av->mutex);
av->vcb.first = function;
av->vcb.second = user_data;
pthread_mutex_unlock(av->mutex);
}
/*******************************************************************************
*
* :: Internal
*
******************************************************************************/
void callback_bwc(BWController *bwc, uint32_t friend_number, float loss, void *user_data)
{
/* Callback which is called when the internal measure mechanism reported packet loss.
* We report suggested lowered bitrate to an app. If app is sending both audio and video,
* we will report lowered bitrate for video only because in that case video probably
* takes more than 90% bandwidth. Otherwise, we report lowered bitrate on audio.
* The application may choose to disable video totally if the stream is too bad.
*/
ToxAVCall *call = user_data;
assert(call);
LOGGER_DEBUG("Reported loss of %f%%", loss * 100);
if (loss < .01f)
return;
pthread_mutex_lock(call->av->mutex);
if (!call->av->bcb.first) {
pthread_mutex_unlock(call->av->mutex);
LOGGER_WARNING("No callback to report loss on");
return;
}
if (call->video_bit_rate)
(*call->av->bcb.first) (call->av, friend_number, call->audio_bit_rate,
call->video_bit_rate - (call->video_bit_rate * loss),
call->av->bcb.second);
else if (call->audio_bit_rate)
(*call->av->bcb.first) (call->av, friend_number,
call->audio_bit_rate - (call->audio_bit_rate * loss),
0, call->av->bcb.second);
pthread_mutex_unlock(call->av->mutex);
}
int callback_invite(void *toxav_inst, MSICall *call)
{
ToxAV *toxav = toxav_inst;
pthread_mutex_lock(toxav->mutex);
ToxAVCall *av_call = call_new(toxav, call->friend_number, NULL);
if (av_call == NULL) {
LOGGER_WARNING("Failed to initialize call...");
pthread_mutex_unlock(toxav->mutex);
return -1;
}
call->av_call = av_call;
av_call->msi_call = call;
if (toxav->ccb.first)
toxav->ccb.first(toxav, call->friend_number, call->peer_capabilities & msi_CapSAudio,
call->peer_capabilities & msi_CapSVideo, toxav->ccb.second);
else {
/* No handler to capture the call request, send failure */
pthread_mutex_unlock(toxav->mutex);
return -1;
}
pthread_mutex_unlock(toxav->mutex);
return 0;
}
int callback_start(void *toxav_inst, MSICall *call)
{
ToxAV *toxav = toxav_inst;
pthread_mutex_lock(toxav->mutex);
ToxAVCall *av_call = call_get(toxav, call->friend_number);
if (av_call == NULL) {
/* Should this ever happen? */
pthread_mutex_unlock(toxav->mutex);
return -1;
}
if (!call_prepare_transmission(av_call)) {
callback_error(toxav_inst, call);
pthread_mutex_unlock(toxav->mutex);
return -1;
}
if (!invoke_call_state_callback(toxav, call->friend_number, call->peer_capabilities)) {
callback_error(toxav_inst, call);
pthread_mutex_unlock(toxav->mutex);
return -1;
}
pthread_mutex_unlock(toxav->mutex);
return 0;
}
int callback_end(void *toxav_inst, MSICall *call)
{
ToxAV *toxav = toxav_inst;
pthread_mutex_lock(toxav->mutex);
invoke_call_state_callback(toxav, call->friend_number, TOXAV_FRIEND_CALL_STATE_FINISHED);
if (call->av_call) {
call_kill_transmission(call->av_call);
call_remove(call->av_call);
}
pthread_mutex_unlock(toxav->mutex);
return 0;
}
int callback_error(void *toxav_inst, MSICall *call)
{
ToxAV *toxav = toxav_inst;
pthread_mutex_lock(toxav->mutex);
invoke_call_state_callback(toxav, call->friend_number, TOXAV_FRIEND_CALL_STATE_ERROR);
if (call->av_call) {
call_kill_transmission(call->av_call);
call_remove(call->av_call);
}
pthread_mutex_unlock(toxav->mutex);
return 0;
}
int callback_capabilites(void *toxav_inst, MSICall *call)
{
ToxAV *toxav = toxav_inst;
pthread_mutex_lock(toxav->mutex);
if (call->peer_capabilities & msi_CapSAudio)
rtp_allow_receiving(((ToxAVCall *)call->av_call)->audio.first);
else
rtp_stop_receiving(((ToxAVCall *)call->av_call)->audio.first);
if (call->peer_capabilities & msi_CapSVideo)
rtp_allow_receiving(((ToxAVCall *)call->av_call)->video.first);
else
rtp_stop_receiving(((ToxAVCall *)call->av_call)->video.first);
invoke_call_state_callback(toxav, call->friend_number, call->peer_capabilities);
pthread_mutex_unlock(toxav->mutex);
return 0;
}
bool audio_bit_rate_invalid(uint32_t bit_rate)
{
/* Opus RFC 6716 section-2.1.1 dictates the following:
* Opus supports all bit rates from 6 kbit/s to 510 kbit/s.
*/
return bit_rate < 6 || bit_rate > 510;
}
bool video_bit_rate_invalid(uint32_t bit_rate)
{
(void) bit_rate;
/* TODO: If anyone knows the answer to this one please fill it up */
return false;
}
bool invoke_call_state_callback(ToxAV *av, uint32_t friend_number, uint32_t state)
{
if (av->scb.first)
av->scb.first(av, friend_number, state, av->scb.second);
else
return false;
return true;
}
ToxAVCall *call_new(ToxAV *av, uint32_t friend_number, TOXAV_ERR_CALL *error)
{
/* Assumes mutex locked */
TOXAV_ERR_CALL rc = TOXAV_ERR_CALL_OK;
ToxAVCall *call = NULL;
if (m_friend_exists(av->m, friend_number) == 0) {
rc = TOXAV_ERR_CALL_FRIEND_NOT_FOUND;
goto END;
}
if (m_get_friend_connectionstatus(av->m, friend_number) < 1) {
rc = TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED;
goto END;
}
if (call_get(av, friend_number) != NULL) {
rc = TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL;
goto END;
}
call = calloc(sizeof(ToxAVCall), 1);
if (call == NULL) {
rc = TOXAV_ERR_CALL_MALLOC;
goto END;
}
call->av = av;
call->friend_number = friend_number;
if (av->calls == NULL) { /* Creating */
av->calls = calloc (sizeof(ToxAVCall *), friend_number + 1);
if (av->calls == NULL) {
free(call);
call = NULL;
rc = TOXAV_ERR_CALL_MALLOC;
goto END;
}
av->calls_tail = av->calls_head = friend_number;
} else if (av->calls_tail < friend_number) { /* Appending */
void *tmp = realloc(av->calls, sizeof(ToxAVCall *) * (friend_number + 1));
if (tmp == NULL) {
free(call);
call = NULL;
rc = TOXAV_ERR_CALL_MALLOC;
goto END;
}
av->calls = tmp;
/* Set fields in between to null */
uint32_t i = av->calls_tail + 1;
for (; i < friend_number; i ++)
av->calls[i] = NULL;
call->prev = av->calls[av->calls_tail];
av->calls[av->calls_tail]->next = call;
av->calls_tail = friend_number;
} else if (av->calls_head > friend_number) { /* Inserting at front */
call->next = av->calls[av->calls_head];
av->calls[av->calls_head]->prev = call;
av->calls_head = friend_number;
}
av->calls[friend_number] = call;
END:
if (error)
*error = rc;
return call;
}
ToxAVCall *call_get(ToxAV *av, uint32_t friend_number)
{
/* Assumes mutex locked */
if (av->calls == NULL || av->calls_tail < friend_number)
return NULL;
return av->calls[friend_number];
}
ToxAVCall *call_remove(ToxAVCall *call)
{
if (call == NULL)
return NULL;
uint32_t friend_number = call->friend_number;
ToxAV *av = call->av;
ToxAVCall *prev = call->prev;
ToxAVCall *next = call->next;
/* Set av call in msi to NULL in order to know if call if ToxAVCall is
* removed from the msi call.
*/
if (call->msi_call) {
call->msi_call->av_call = NULL;
}
free(call);
if (prev)
prev->next = next;
else if (next)
av->calls_head = next->friend_number;
else goto CLEAR;
if (next)
next->prev = prev;
else if (prev)
av->calls_tail = prev->friend_number;
else goto CLEAR;
av->calls[friend_number] = NULL;
return next;
CLEAR:
av->calls_head = av->calls_tail = 0;
free(av->calls);
av->calls = NULL;
return NULL;
}
bool call_prepare_transmission(ToxAVCall *call)
{
/* Assumes mutex locked */
if (call == NULL)
return false;
ToxAV *av = call->av;
if (!av->acb.first && !av->vcb.first)
/* It makes no sense to have CSession without callbacks */
return false;
if (call->active) {
LOGGER_WARNING("Call already active!\n");
return true;
}
if (create_recursive_mutex(call->mutex_audio) != 0)
return false;
if (create_recursive_mutex(call->mutex_video) != 0)
goto FAILURE_3;
if (create_recursive_mutex(call->mutex) != 0)
goto FAILURE_2;
/* Prepare bwc */
call->bwc = bwc_new(av->m, call->friend_number, callback_bwc, call);
{ /* Prepare audio */
call->audio.second = ac_new(av, call->friend_number, av->acb.first, av->acb.second);
if (!call->audio.second) {
LOGGER_ERROR("Failed to create audio codec session");
goto FAILURE;
}
call->audio.first = rtp_new(rtp_TypeAudio, av->m, call->friend_number, call->bwc,
call->audio.second, ac_queue_message);
if (!call->audio.first) {
LOGGER_ERROR("Failed to create audio rtp session");;
goto FAILURE;
}
}
{ /* Prepare video */
call->video.second = vc_new(av, call->friend_number, av->vcb.first, av->vcb.second);
if (!call->video.second) {
LOGGER_ERROR("Failed to create video codec session");
goto FAILURE;
}
call->video.first = rtp_new(rtp_TypeVideo, av->m, call->friend_number, call->bwc,
call->video.second, vc_queue_message);
if (!call->video.first) {
LOGGER_ERROR("Failed to create video rtp session");
goto FAILURE;
}
}
call->active = 1;
return true;
FAILURE:
bwc_kill(call->bwc);
rtp_kill(call->audio.first);
ac_kill(call->audio.second);
call->audio.first = NULL;
call->audio.second = NULL;
rtp_kill(call->video.first);
vc_kill(call->video.second);
call->video.first = NULL;
call->video.second = NULL;
pthread_mutex_destroy(call->mutex);
FAILURE_2:
pthread_mutex_destroy(call->mutex_video);
FAILURE_3:
pthread_mutex_destroy(call->mutex_audio);
return false;
}
void call_kill_transmission(ToxAVCall *call)
{
if (call == NULL || call->active == 0)
return;
call->active = 0;
pthread_mutex_lock(call->mutex_audio);
pthread_mutex_unlock(call->mutex_audio);
pthread_mutex_lock(call->mutex_video);
pthread_mutex_unlock(call->mutex_video);
pthread_mutex_lock(call->mutex);
pthread_mutex_unlock(call->mutex);
bwc_kill(call->bwc);
rtp_kill(call->audio.first);
ac_kill(call->audio.second);
call->audio.first = NULL;
call->audio.second = NULL;
rtp_kill(call->video.first);
vc_kill(call->video.second);
call->video.first = NULL;
call->video.second = NULL;
pthread_mutex_destroy(call->mutex_audio);
pthread_mutex_destroy(call->mutex_video);
pthread_mutex_destroy(call->mutex);
}
================================================
FILE: toxav/toxav.h
================================================
/* toxav.h
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef TOXAV_H
#define TOXAV_H
#include
#include
#include
#ifdef __cplusplus
extern "C" {
#endif
/** \page av Public audio/video API for Tox clients.
*
* This API can handle multiple calls. Each call has its state, in very rare
* occasions the library can change the state of the call without apps knowledge.
*
*/
/** \subsection events Events and callbacks
*
* As in Core API, events are handled by callbacks. One callback can be
* registered per event. All events have a callback function type named
* `toxav_{event}_cb` and a function to register it named `toxav_callback_{event}`.
* Passing a NULL callback will result in no callback being registered for that
* event. Only one callback per event can be registered, so if a client needs
* multiple event listeners, it needs to implement the dispatch functionality
* itself. Unlike Core API, lack of some event handlers will cause the the
* library to drop calls before they are started. Hanging up call from a
* callback causes undefined behaviour.
*
*/
/** \subsection threading Threading implications
*
* Unlike the Core API, this API is fully thread-safe. The library will ensure
* the proper synchronization of parallel calls.
*
* A common way to run ToxAV (multiple or single instance) is to have a thread,
* separate from tox instance thread, running a simple toxav_iterate loop,
* sleeping for toxav_iteration_interval * milliseconds on each iteration.
*
* An important thing to note is that events are triggered from both tox and
* toxav thread (see above). Audio and video receive frame events are triggered
* from toxav thread while all the other events are triggered from tox thread.
*
* Tox thread has priority with mutex mechanisms. Any api function can
* fail if mutexes are held by tox thread in which case they will set SYNC
* error code.
*/
/**
* External Tox type.
*/
#ifndef TOX_DEFINED
#define TOX_DEFINED
typedef struct Tox Tox;
#endif /* TOX_DEFINED */
/**
* ToxAV.
*/
/**
* The ToxAV instance type. Each ToxAV instance can be bound to only one Tox
* instance, and Tox instance can have only one ToxAV instance. One must make
* sure to close ToxAV instance prior closing Tox instance otherwise undefined
* behaviour occurs. Upon closing of ToxAV instance, all active calls will be
* forcibly terminated without notifying peers.
*
*/
#ifndef TOXAV_DEFINED
#define TOXAV_DEFINED
typedef struct ToxAV ToxAV;
#endif /* TOXAV_DEFINED */
/*******************************************************************************
*
* :: API version
*
******************************************************************************/
/**
* The major version number. Incremented when the API or ABI changes in an
* incompatible way.
*/
#define TOXAV_VERSION_MAJOR 0u
/**
* The minor version number. Incremented when functionality is added without
* breaking the API or ABI. Set to 0 when the major version number is
* incremented.
*/
#define TOXAV_VERSION_MINOR 0u
/**
* The patch or revision number. Incremented when bugfixes are applied without
* changing any functionality or API or ABI.
*/
#define TOXAV_VERSION_PATCH 0u
/**
* A macro to check at preprocessing time whether the client code is compatible
* with the installed version of ToxAV.
*/
#define TOXAV_VERSION_IS_API_COMPATIBLE(MAJOR, MINOR, PATCH) \
(TOXAV_VERSION_MAJOR == MAJOR && \
(TOXAV_VERSION_MINOR > MINOR || \
(TOXAV_VERSION_MINOR == MINOR && \
TOXAV_VERSION_PATCH >= PATCH)))
/**
* A macro to make compilation fail if the client code is not compatible with
* the installed version of ToxAV.
*/
#define TOXAV_VERSION_REQUIRE(MAJOR, MINOR, PATCH) \
typedef char toxav_required_version[TOXAV_IS_COMPATIBLE(MAJOR, MINOR, PATCH) ? 1 : -1]
/**
* A convenience macro to call toxav_version_is_compatible with the currently
* compiling API version.
*/
#define TOXAV_VERSION_IS_ABI_COMPATIBLE() \
toxav_version_is_compatible(TOXAV_VERSION_MAJOR, TOXAV_VERSION_MINOR, TOXAV_VERSION_PATCH)
/**
* Return the major version number of the library. Can be used to display the
* ToxAV library version or to check whether the client is compatible with the
* dynamically linked version of ToxAV.
*/
uint32_t toxav_version_major(void);
/**
* Return the minor version number of the library.
*/
uint32_t toxav_version_minor(void);
/**
* Return the patch number of the library.
*/
uint32_t toxav_version_patch(void);
/**
* Return whether the compiled library version is compatible with the passed
* version numbers.
*/
bool toxav_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch);
/*******************************************************************************
*
* :: Creation and destruction
*
******************************************************************************/
typedef enum TOXAV_ERR_NEW {
/**
* The function returned successfully.
*/
TOXAV_ERR_NEW_OK,
/**
* One of the arguments to the function was NULL when it was not expected.
*/
TOXAV_ERR_NEW_NULL,
/**
* Memory allocation failure while trying to allocate structures required for
* the A/V session.
*/
TOXAV_ERR_NEW_MALLOC,
/**
* Attempted to create a second session for the same Tox instance.
*/
TOXAV_ERR_NEW_MULTIPLE,
} TOXAV_ERR_NEW;
/**
* Start new A/V session. There can only be only one session per Tox instance.
*/
ToxAV *toxav_new(Tox *tox, TOXAV_ERR_NEW *error);
/**
* Releases all resources associated with the A/V session.
*
* If any calls were ongoing, these will be forcibly terminated without
* notifying peers. After calling this function, no other functions may be
* called and the av pointer becomes invalid.
*/
void toxav_kill(ToxAV *toxAV);
/**
* Returns the Tox instance the A/V object was created for.
*/
Tox *toxav_get_tox(const ToxAV *toxAV);
/*******************************************************************************
*
* :: A/V event loop
*
******************************************************************************/
/**
* Returns the interval in milliseconds when the next toxav_iterate call should
* be. If no call is active at the moment, this function returns 200.
*/
uint32_t toxav_iteration_interval(const ToxAV *toxAV);
/**
* Main loop for the session. This function needs to be called in intervals of
* toxav_iteration_interval() milliseconds. It is best called in the separate
* thread from tox_iterate.
*/
void toxav_iterate(ToxAV *toxAV);
/*******************************************************************************
*
* :: Call setup
*
******************************************************************************/
typedef enum TOXAV_ERR_CALL {
/**
* The function returned successfully.
*/
TOXAV_ERR_CALL_OK,
/**
* A resource allocation error occurred while trying to create the structures
* required for the call.
*/
TOXAV_ERR_CALL_MALLOC,
/**
* Synchronization error occurred.
*/
TOXAV_ERR_CALL_SYNC,
/**
* The friend number did not designate a valid friend.
*/
TOXAV_ERR_CALL_FRIEND_NOT_FOUND,
/**
* The friend was valid, but not currently connected.
*/
TOXAV_ERR_CALL_FRIEND_NOT_CONNECTED,
/**
* Attempted to call a friend while already in an audio or video call with
* them.
*/
TOXAV_ERR_CALL_FRIEND_ALREADY_IN_CALL,
/**
* Audio or video bit rate is invalid.
*/
TOXAV_ERR_CALL_INVALID_BIT_RATE,
} TOXAV_ERR_CALL;
/**
* Call a friend. This will start ringing the friend.
*
* It is the client's responsibility to stop ringing after a certain timeout,
* if such behaviour is desired. If the client does not stop ringing, the
* library will not stop until the friend is disconnected. Audio and video
* receiving are both enabled by default.
*
* @param friend_number The friend number of the friend that should be called.
* @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable
* audio sending.
* @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable
* video sending.
*/
bool toxav_call(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate,
TOXAV_ERR_CALL *error);
/**
* The function type for the call callback.
*
* @param friend_number The friend number from which the call is incoming.
* @param audio_enabled True if friend is sending audio.
* @param video_enabled True if friend is sending video.
*/
typedef void toxav_call_cb(ToxAV *toxAV, uint32_t friend_number, bool audio_enabled, bool video_enabled,
void *user_data);
/**
* Set the callback for the `call` event. Pass NULL to unset.
*
*/
void toxav_callback_call(ToxAV *toxAV, toxav_call_cb *callback, void *user_data);
typedef enum TOXAV_ERR_ANSWER {
/**
* The function returned successfully.
*/
TOXAV_ERR_ANSWER_OK,
/**
* Synchronization error occurred.
*/
TOXAV_ERR_ANSWER_SYNC,
/**
* Failed to initialize codecs for call session. Note that codec initiation
* will fail if there is no receive callback registered for either audio or
* video.
*/
TOXAV_ERR_ANSWER_CODEC_INITIALIZATION,
/**
* The friend number did not designate a valid friend.
*/
TOXAV_ERR_ANSWER_FRIEND_NOT_FOUND,
/**
* The friend was valid, but they are not currently trying to initiate a call.
* This is also returned if this client is already in a call with the friend.
*/
TOXAV_ERR_ANSWER_FRIEND_NOT_CALLING,
/**
* Audio or video bit rate is invalid.
*/
TOXAV_ERR_ANSWER_INVALID_BIT_RATE,
} TOXAV_ERR_ANSWER;
/**
* Accept an incoming call.
*
* If answering fails for any reason, the call will still be pending and it is
* possible to try and answer it later. Audio and video receiving are both
* enabled by default.
*
* @param friend_number The friend number of the friend that is calling.
* @param audio_bit_rate Audio bit rate in Kb/sec. Set this to 0 to disable
* audio sending.
* @param video_bit_rate Video bit rate in Kb/sec. Set this to 0 to disable
* video sending.
*/
bool toxav_answer(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate, uint32_t video_bit_rate,
TOXAV_ERR_ANSWER *error);
/*******************************************************************************
*
* :: Call state graph
*
******************************************************************************/
enum TOXAV_FRIEND_CALL_STATE {
/**
* Set by the AV core if an error occurred on the remote end or if friend
* timed out. This is the final state after which no more state
* transitions can occur for the call. This call state will never be triggered
* in combination with other call states.
*/
TOXAV_FRIEND_CALL_STATE_ERROR = 1,
/**
* The call has finished. This is the final state after which no more state
* transitions can occur for the call. This call state will never be
* triggered in combination with other call states.
*/
TOXAV_FRIEND_CALL_STATE_FINISHED = 2,
/**
* The flag that marks that friend is sending audio.
*/
TOXAV_FRIEND_CALL_STATE_SENDING_A = 4,
/**
* The flag that marks that friend is sending video.
*/
TOXAV_FRIEND_CALL_STATE_SENDING_V = 8,
/**
* The flag that marks that friend is receiving audio.
*/
TOXAV_FRIEND_CALL_STATE_ACCEPTING_A = 16,
/**
* The flag that marks that friend is receiving video.
*/
TOXAV_FRIEND_CALL_STATE_ACCEPTING_V = 32,
};
/**
* The function type for the call_state callback.
*
* @param friend_number The friend number for which the call state changed.
* @param state The bitmask of the new call state which is guaranteed to be
* different than the previous state. The state is set to 0 when the call is
* paused. The bitmask represents all the activities currently performed by the
* friend.
*/
typedef void toxav_call_state_cb(ToxAV *toxAV, uint32_t friend_number, uint32_t state, void *user_data);
/**
* Set the callback for the `call_state` event. Pass NULL to unset.
*
*/
void toxav_callback_call_state(ToxAV *toxAV, toxav_call_state_cb *callback, void *user_data);
/*******************************************************************************
*
* :: Call control
*
******************************************************************************/
typedef enum TOXAV_CALL_CONTROL {
/**
* Resume a previously paused call. Only valid if the pause was caused by this
* client, if not, this control is ignored. Not valid before the call is accepted.
*/
TOXAV_CALL_CONTROL_RESUME,
/**
* Put a call on hold. Not valid before the call is accepted.
*/
TOXAV_CALL_CONTROL_PAUSE,
/**
* Reject a call if it was not answered, yet. Cancel a call after it was
* answered.
*/
TOXAV_CALL_CONTROL_CANCEL,
/**
* Request that the friend stops sending audio. Regardless of the friend's
* compliance, this will cause the audio_receive_frame event to stop being
* triggered on receiving an audio frame from the friend.
*/
TOXAV_CALL_CONTROL_MUTE_AUDIO,
/**
* Calling this control will notify client to start sending audio again.
*/
TOXAV_CALL_CONTROL_UNMUTE_AUDIO,
/**
* Request that the friend stops sending video. Regardless of the friend's
* compliance, this will cause the video_receive_frame event to stop being
* triggered on receiving a video frame from the friend.
*/
TOXAV_CALL_CONTROL_HIDE_VIDEO,
/**
* Calling this control will notify client to start sending video again.
*/
TOXAV_CALL_CONTROL_SHOW_VIDEO,
} TOXAV_CALL_CONTROL;
typedef enum TOXAV_ERR_CALL_CONTROL {
/**
* The function returned successfully.
*/
TOXAV_ERR_CALL_CONTROL_OK,
/**
* Synchronization error occurred.
*/
TOXAV_ERR_CALL_CONTROL_SYNC,
/**
* The friend_number passed did not designate a valid friend.
*/
TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_FOUND,
/**
* This client is currently not in a call with the friend. Before the call is
* answered, only CANCEL is a valid control.
*/
TOXAV_ERR_CALL_CONTROL_FRIEND_NOT_IN_CALL,
/**
* Happens if user tried to pause an already paused call or if trying to
* resume a call that is not paused.
*/
TOXAV_ERR_CALL_CONTROL_INVALID_TRANSITION,
} TOXAV_ERR_CALL_CONTROL;
/**
* Sends a call control command to a friend.
*
* @param friend_number The friend number of the friend this client is in a call
* with.
* @param control The control command to send.
*
* @return true on success.
*/
bool toxav_call_control(ToxAV *toxAV, uint32_t friend_number, TOXAV_CALL_CONTROL control,
TOXAV_ERR_CALL_CONTROL *error);
/*******************************************************************************
*
* :: Controlling bit rates
*
******************************************************************************/
typedef enum TOXAV_ERR_BIT_RATE_SET {
/**
* The function returned successfully.
*/
TOXAV_ERR_BIT_RATE_SET_OK,
/**
* Synchronization error occurred.
*/
TOXAV_ERR_BIT_RATE_SET_SYNC,
/**
* The audio bit rate passed was not one of the supported values.
*/
TOXAV_ERR_BIT_RATE_SET_INVALID_AUDIO_BIT_RATE,
/**
* The video bit rate passed was not one of the supported values.
*/
TOXAV_ERR_BIT_RATE_SET_INVALID_VIDEO_BIT_RATE,
/**
* The friend_number passed did not designate a valid friend.
*/
TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_FOUND,
/**
* This client is currently not in a call with the friend.
*/
TOXAV_ERR_BIT_RATE_SET_FRIEND_NOT_IN_CALL,
} TOXAV_ERR_BIT_RATE_SET;
/**
* Set the bit rate to be used in subsequent audio/video frames.
*
* @param friend_number The friend number of the friend for which to set the
* bit rate.
* @param audio_bit_rate The new audio bit rate in Kb/sec. Set to 0 to disable
* audio sending. Set to -1 to leave unchanged.
* @param video_bit_rate The new video bit rate in Kb/sec. Set to 0 to disable
* video sending. Set to -1 to leave unchanged.
*
*/
bool toxav_bit_rate_set(ToxAV *toxAV, uint32_t friend_number, int32_t audio_bit_rate, int32_t video_bit_rate,
TOXAV_ERR_BIT_RATE_SET *error);
/**
* The function type for the bit_rate_status callback. The event is triggered
* when the network becomes too saturated for current bit rates at which
* point core suggests new bit rates.
*
* @param friend_number The friend number of the friend for which to set the
* bit rate.
* @param audio_bit_rate Suggested maximum audio bit rate in Kb/sec.
* @param video_bit_rate Suggested maximum video bit rate in Kb/sec.
*/
typedef void toxav_bit_rate_status_cb(ToxAV *toxAV, uint32_t friend_number, uint32_t audio_bit_rate,
uint32_t video_bit_rate, void *user_data);
/**
* Set the callback for the `bit_rate_status` event. Pass NULL to unset.
*
*/
void toxav_callback_bit_rate_status(ToxAV *toxAV, toxav_bit_rate_status_cb *callback, void *user_data);
/*******************************************************************************
*
* :: A/V sending
*
******************************************************************************/
typedef enum TOXAV_ERR_SEND_FRAME {
/**
* The function returned successfully.
*/
TOXAV_ERR_SEND_FRAME_OK,
/**
* In case of video, one of Y, U, or V was NULL. In case of audio, the samples
* data pointer was NULL.
*/
TOXAV_ERR_SEND_FRAME_NULL,
/**
* The friend_number passed did not designate a valid friend.
*/
TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND,
/**
* This client is currently not in a call with the friend.
*/
TOXAV_ERR_SEND_FRAME_FRIEND_NOT_IN_CALL,
/**
* Synchronization error occurred.
*/
TOXAV_ERR_SEND_FRAME_SYNC,
/**
* One of the frame parameters was invalid. E.g. the resolution may be too
* small or too large, or the audio sampling rate may be unsupported.
*/
TOXAV_ERR_SEND_FRAME_INVALID,
/**
* Either friend turned off audio or video receiving or we turned off sending
* for the said payload.
*/
TOXAV_ERR_SEND_FRAME_PAYLOAD_TYPE_DISABLED,
/**
* Failed to push frame through rtp interface.
*/
TOXAV_ERR_SEND_FRAME_RTP_FAILED,
} TOXAV_ERR_SEND_FRAME;
/**
* Send an audio frame to a friend.
*
* The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]...
* Meaning: sample 1 for channel 1, sample 1 for channel 2, ...
* For mono audio, this has no meaning, every sample is subsequent. For stereo,
* this means the expected format is LRLRLR... with samples for left and right
* alternating.
*
* @param friend_number The friend number of the friend to which to send an
* audio frame.
* @param pcm An array of audio samples. The size of this array must be
* sample_count * channels.
* @param sample_count Number of samples in this frame. Valid numbers here are
* ((sample rate) * (audio length) / 1000), where audio length can be
* 2.5, 5, 10, 20, 40 or 60 millseconds.
* @param channels Number of audio channels. Supported values are 1 and 2.
* @param sampling_rate Audio sampling rate used in this frame. Valid sampling
* rates are 8000, 12000, 16000, 24000, or 48000.
*/
bool toxav_audio_send_frame(ToxAV *toxAV, uint32_t friend_number, const int16_t *pcm, size_t sample_count,
uint8_t channels, uint32_t sampling_rate, TOXAV_ERR_SEND_FRAME *error);
/**
* Send a video frame to a friend.
*
* Y - plane should be of size: height * width
* U - plane should be of size: (height/2) * (width/2)
* V - plane should be of size: (height/2) * (width/2)
*
* @param friend_number The friend number of the friend to which to send a video
* frame.
* @param width Width of the frame in pixels.
* @param height Height of the frame in pixels.
* @param y Y (Luminance) plane data.
* @param u U (Chroma) plane data.
* @param v V (Chroma) plane data.
*/
bool toxav_video_send_frame(ToxAV *toxAV, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y,
const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error);
/*******************************************************************************
*
* :: A/V receiving
*
******************************************************************************/
/**
* The function type for the audio_receive_frame callback. The callback can be
* called multiple times per single iteration depending on the amount of queued
* frames in the buffer. The received format is the same as in send function.
*
* @param friend_number The friend number of the friend who sent an audio frame.
* @param pcm An array of audio samples (sample_count * channels elements).
* @param sample_count The number of audio samples per channel in the PCM array.
* @param channels Number of audio channels.
* @param sampling_rate Sampling rate used in this frame.
*
*/
typedef void toxav_audio_receive_frame_cb(ToxAV *toxAV, uint32_t friend_number, const int16_t *pcm, size_t sample_count,
uint8_t channels, uint32_t sampling_rate, void *user_data);
/**
* Set the callback for the `audio_receive_frame` event. Pass NULL to unset.
*
*/
void toxav_callback_audio_receive_frame(ToxAV *toxAV, toxav_audio_receive_frame_cb *callback, void *user_data);
/**
* The function type for the video_receive_frame callback.
*
* @param friend_number The friend number of the friend who sent a video frame.
* @param width Width of the frame in pixels.
* @param height Height of the frame in pixels.
* @param y
* @param u
* @param v Plane data.
* The size of plane data is derived from width and height where
* Y = MAX(width, abs(ystride)) * height,
* U = MAX(width/2, abs(ustride)) * (height/2) and
* V = MAX(width/2, abs(vstride)) * (height/2).
* @param ystride
* @param ustride
* @param vstride Strides data. Strides represent padding for each plane
* that may or may not be present. You must handle strides in
* your image processing code. Strides are negative if the
* image is bottom-up hence why you MUST abs() it when
* calculating plane buffer size.
*/
typedef void toxav_video_receive_frame_cb(ToxAV *toxAV, uint32_t friend_number, uint16_t width, uint16_t height,
const uint8_t *y, const uint8_t *u, const uint8_t *v, int32_t ystride, int32_t ustride, int32_t vstride,
void *user_data);
/**
* Set the callback for the `video_receive_frame` event. Pass NULL to unset.
*
*/
void toxav_callback_video_receive_frame(ToxAV *toxAV, toxav_video_receive_frame_cb *callback, void *user_data);
/**
* NOTE Compatibility with old toxav group calls TODO remove
*/
/* Create a new toxav group.
*
* return group number on success.
* return -1 on failure.
*
* Audio data callback format:
* audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata)
*
* Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)).
*/
int toxav_add_av_groupchat(Tox *tox, void (*audio_callback)(void *, int, int, const int16_t *, unsigned int, uint8_t,
unsigned int, void *), void *userdata);
/* Join a AV group (you need to have been invited first.)
*
* returns group number on success
* returns -1 on failure.
*
* Audio data callback format (same as the one for toxav_add_av_groupchat()):
* audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata)
*
* Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)).
*/
int toxav_join_av_groupchat(Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length,
void (*audio_callback)(void *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *), void *userdata);
/* Send audio to the group chat.
*
* return 0 on success.
* return -1 on failure.
*
* Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)).
*
* Valid number of samples are ((sample rate) * (audio length (Valid ones are: 2.5, 5, 10, 20, 40 or 60 ms)) / 1000)
* Valid number of channels are 1 or 2.
* Valid sample rates are 8000, 12000, 16000, 24000, or 48000.
*
* Recommended values are: samples = 960, channels = 1, sample_rate = 48000
*/
int toxav_group_send_audio(Tox *tox, int groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels,
unsigned int sample_rate);
#ifdef __cplusplus
}
#endif
#endif /* TOXAV_H */
================================================
FILE: toxav/toxav_old.c
================================================
/* toxav_old.h
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
/**
* This file contains the group chats code for the backwards compatibility.
*/
#include "toxav.h"
#include "group.h"
/* Create a new toxav group.
*
* return group number on success.
* return -1 on failure.
*
* Audio data callback format:
* audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata)
*
* Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)).
*/
int toxav_add_av_groupchat(struct Tox *tox, void (*audio_callback)(void *, int, int, const int16_t *, unsigned int,
uint8_t, unsigned int, void *), void *userdata)
{
Messenger *m = (Messenger *)tox;
return add_av_groupchat(m->group_chat_object, audio_callback, userdata);
}
/* Join a AV group (you need to have been invited first.)
*
* returns group number on success
* returns -1 on failure.
*
* Audio data callback format (same as the one for toxav_add_av_groupchat()):
* audio_callback(Tox *tox, int groupnumber, int peernumber, const int16_t *pcm, unsigned int samples, uint8_t channels, unsigned int sample_rate, void *userdata)
*
* Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)).
*/
int toxav_join_av_groupchat(struct Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length,
void (*audio_callback)(void *, int, int, const int16_t *, unsigned int, uint8_t, unsigned int, void *),
void *userdata)
{
Messenger *m = (Messenger *)tox;
return join_av_groupchat(m->group_chat_object, friendnumber, data, length, audio_callback, userdata);
}
/* Send audio to the group chat.
*
* return 0 on success.
* return -1 on failure.
*
* Note that total size of pcm in bytes is equal to (samples * channels * sizeof(int16_t)).
*
* Valid number of samples are ((sample rate) * (audio length (Valid ones are: 2.5, 5, 10, 20, 40 or 60 ms)) / 1000)
* Valid number of channels are 1 or 2.
* Valid sample rates are 8000, 12000, 16000, 24000, or 48000.
*
* Recommended values are: samples = 960, channels = 1, sample_rate = 48000
*/
int toxav_group_send_audio(struct Tox *tox, int groupnumber, const int16_t *pcm, unsigned int samples, uint8_t channels,
unsigned int sample_rate)
{
Messenger *m = (Messenger *)tox;
return group_send_audio(m->group_chat_object, groupnumber, pcm, samples, channels, sample_rate);
}
================================================
FILE: toxav/video.c
================================================
/** video.c
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include
#include
#include "video.h"
#include "msi.h"
#include "rtp.h"
#include "../toxcore/logger.h"
#include "../toxcore/network.h"
#define MAX_DECODE_TIME_US 0 /* Good quality encode. */
#define VIDEO_DECODE_BUFFER_SIZE 20
VCSession *vc_new(ToxAV *av, uint32_t friend_number, toxav_video_receive_frame_cb *cb, void *cb_data)
{
VCSession *vc = calloc(sizeof(VCSession), 1);
if (!vc) {
LOGGER_WARNING("Allocation failed! Application might misbehave!");
return NULL;
}
if (create_recursive_mutex(vc->queue_mutex) != 0) {
LOGGER_WARNING("Failed to create recursive mutex!");
free(vc);
return NULL;
}
if (!(vc->vbuf_raw = rb_new(VIDEO_DECODE_BUFFER_SIZE)))
goto BASE_CLEANUP;
int rc = vpx_codec_dec_init(vc->decoder, VIDEO_CODEC_DECODER_INTERFACE, NULL, 0);
if (rc != VPX_CODEC_OK) {
LOGGER_ERROR("Init video_decoder failed: %s", vpx_codec_err_to_string(rc));
goto BASE_CLEANUP;
}
/* Set encoder to some initial values
*/
vpx_codec_enc_cfg_t cfg;
rc = vpx_codec_enc_config_default(VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0);
if (rc != VPX_CODEC_OK) {
LOGGER_ERROR("Failed to get config: %s", vpx_codec_err_to_string(rc));
goto BASE_CLEANUP_1;
}
cfg.rc_target_bitrate = 500000;
cfg.g_w = 800;
cfg.g_h = 600;
cfg.g_pass = VPX_RC_ONE_PASS;
/* FIXME If we set error resilience the app will crash due to bug in vp8.
Perhaps vp9 has solved it?*/
// cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT | VPX_ERROR_RESILIENT_PARTITIONS;
cfg.g_lag_in_frames = 0;
cfg.kf_min_dist = 0;
cfg.kf_max_dist = 48;
cfg.kf_mode = VPX_KF_AUTO;
rc = vpx_codec_enc_init(vc->encoder, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0);
if (rc != VPX_CODEC_OK) {
LOGGER_ERROR("Failed to initialize encoder: %s", vpx_codec_err_to_string(rc));
goto BASE_CLEANUP_1;
}
rc = vpx_codec_control(vc->encoder, VP8E_SET_CPUUSED, 8);
if (rc != VPX_CODEC_OK) {
LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc));
vpx_codec_destroy(vc->encoder);
goto BASE_CLEANUP_1;
}
vc->linfts = current_time_monotonic();
vc->lcfd = 60;
vc->vcb.first = cb;
vc->vcb.second = cb_data;
vc->friend_number = friend_number;
vc->av = av;
return vc;
BASE_CLEANUP_1:
vpx_codec_destroy(vc->decoder);
BASE_CLEANUP:
pthread_mutex_destroy(vc->queue_mutex);
rb_kill(vc->vbuf_raw);
free(vc);
return NULL;
}
void vc_kill(VCSession *vc)
{
if (!vc)
return;
vpx_codec_destroy(vc->encoder);
vpx_codec_destroy(vc->decoder);
void *p;
while (rb_read(vc->vbuf_raw, (void **)&p))
free(p);
rb_kill(vc->vbuf_raw);
pthread_mutex_destroy(vc->queue_mutex);
LOGGER_DEBUG("Terminated video handler: %p", vc);
free(vc);
}
void vc_iterate(VCSession *vc)
{
if (!vc)
return;
struct RTPMessage *p;
int rc;
pthread_mutex_lock(vc->queue_mutex);
if (rb_read(vc->vbuf_raw, (void **)&p)) {
pthread_mutex_unlock(vc->queue_mutex);
rc = vpx_codec_decode(vc->decoder, p->data, p->len, NULL, MAX_DECODE_TIME_US);
free(p);
if (rc != VPX_CODEC_OK)
LOGGER_ERROR("Error decoding video: %s", vpx_codec_err_to_string(rc));
else {
vpx_codec_iter_t iter = NULL;
vpx_image_t *dest = vpx_codec_get_frame(vc->decoder, &iter);
/* Play decoded images */
for (; dest; dest = vpx_codec_get_frame(vc->decoder, &iter)) {
if (vc->vcb.first)
vc->vcb.first(vc->av, vc->friend_number, dest->d_w, dest->d_h,
(const uint8_t *)dest->planes[0], (const uint8_t *)dest->planes[1], (const uint8_t *)dest->planes[2],
dest->stride[0], dest->stride[1], dest->stride[2], vc->vcb.second);
vpx_img_free(dest);
}
}
return;
}
pthread_mutex_unlock(vc->queue_mutex);
}
int vc_queue_message(void *vcp, struct RTPMessage *msg)
{
/* This function does the reconstruction of video packets.
* See more info about video splitting in docs
*/
if (!vcp || !msg)
return -1;
if (msg->header.pt == (rtp_TypeVideo + 2) % 128) {
LOGGER_WARNING("Got dummy!");
free(msg);
return 0;
}
if (msg->header.pt != rtp_TypeVideo % 128) {
LOGGER_WARNING("Invalid payload type!");
free(msg);
return -1;
}
VCSession *vc = vcp;
pthread_mutex_lock(vc->queue_mutex);
free(rb_write(vc->vbuf_raw, msg));
{
/* Calculate time took for peer to send us this frame */
uint32_t t_lcfd = current_time_monotonic() - vc->linfts;
vc->lcfd = t_lcfd > 100 ? vc->lcfd : t_lcfd;
vc->linfts = current_time_monotonic();
}
pthread_mutex_unlock(vc->queue_mutex);
return 0;
}
int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uint16_t height)
{
if (!vc)
return -1;
vpx_codec_enc_cfg_t cfg = *vc->encoder->config.enc;
int rc;
if (cfg.rc_target_bitrate == bit_rate && cfg.g_w == width && cfg.g_h == height)
return 0; /* Nothing changed */
if (cfg.g_w == width && cfg.g_h == height) {
/* Only bit rate changed */
cfg.rc_target_bitrate = bit_rate;
rc = vpx_codec_enc_config_set(vc->encoder, &cfg);
if (rc != VPX_CODEC_OK) {
LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc));
return -1;
}
} else {
/* Resolution is changed, must reinitialize encoder since libvpx v1.4 doesn't support
* reconfiguring encoder to use resolutions greater than initially set.
*/
LOGGER_DEBUG("Have to reinitialize vpx encoder on session %p", vc);
cfg.rc_target_bitrate = bit_rate;
cfg.g_w = width;
cfg.g_h = height;
vpx_codec_ctx_t new_c;
rc = vpx_codec_enc_init(&new_c, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0);
if (rc != VPX_CODEC_OK) {
LOGGER_ERROR("Failed to initialize encoder: %s", vpx_codec_err_to_string(rc));
return -1;
}
rc = vpx_codec_control(&new_c, VP8E_SET_CPUUSED, 8);
if (rc != VPX_CODEC_OK) {
LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc));
vpx_codec_destroy(&new_c);
return -1;
}
vpx_codec_destroy(vc->encoder);
memcpy(vc->encoder, &new_c, sizeof(new_c));
}
return 0;
}
================================================
FILE: toxav/video.h
================================================
/** video.h
*
* Copyright (C) 2013-2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef VIDEO_H
#define VIDEO_H
#include
#include
#include
#include
#include
#define VIDEO_CODEC_DECODER_INTERFACE (vpx_codec_vp8_dx())
#define VIDEO_CODEC_ENCODER_INTERFACE (vpx_codec_vp8_cx())
#include
#include "toxav.h"
#include "../toxcore/util.h"
struct RTPMessage;
typedef struct VCSession_s {
/* encoding */
vpx_codec_ctx_t encoder[1];
uint32_t frame_counter;
/* decoding */
vpx_codec_ctx_t decoder[1];
void *vbuf_raw; /* Un-decoded data */
uint64_t linfts; /* Last received frame time stamp */
uint32_t lcfd; /* Last calculated frame duration for incoming video payload */
ToxAV *av;
uint32_t friend_number;
PAIR(toxav_video_receive_frame_cb *, void *) vcb; /* Video frame receive callback */
pthread_mutex_t queue_mutex[1];
} VCSession;
VCSession *vc_new(ToxAV *av, uint32_t friend_number, toxav_video_receive_frame_cb *cb, void *cb_data);
void vc_kill(VCSession *vc);
void vc_iterate(VCSession *vc);
int vc_queue_message(void *vcp, struct RTPMessage *msg);
int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uint16_t height);
#endif /* VIDEO_H */
================================================
FILE: toxcore/DHT.c
================================================
/* DHT.c
*
* An implementation of the DHT as seen in docs/updates/DHT.md
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
/*----------------------------------------------------------------------------------*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef DEBUG
#include
#endif
#include "logger.h"
#include "DHT.h"
#ifdef ENABLE_ASSOC_DHT
#include "assoc.h"
#endif
#include "ping.h"
#include "network.h"
#include "LAN_discovery.h"
#include "misc_tools.h"
#include "util.h"
/* The timeout after which a node is discarded completely. */
#define KILL_NODE_TIMEOUT (BAD_NODE_TIMEOUT + PING_INTERVAL)
/* Ping interval in seconds for each random sending of a get nodes request. */
#define GET_NODE_INTERVAL 20
#define MAX_PUNCHING_PORTS 48
/* Interval in seconds between punching attempts*/
#define PUNCH_INTERVAL 3
#define MAX_NORMAL_PUNCHING_TRIES 5
#define NAT_PING_REQUEST 0
#define NAT_PING_RESPONSE 1
/* Number of get node requests to send to quickly find close nodes. */
#define MAX_BOOTSTRAP_TIMES 5
/* Compares pk1 and pk2 with pk.
*
* return 0 if both are same distance.
* return 1 if pk1 is closer.
* return 2 if pk2 is closer.
*/
int id_closest(const uint8_t *pk, const uint8_t *pk1, const uint8_t *pk2)
{
size_t i;
uint8_t distance1, distance2;
for (i = 0; i < crypto_box_PUBLICKEYBYTES; ++i) {
distance1 = pk[i] ^ pk1[i];
distance2 = pk[i] ^ pk2[i];
if (distance1 < distance2)
return 1;
if (distance1 > distance2)
return 2;
}
return 0;
}
/* Return index of first unequal bit number.
*/
static unsigned int bit_by_bit_cmp(const uint8_t *pk1, const uint8_t *pk2)
{
unsigned int i, j = 0;
for (i = 0; i < crypto_box_PUBLICKEYBYTES; ++i) {
if (pk1[i] == pk2[i])
continue;
for (j = 0; j < 8; ++j) {
if ((pk1[i] & (1 << (7 - j))) != (pk2[i] & (1 << (7 - j))))
break;
}
break;
}
return i * 8 + j;
}
/* Shared key generations are costly, it is therefor smart to store commonly used
* ones so that they can re used later without being computed again.
*
* If shared key is already in shared_keys, copy it to shared_key.
* else generate it into shared_key and copy it to shared_keys
*/
void get_shared_key(Shared_Keys *shared_keys, uint8_t *shared_key, const uint8_t *secret_key, const uint8_t *public_key)
{
uint32_t i, num = ~0, curr = 0;
for (i = 0; i < MAX_KEYS_PER_SLOT; ++i) {
int index = public_key[30] * MAX_KEYS_PER_SLOT + i;
if (shared_keys->keys[index].stored) {
if (public_key_cmp(public_key, shared_keys->keys[index].public_key) == 0) {
memcpy(shared_key, shared_keys->keys[index].shared_key, crypto_box_BEFORENMBYTES);
++shared_keys->keys[index].times_requested;
shared_keys->keys[index].time_last_requested = unix_time();
return;
}
if (num != 0) {
if (is_timeout(shared_keys->keys[index].time_last_requested, KEYS_TIMEOUT)) {
num = 0;
curr = index;
} else if (num > shared_keys->keys[index].times_requested) {
num = shared_keys->keys[index].times_requested;
curr = index;
}
}
} else {
if (num != 0) {
num = 0;
curr = index;
}
}
}
encrypt_precompute(public_key, secret_key, shared_key);
if (num != (uint32_t)~0) {
shared_keys->keys[curr].stored = 1;
shared_keys->keys[curr].times_requested = 1;
memcpy(shared_keys->keys[curr].public_key, public_key, crypto_box_PUBLICKEYBYTES);
memcpy(shared_keys->keys[curr].shared_key, shared_key, crypto_box_BEFORENMBYTES);
shared_keys->keys[curr].time_last_requested = unix_time();
}
}
/* Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key
* for packets that we receive.
*/
void DHT_get_shared_key_recv(DHT *dht, uint8_t *shared_key, const uint8_t *public_key)
{
get_shared_key(&dht->shared_keys_recv, shared_key, dht->self_secret_key, public_key);
}
/* Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key
* for packets that we send.
*/
void DHT_get_shared_key_sent(DHT *dht, uint8_t *shared_key, const uint8_t *public_key)
{
get_shared_key(&dht->shared_keys_sent, shared_key, dht->self_secret_key, public_key);
}
void to_net_family(IP *ip)
{
if (ip->family == AF_INET)
ip->family = TOX_AF_INET;
else if (ip->family == AF_INET6)
ip->family = TOX_AF_INET6;
}
int to_host_family(IP *ip)
{
if (ip->family == TOX_AF_INET) {
ip->family = AF_INET;
return 0;
} else if (ip->family == TOX_AF_INET6) {
ip->family = AF_INET6;
return 0;
} else {
return -1;
}
}
#define PACKED_NODE_SIZE_IP4 (1 + SIZE_IP4 + sizeof(uint16_t) + crypto_box_PUBLICKEYBYTES)
#define PACKED_NODE_SIZE_IP6 (1 + SIZE_IP6 + sizeof(uint16_t) + crypto_box_PUBLICKEYBYTES)
/* Return packet size of packed node with ip_family on success.
* Return -1 on failure.
*/
int packed_node_size(uint8_t ip_family)
{
if (ip_family == AF_INET) {
return PACKED_NODE_SIZE_IP4;
} else if (ip_family == TCP_INET) {
return PACKED_NODE_SIZE_IP4;
} else if (ip_family == AF_INET6) {
return PACKED_NODE_SIZE_IP6;
} else if (ip_family == TCP_INET6) {
return PACKED_NODE_SIZE_IP6;
} else {
return -1;
}
}
/* Pack number of nodes into data of maxlength length.
*
* return length of packed nodes on success.
* return -1 on failure.
*/
int pack_nodes(uint8_t *data, uint16_t length, const Node_format *nodes, uint16_t number)
{
uint32_t i, packed_length = 0;
for (i = 0; i < number; ++i) {
int ipv6 = -1;
uint8_t net_family;
// FIXME use functions to convert endianness
if (nodes[i].ip_port.ip.family == AF_INET) {
ipv6 = 0;
net_family = TOX_AF_INET;
} else if (nodes[i].ip_port.ip.family == TCP_INET) {
ipv6 = 0;
net_family = TOX_TCP_INET;
} else if (nodes[i].ip_port.ip.family == AF_INET6) {
ipv6 = 1;
net_family = TOX_AF_INET6;
} else if (nodes[i].ip_port.ip.family == TCP_INET6) {
ipv6 = 1;
net_family = TOX_TCP_INET6;
} else {
return -1;
}
if (ipv6 == 0) {
uint32_t size = PACKED_NODE_SIZE_IP4;
if (packed_length + size > length)
return -1;
data[packed_length] = net_family;
memcpy(data + packed_length + 1, &nodes[i].ip_port.ip.ip4, SIZE_IP4);
memcpy(data + packed_length + 1 + SIZE_IP4, &nodes[i].ip_port.port, sizeof(uint16_t));
memcpy(data + packed_length + 1 + SIZE_IP4 + sizeof(uint16_t), nodes[i].public_key, crypto_box_PUBLICKEYBYTES);
packed_length += size;
} else if (ipv6 == 1) {
uint32_t size = PACKED_NODE_SIZE_IP6;
if (packed_length + size > length)
return -1;
data[packed_length] = net_family;
memcpy(data + packed_length + 1, &nodes[i].ip_port.ip.ip6, SIZE_IP6);
memcpy(data + packed_length + 1 + SIZE_IP6, &nodes[i].ip_port.port, sizeof(uint16_t));
memcpy(data + packed_length + 1 + SIZE_IP6 + sizeof(uint16_t), nodes[i].public_key, crypto_box_PUBLICKEYBYTES);
packed_length += size;
} else {
return -1;
}
}
return packed_length;
}
/* Unpack data of length into nodes of size max_num_nodes.
* Put the length of the data processed in processed_data_len.
* tcp_enabled sets if TCP nodes are expected (true) or not (false).
*
* return number of unpacked nodes on success.
* return -1 on failure.
*/
int unpack_nodes(Node_format *nodes, uint16_t max_num_nodes, uint16_t *processed_data_len, const uint8_t *data,
uint16_t length, uint8_t tcp_enabled)
{
uint32_t num = 0, len_processed = 0;
while (num < max_num_nodes && len_processed < length) {
int ipv6 = -1;
uint8_t host_family;
if (data[len_processed] == TOX_AF_INET) {
ipv6 = 0;
host_family = AF_INET;
} else if (data[len_processed] == TOX_TCP_INET) {
if (!tcp_enabled)
return -1;
ipv6 = 0;
host_family = TCP_INET;
} else if (data[len_processed] == TOX_AF_INET6) {
ipv6 = 1;
host_family = AF_INET6;
} else if (data[len_processed] == TOX_TCP_INET6) {
if (!tcp_enabled)
return -1;
ipv6 = 1;
host_family = TCP_INET6;
} else {
return -1;
}
if (ipv6 == 0) {
uint32_t size = PACKED_NODE_SIZE_IP4;
if (len_processed + size > length)
return -1;
nodes[num].ip_port.ip.family = host_family;
memcpy(&nodes[num].ip_port.ip.ip4, data + len_processed + 1, SIZE_IP4);
memcpy(&nodes[num].ip_port.port, data + len_processed + 1 + SIZE_IP4, sizeof(uint16_t));
memcpy(nodes[num].public_key, data + len_processed + 1 + SIZE_IP4 + sizeof(uint16_t), crypto_box_PUBLICKEYBYTES);
len_processed += size;
++num;
} else if (ipv6 == 1) {
uint32_t size = PACKED_NODE_SIZE_IP6;
if (len_processed + size > length)
return -1;
nodes[num].ip_port.ip.family = host_family;
memcpy(&nodes[num].ip_port.ip.ip6, data + len_processed + 1, SIZE_IP6);
memcpy(&nodes[num].ip_port.port, data + len_processed + 1 + SIZE_IP6, sizeof(uint16_t));
memcpy(nodes[num].public_key, data + len_processed + 1 + SIZE_IP6 + sizeof(uint16_t), crypto_box_PUBLICKEYBYTES);
len_processed += size;
++num;
} else {
return -1;
}
}
if (processed_data_len)
*processed_data_len = len_processed;
return num;
}
/* Check if client with public_key is already in list of length length.
* If it is then set its corresponding timestamp to current time.
* If the id is already in the list with a different ip_port, update it.
* TODO: Maybe optimize this.
*
* return True(1) or False(0)
*/
static int client_or_ip_port_in_list(Client_data *list, uint16_t length, const uint8_t *public_key, IP_Port ip_port)
{
uint32_t i;
uint64_t temp_time = unix_time();
/* if public_key is in list, find it and maybe overwrite ip_port */
for (i = 0; i < length; ++i)
if (id_equal(list[i].public_key, public_key)) {
/* Refresh the client timestamp. */
if (ip_port.ip.family == AF_INET) {
LOGGER_SCOPE( if (!ipport_equal(&list[i].assoc4.ip_port, &ip_port)) {
LOGGER_TRACE("coipil[%u]: switching ipv4 from %s:%u to %s:%u", i,
ip_ntoa(&list[i].assoc4.ip_port.ip), ntohs(list[i].assoc4.ip_port.port),
ip_ntoa(&ip_port.ip), ntohs(ip_port.port));
}
);
if (LAN_ip(list[i].assoc4.ip_port.ip) != 0 && LAN_ip(ip_port.ip) == 0)
return 1;
list[i].assoc4.ip_port = ip_port;
list[i].assoc4.timestamp = temp_time;
} else if (ip_port.ip.family == AF_INET6) {
LOGGER_SCOPE( if (!ipport_equal(&list[i].assoc4.ip_port, &ip_port)) {
LOGGER_TRACE("coipil[%u]: switching ipv6 from %s:%u to %s:%u", i,
ip_ntoa(&list[i].assoc6.ip_port.ip), ntohs(list[i].assoc6.ip_port.port),
ip_ntoa(&ip_port.ip), ntohs(ip_port.port));
}
);
if (LAN_ip(list[i].assoc6.ip_port.ip) != 0 && LAN_ip(ip_port.ip) == 0)
return 1;
list[i].assoc6.ip_port = ip_port;
list[i].assoc6.timestamp = temp_time;
}
return 1;
}
/* public_key not in list yet: see if we can find an identical ip_port, in
* that case we kill the old public_key by overwriting it with the new one
* TODO: maybe we SHOULDN'T do that if that public_key is in a friend_list
* and the one who is the actual friend's public_key/address set? */
for (i = 0; i < length; ++i) {
/* MAYBE: check the other address, if valid, don't nuke? */
if ((ip_port.ip.family == AF_INET) && ipport_equal(&list[i].assoc4.ip_port, &ip_port)) {
/* Initialize client timestamp. */
list[i].assoc4.timestamp = temp_time;
memcpy(list[i].public_key, public_key, crypto_box_PUBLICKEYBYTES);
LOGGER_DEBUG("coipil[%u]: switching public_key (ipv4)", i);
/* kill the other address, if it was set */
memset(&list[i].assoc6, 0, sizeof(list[i].assoc6));
return 1;
} else if ((ip_port.ip.family == AF_INET6) && ipport_equal(&list[i].assoc6.ip_port, &ip_port)) {
/* Initialize client timestamp. */
list[i].assoc6.timestamp = temp_time;
memcpy(list[i].public_key, public_key, crypto_box_PUBLICKEYBYTES);
LOGGER_DEBUG("coipil[%u]: switching public_key (ipv6)", i);
/* kill the other address, if it was set */
memset(&list[i].assoc4, 0, sizeof(list[i].assoc4));
return 1;
}
}
return 0;
}
/* Check if client with public_key is already in node format list of length length.
*
* return 1 if true.
* return 0 if false.
*/
static int client_in_nodelist(const Node_format *list, uint16_t length, const uint8_t *public_key)
{
uint32_t i;
for (i = 0; i < length; ++i) {
if (id_equal(list[i].public_key, public_key))
return 1;
}
return 0;
}
/* return friend number from the public_key.
* return -1 if a failure occurs.
*/
static int friend_number(const DHT *dht, const uint8_t *public_key)
{
uint32_t i;
for (i = 0; i < dht->num_friends; ++i) {
if (id_equal(dht->friends_list[i].public_key, public_key))
return i;
}
return -1;
}
/* Add node to the node list making sure only the nodes closest to cmp_pk are in the list.
*/
_Bool add_to_list(Node_format *nodes_list, unsigned int length, const uint8_t *pk, IP_Port ip_port,
const uint8_t *cmp_pk)
{
uint8_t pk_bak[crypto_box_PUBLICKEYBYTES];
IP_Port ip_port_bak;
unsigned int i;
for (i = 0; i < length; ++i) {
if (id_closest(cmp_pk, nodes_list[i].public_key, pk) == 2) {
memcpy(pk_bak, nodes_list[i].public_key, crypto_box_PUBLICKEYBYTES);
ip_port_bak = nodes_list[i].ip_port;
memcpy(nodes_list[i].public_key, pk, crypto_box_PUBLICKEYBYTES);
nodes_list[i].ip_port = ip_port;
if (i != (length - 1))
add_to_list(nodes_list, length, pk_bak, ip_port_bak, cmp_pk);
return 1;
}
}
return 0;
}
/*TODO: change this to 7 when done*/
#define HARDENING_ALL_OK 2
/* return 0 if not.
* return 1 if route request are ok
* return 2 if it responds to send node packets correctly
* return 4 if it can test other nodes correctly
* return HARDENING_ALL_OK if all ok.
*/
static uint8_t hardening_correct(const Hardening *h)
{
return h->routes_requests_ok + (h->send_nodes_ok << 1) + (h->testing_requests << 2);
}
/*
* helper for get_close_nodes(). argument list is a monster :D
*/
static void get_close_nodes_inner(const uint8_t *public_key, Node_format *nodes_list,
sa_family_t sa_family, const Client_data *client_list, uint32_t client_list_length,
uint32_t *num_nodes_ptr, uint8_t is_LAN, uint8_t want_good)
{
if ((sa_family != AF_INET) && (sa_family != AF_INET6) && (sa_family != 0))
return;
uint32_t num_nodes = *num_nodes_ptr;
uint32_t i;
for (i = 0; i < client_list_length; i++) {
const Client_data *client = &client_list[i];
/* node already in list? */
if (client_in_nodelist(nodes_list, MAX_SENT_NODES, client->public_key))
continue;
const IPPTsPng *ipptp = NULL;
if (sa_family == AF_INET) {
ipptp = &client->assoc4;
} else if (sa_family == AF_INET6) {
ipptp = &client->assoc6;
} else {
if (client->assoc4.timestamp >= client->assoc6.timestamp) {
ipptp = &client->assoc4;
} else {
ipptp = &client->assoc6;
}
}
/* node not in a good condition? */
if (is_timeout(ipptp->timestamp, BAD_NODE_TIMEOUT))
continue;
/* don't send LAN ips to non LAN peers */
if (LAN_ip(ipptp->ip_port.ip) == 0 && !is_LAN)
continue;
if (LAN_ip(ipptp->ip_port.ip) != 0 && want_good && hardening_correct(&ipptp->hardening) != HARDENING_ALL_OK
&& !id_equal(public_key, client->public_key))
continue;
if (num_nodes < MAX_SENT_NODES) {
memcpy(nodes_list[num_nodes].public_key,
client->public_key,
crypto_box_PUBLICKEYBYTES );
nodes_list[num_nodes].ip_port = ipptp->ip_port;
num_nodes++;
} else {
add_to_list(nodes_list, MAX_SENT_NODES, client->public_key, ipptp->ip_port, public_key);
}
}
*num_nodes_ptr = num_nodes;
}
/* Find MAX_SENT_NODES nodes closest to the public_key for the send nodes request:
* put them in the nodes_list and return how many were found.
*
* TODO: For the love of based make
* this function cleaner and much more efficient.
*
* want_good : do we want only good nodes as checked with the hardening returned or not?
*/
static int get_somewhat_close_nodes(const DHT *dht, const uint8_t *public_key, Node_format *nodes_list,
sa_family_t sa_family, uint8_t is_LAN, uint8_t want_good)
{
uint32_t num_nodes = 0, i;
get_close_nodes_inner(public_key, nodes_list, sa_family,
dht->close_clientlist, LCLIENT_LIST, &num_nodes, is_LAN, 0);
/*TODO uncomment this when hardening is added to close friend clients
for (i = 0; i < dht->num_friends; ++i)
get_close_nodes_inner(dht, public_key, nodes_list, sa_family,
dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS,
&num_nodes, is_LAN, want_good);
*/
for (i = 0; i < dht->num_friends; ++i)
get_close_nodes_inner(public_key, nodes_list, sa_family,
dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS,
&num_nodes, is_LAN, 0);
return num_nodes;
}
int get_close_nodes(const DHT *dht, const uint8_t *public_key, Node_format *nodes_list, sa_family_t sa_family,
uint8_t is_LAN, uint8_t want_good)
{
memset(nodes_list, 0, MAX_SENT_NODES * sizeof(Node_format));
#ifdef ENABLE_ASSOC_DHT
if (!dht->assoc)
#endif
return get_somewhat_close_nodes(dht, public_key, nodes_list, sa_family, is_LAN, want_good);
#ifdef ENABLE_ASSOC_DHT
//TODO: assoc, sa_family 0 (don't care if ipv4 or ipv6) support.
Client_data *result[MAX_SENT_NODES];
Assoc_close_entries request;
memset(&request, 0, sizeof(request));
request.count = MAX_SENT_NODES;
request.count_good = MAX_SENT_NODES - 2; /* allow 2 'indirect' nodes */
request.result = result;
request.wanted_id = public_key;
request.flags = (is_LAN ? LANOk : 0) + (sa_family == AF_INET ? ProtoIPv4 : ProtoIPv6);
uint8_t num_found = Assoc_get_close_entries(dht->assoc, &request);
if (!num_found) {
LOGGER_DEBUG("get_close_nodes(): Assoc_get_close_entries() returned zero nodes");
return get_somewhat_close_nodes(dht, public_key, nodes_list, sa_family, is_LAN, want_good);
}
LOGGER_DEBUG("get_close_nodes(): Assoc_get_close_entries() returned %i 'direct' and %i 'indirect' nodes",
request.count_good, num_found - request.count_good);
uint8_t i, num_returned = 0;
for (i = 0; i < num_found; i++) {
Client_data *client = result[i];
if (client) {
id_copy(nodes_list[num_returned].public_key, client->public_key);
if (sa_family == AF_INET)
if (ipport_isset(&client->assoc4.ip_port)) {
nodes_list[num_returned].ip_port = client->assoc4.ip_port;
num_returned++;
continue;
}
if (sa_family == AF_INET6)
if (ipport_isset(&client->assoc6.ip_port)) {
nodes_list[num_returned].ip_port = client->assoc6.ip_port;
num_returned++;
continue;
}
}
}
return num_returned;
#endif
}
static uint8_t cmp_public_key[crypto_box_PUBLICKEYBYTES];
static int cmp_dht_entry(const void *a, const void *b)
{
Client_data entry1, entry2;
memcpy(&entry1, a, sizeof(Client_data));
memcpy(&entry2, b, sizeof(Client_data));
int t1 = is_timeout(entry1.assoc4.timestamp, BAD_NODE_TIMEOUT) && is_timeout(entry1.assoc6.timestamp, BAD_NODE_TIMEOUT);
int t2 = is_timeout(entry2.assoc4.timestamp, BAD_NODE_TIMEOUT) && is_timeout(entry2.assoc6.timestamp, BAD_NODE_TIMEOUT);
if (t1 && t2)
return 0;
if (t1)
return -1;
if (t2)
return 1;
t1 = hardening_correct(&entry1.assoc4.hardening) != HARDENING_ALL_OK
&& hardening_correct(&entry1.assoc6.hardening) != HARDENING_ALL_OK;
t2 = hardening_correct(&entry2.assoc4.hardening) != HARDENING_ALL_OK
&& hardening_correct(&entry2.assoc6.hardening) != HARDENING_ALL_OK;
if (t1 != t2) {
if (t1)
return -1;
if (t2)
return 1;
}
int close = id_closest(cmp_public_key, entry1.public_key, entry2.public_key);
if (close == 1)
return 1;
if (close == 2)
return -1;
return 0;
}
/* Is it ok to store node with public_key in client.
*
* return 0 if node can't be stored.
* return 1 if it can.
*/
static unsigned int store_node_ok(const Client_data *client, const uint8_t *public_key, const uint8_t *comp_public_key)
{
if ((is_timeout(client->assoc4.timestamp, BAD_NODE_TIMEOUT) && is_timeout(client->assoc6.timestamp, BAD_NODE_TIMEOUT))
|| (id_closest(comp_public_key, client->public_key, public_key) == 2)) {
return 1;
} else {
return 0;
}
}
static void sort_client_list(Client_data *list, unsigned int length, const uint8_t *comp_public_key)
{
memcpy(cmp_public_key, comp_public_key, crypto_box_PUBLICKEYBYTES);
qsort(list, length, sizeof(Client_data), cmp_dht_entry);
}
/* Replace a first bad (or empty) node with this one
* or replace a possibly bad node (tests failed or not done yet)
* that is further than any other in the list
* from the comp_public_key
* or replace a good node that is further
* than any other in the list from the comp_public_key
* and further than public_key.
*
* Do not replace any node if the list has no bad or possibly bad nodes
* and all nodes in the list are closer to comp_public_key
* than public_key.
*
* returns True(1) when the item was stored, False(0) otherwise */
static int replace_all( Client_data *list,
uint16_t length,
const uint8_t *public_key,
IP_Port ip_port,
const uint8_t *comp_public_key )
{
if ((ip_port.ip.family != AF_INET) && (ip_port.ip.family != AF_INET6))
return 0;
if (store_node_ok(&list[1], public_key, comp_public_key) || store_node_ok(&list[0], public_key, comp_public_key)) {
sort_client_list(list, length, comp_public_key);
IPPTsPng *ipptp_write = NULL;
IPPTsPng *ipptp_clear = NULL;
Client_data *client = &list[0];
if (ip_port.ip.family == AF_INET) {
ipptp_write = &client->assoc4;
ipptp_clear = &client->assoc6;
} else {
ipptp_write = &client->assoc6;
ipptp_clear = &client->assoc4;
}
id_copy(client->public_key, public_key);
ipptp_write->ip_port = ip_port;
ipptp_write->timestamp = unix_time();
ip_reset(&ipptp_write->ret_ip_port.ip);
ipptp_write->ret_ip_port.port = 0;
ipptp_write->ret_timestamp = 0;
/* zero out other address */
memset(ipptp_clear, 0, sizeof(*ipptp_clear));
return 1;
}
return 0;
}
/* Add node to close list.
*
* simulate is set to 1 if we want to check if a node can be added to the list without adding it.
*
* return -1 on failure.
* return 0 on success.
*/
static int add_to_close(DHT *dht, const uint8_t *public_key, IP_Port ip_port, _Bool simulate)
{
unsigned int i;
unsigned int index = bit_by_bit_cmp(public_key, dht->self_public_key);
if (index > LCLIENT_LENGTH)
index = LCLIENT_LENGTH - 1;
for (i = 0; i < LCLIENT_NODES; ++i) {
Client_data *client = &dht->close_clientlist[(index * LCLIENT_NODES) + i];
if (is_timeout(client->assoc4.timestamp, BAD_NODE_TIMEOUT) && is_timeout(client->assoc6.timestamp, BAD_NODE_TIMEOUT)) {
if (!simulate) {
IPPTsPng *ipptp_write = NULL;
IPPTsPng *ipptp_clear = NULL;
if (ip_port.ip.family == AF_INET) {
ipptp_write = &client->assoc4;
ipptp_clear = &client->assoc6;
} else {
ipptp_write = &client->assoc6;
ipptp_clear = &client->assoc4;
}
id_copy(client->public_key, public_key);
ipptp_write->ip_port = ip_port;
ipptp_write->timestamp = unix_time();
ip_reset(&ipptp_write->ret_ip_port.ip);
ipptp_write->ret_ip_port.port = 0;
ipptp_write->ret_timestamp = 0;
/* zero out other address */
memset(ipptp_clear, 0, sizeof(*ipptp_clear));
}
return 0;
}
}
return -1;
}
/* Return 1 if node can be added to close list, 0 if it can't.
*/
_Bool node_addable_to_close_list(DHT *dht, const uint8_t *public_key, IP_Port ip_port)
{
if (add_to_close(dht, public_key, ip_port, 1) == 0) {
return 1;
}
return 0;
}
static _Bool is_pk_in_client_list(Client_data *list, unsigned int client_list_length, const uint8_t *public_key,
IP_Port ip_port)
{
unsigned int i;
for (i = 0; i < client_list_length; ++i) {
if ((ip_port.ip.family == AF_INET && !is_timeout(list[i].assoc4.timestamp, BAD_NODE_TIMEOUT))
|| (ip_port.ip.family == AF_INET6 && !is_timeout(list[i].assoc6.timestamp, BAD_NODE_TIMEOUT))) {
if (public_key_cmp(list[i].public_key, public_key) == 0) {
return 1;
}
}
}
return 0;
}
/* Check if the node obtained with a get_nodes with public_key should be pinged.
* NOTE: for best results call it after addto_lists;
*
* return 0 if the node should not be pinged.
* return 1 if it should.
*/
static unsigned int ping_node_from_getnodes_ok(DHT *dht, const uint8_t *public_key, IP_Port ip_port)
{
_Bool ret = 0;
if (add_to_close(dht, public_key, ip_port, 1) == 0) {
ret = 1;
}
if (ret && !client_in_nodelist(dht->to_bootstrap, dht->num_to_bootstrap, public_key)) {
if (dht->num_to_bootstrap < MAX_CLOSE_TO_BOOTSTRAP_NODES) {
memcpy(dht->to_bootstrap[dht->num_to_bootstrap].public_key, public_key, crypto_box_PUBLICKEYBYTES);
dht->to_bootstrap[dht->num_to_bootstrap].ip_port = ip_port;
++dht->num_to_bootstrap;
} else {
//TODO: ipv6 vs v4
add_to_list(dht->to_bootstrap, MAX_CLOSE_TO_BOOTSTRAP_NODES, public_key, ip_port, dht->self_public_key);
}
}
unsigned int i;
for (i = 0; i < dht->num_friends; ++i) {
_Bool store_ok = 0;
DHT_Friend *friend = &dht->friends_list[i];
if (store_node_ok(&friend->client_list[1], public_key, friend->public_key)) {
store_ok = 1;
}
if (store_node_ok(&friend->client_list[0], public_key, friend->public_key)) {
store_ok = 1;
}
if (store_ok && !client_in_nodelist(friend->to_bootstrap, friend->num_to_bootstrap, public_key)
&& !is_pk_in_client_list(friend->client_list, MAX_FRIEND_CLIENTS, public_key, ip_port)) {
if (friend->num_to_bootstrap < MAX_SENT_NODES) {
memcpy(friend->to_bootstrap[friend->num_to_bootstrap].public_key, public_key, crypto_box_PUBLICKEYBYTES);
friend->to_bootstrap[friend->num_to_bootstrap].ip_port = ip_port;
++friend->num_to_bootstrap;
} else {
add_to_list(friend->to_bootstrap, MAX_SENT_NODES, public_key, ip_port, friend->public_key);
}
ret = 1;
}
}
return ret;
}
/* Attempt to add client with ip_port and public_key to the friends client list
* and close_clientlist.
*
* returns 1+ if the item is used in any list, 0 else
*/
int addto_lists(DHT *dht, IP_Port ip_port, const uint8_t *public_key)
{
uint32_t i, used = 0;
/* convert IPv4-in-IPv6 to IPv4 */
if ((ip_port.ip.family == AF_INET6) && IPV6_IPV4_IN_V6(ip_port.ip.ip6)) {
ip_port.ip.family = AF_INET;
ip_port.ip.ip4.uint32 = ip_port.ip.ip6.uint32[3];
}
/* NOTE: Current behavior if there are two clients with the same id is
* to replace the first ip by the second.
*/
if (!client_or_ip_port_in_list(dht->close_clientlist, LCLIENT_LIST, public_key, ip_port)) {
if (add_to_close(dht, public_key, ip_port, 0))
used++;
} else
used++;
DHT_Friend *friend_foundip = 0;
for (i = 0; i < dht->num_friends; ++i) {
if (!client_or_ip_port_in_list(dht->friends_list[i].client_list,
MAX_FRIEND_CLIENTS, public_key, ip_port)) {
if (replace_all(dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS,
public_key, ip_port, dht->friends_list[i].public_key)) {
DHT_Friend *friend = &dht->friends_list[i];
if (public_key_cmp(public_key, friend->public_key) == 0) {
friend_foundip = friend;
}
used++;
}
} else {
DHT_Friend *friend = &dht->friends_list[i];
if (public_key_cmp(public_key, friend->public_key) == 0) {
friend_foundip = friend;
}
used++;
}
}
if (friend_foundip) {
uint32_t j;
for (j = 0; j < friend_foundip->lock_count; ++j) {
if (friend_foundip->callbacks[j].ip_callback)
friend_foundip->callbacks[j].ip_callback(friend_foundip->callbacks[j].data, friend_foundip->callbacks[j].number,
ip_port);
}
}
#ifdef ENABLE_ASSOC_DHT
if (dht->assoc) {
IPPTs ippts;
ippts.ip_port = ip_port;
ippts.timestamp = unix_time();
Assoc_add_entry(dht->assoc, public_key, &ippts, NULL, used ? 1 : 0);
}
#endif
return used;
}
/* If public_key is a friend or us, update ret_ip_port
* nodepublic_key is the id of the node that sent us this info.
*/
static int returnedip_ports(DHT *dht, IP_Port ip_port, const uint8_t *public_key, const uint8_t *nodepublic_key)
{
uint32_t i, j;
uint64_t temp_time = unix_time();
uint32_t used = 0;
/* convert IPv4-in-IPv6 to IPv4 */
if ((ip_port.ip.family == AF_INET6) && IPV6_IPV4_IN_V6(ip_port.ip.ip6)) {
ip_port.ip.family = AF_INET;
ip_port.ip.ip4.uint32 = ip_port.ip.ip6.uint32[3];
}
if (id_equal(public_key, dht->self_public_key)) {
for (i = 0; i < LCLIENT_LIST; ++i) {
if (id_equal(nodepublic_key, dht->close_clientlist[i].public_key)) {
if (ip_port.ip.family == AF_INET) {
dht->close_clientlist[i].assoc4.ret_ip_port = ip_port;
dht->close_clientlist[i].assoc4.ret_timestamp = temp_time;
} else if (ip_port.ip.family == AF_INET6) {
dht->close_clientlist[i].assoc6.ret_ip_port = ip_port;
dht->close_clientlist[i].assoc6.ret_timestamp = temp_time;
}
++used;
break;
}
}
} else {
for (i = 0; i < dht->num_friends; ++i) {
if (id_equal(public_key, dht->friends_list[i].public_key)) {
for (j = 0; j < MAX_FRIEND_CLIENTS; ++j) {
if (id_equal(nodepublic_key, dht->friends_list[i].client_list[j].public_key)) {
if (ip_port.ip.family == AF_INET) {
dht->friends_list[i].client_list[j].assoc4.ret_ip_port = ip_port;
dht->friends_list[i].client_list[j].assoc4.ret_timestamp = temp_time;
} else if (ip_port.ip.family == AF_INET6) {
dht->friends_list[i].client_list[j].assoc6.ret_ip_port = ip_port;
dht->friends_list[i].client_list[j].assoc6.ret_timestamp = temp_time;
}
++used;
goto end;
}
}
}
}
}
end:
#ifdef ENABLE_ASSOC_DHT
if (dht->assoc) {
IPPTs ippts;
ippts.ip_port = ip_port;
ippts.timestamp = temp_time;
/* this is only a hear-say entry, so ret-ipp is NULL, but used is required
* to decide how valuable it is ("used" may throw an "unused" entry out) */
Assoc_add_entry(dht->assoc, public_key, &ippts, NULL, used ? 1 : 0);
}
#endif
return 0;
}
/* Send a getnodes request.
sendback_node is the node that it will send back the response to (set to NULL to disable this) */
static int getnodes(DHT *dht, IP_Port ip_port, const uint8_t *public_key, const uint8_t *client_id,
const Node_format *sendback_node)
{
/* Check if packet is going to be sent to ourself. */
if (id_equal(public_key, dht->self_public_key))
return -1;
uint8_t plain_message[sizeof(Node_format) * 2] = {0};
Node_format receiver;
memcpy(receiver.public_key, public_key, crypto_box_PUBLICKEYBYTES);
receiver.ip_port = ip_port;
memcpy(plain_message, &receiver, sizeof(receiver));
uint64_t ping_id = 0;
if (sendback_node != NULL) {
memcpy(plain_message + sizeof(receiver), sendback_node, sizeof(Node_format));
ping_id = ping_array_add(&dht->dht_harden_ping_array, plain_message, sizeof(plain_message));
} else {
ping_id = ping_array_add(&dht->dht_ping_array, plain_message, sizeof(receiver));
}
if (ping_id == 0)
return -1;
uint8_t plain[crypto_box_PUBLICKEYBYTES + sizeof(ping_id)];
uint8_t encrypt[sizeof(plain) + crypto_box_MACBYTES];
uint8_t data[1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + sizeof(encrypt)];
memcpy(plain, client_id, crypto_box_PUBLICKEYBYTES);
memcpy(plain + crypto_box_PUBLICKEYBYTES, &ping_id, sizeof(ping_id));
uint8_t shared_key[crypto_box_BEFORENMBYTES];
DHT_get_shared_key_sent(dht, shared_key, public_key);
uint8_t nonce[crypto_box_NONCEBYTES];
new_nonce(nonce);
int len = encrypt_data_symmetric( shared_key,
nonce,
plain,
sizeof(plain),
encrypt );
if (len != sizeof(encrypt))
return -1;
data[0] = NET_PACKET_GET_NODES;
memcpy(data + 1, dht->self_public_key, crypto_box_PUBLICKEYBYTES);
memcpy(data + 1 + crypto_box_PUBLICKEYBYTES, nonce, crypto_box_NONCEBYTES);
memcpy(data + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES, encrypt, len);
return sendpacket(dht->net, ip_port, data, sizeof(data));
}
/* Send a send nodes response: message for IPv6 nodes */
static int sendnodes_ipv6(const DHT *dht, IP_Port ip_port, const uint8_t *public_key, const uint8_t *client_id,
const uint8_t *sendback_data, uint16_t length, const uint8_t *shared_encryption_key)
{
/* Check if packet is going to be sent to ourself. */
if (id_equal(public_key, dht->self_public_key))
return -1;
if (length != sizeof(uint64_t))
return -1;
size_t Node_format_size = sizeof(Node_format);
uint8_t data[1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES
+ Node_format_size * MAX_SENT_NODES + length + crypto_box_MACBYTES];
Node_format nodes_list[MAX_SENT_NODES];
uint32_t num_nodes = get_close_nodes(dht, client_id, nodes_list, 0, LAN_ip(ip_port.ip) == 0, 1);
uint8_t plain[1 + Node_format_size * MAX_SENT_NODES + length];
uint8_t encrypt[sizeof(plain) + crypto_box_MACBYTES];
uint8_t nonce[crypto_box_NONCEBYTES];
new_nonce(nonce);
int nodes_length = 0;
if (num_nodes) {
nodes_length = pack_nodes(plain + 1, Node_format_size * MAX_SENT_NODES, nodes_list, num_nodes);
if (nodes_length <= 0)
return -1;
}
plain[0] = num_nodes;
memcpy(plain + 1 + nodes_length, sendback_data, length);
int len = encrypt_data_symmetric( shared_encryption_key,
nonce,
plain,
1 + nodes_length + length,
encrypt );
if (len != 1 + nodes_length + length + crypto_box_MACBYTES)
return -1;
data[0] = NET_PACKET_SEND_NODES_IPV6;
memcpy(data + 1, dht->self_public_key, crypto_box_PUBLICKEYBYTES);
memcpy(data + 1 + crypto_box_PUBLICKEYBYTES, nonce, crypto_box_NONCEBYTES);
memcpy(data + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES, encrypt, len);
return sendpacket(dht->net, ip_port, data, 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + len);
}
static int handle_getnodes(void *object, IP_Port source, const uint8_t *packet, uint16_t length)
{
if (length != (1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + sizeof(
uint64_t) + crypto_box_MACBYTES))
return 1;
DHT *dht = object;
/* Check if packet is from ourself. */
if (id_equal(packet + 1, dht->self_public_key))
return 1;
uint8_t plain[crypto_box_PUBLICKEYBYTES + sizeof(uint64_t)];
uint8_t shared_key[crypto_box_BEFORENMBYTES];
DHT_get_shared_key_recv(dht, shared_key, packet + 1);
int len = decrypt_data_symmetric( shared_key,
packet + 1 + crypto_box_PUBLICKEYBYTES,
packet + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES,
crypto_box_PUBLICKEYBYTES + sizeof(uint64_t) + crypto_box_MACBYTES,
plain );
if (len != crypto_box_PUBLICKEYBYTES + sizeof(uint64_t))
return 1;
sendnodes_ipv6(dht, source, packet + 1, plain, plain + crypto_box_PUBLICKEYBYTES, sizeof(uint64_t), shared_key);
add_to_ping(dht->ping, packet + 1, source);
return 0;
}
/* return 0 if no
return 1 if yes */
static uint8_t sent_getnode_to_node(DHT *dht, const uint8_t *public_key, IP_Port node_ip_port, uint64_t ping_id,
Node_format *sendback_node)
{
uint8_t data[sizeof(Node_format) * 2];
if (ping_array_check(data, sizeof(data), &dht->dht_ping_array, ping_id) == sizeof(Node_format)) {
memset(sendback_node, 0, sizeof(Node_format));
} else if (ping_array_check(data, sizeof(data), &dht->dht_harden_ping_array, ping_id) == sizeof(data)) {
memcpy(sendback_node, data + sizeof(Node_format), sizeof(Node_format));
} else {
return 0;
}
Node_format test;
memcpy(&test, data, sizeof(Node_format));
if (!ipport_equal(&test.ip_port, &node_ip_port) || public_key_cmp(test.public_key, public_key) != 0)
return 0;
return 1;
}
/* Function is needed in following functions. */
static int send_hardening_getnode_res(const DHT *dht, const Node_format *sendto, const uint8_t *queried_client_id,
const uint8_t *nodes_data, uint16_t nodes_data_length);
static int handle_sendnodes_core(void *object, IP_Port source, const uint8_t *packet, uint16_t length,
Node_format *plain_nodes, uint16_t size_plain_nodes, uint32_t *num_nodes_out)
{
DHT *dht = object;
uint32_t cid_size = 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + 1 + sizeof(uint64_t) + crypto_box_MACBYTES;
if (length < cid_size) /* too short */
return 1;
uint32_t data_size = length - cid_size;
if (data_size == 0)
return 1;
if (data_size > sizeof(Node_format) * MAX_SENT_NODES) /* invalid length */
return 1;
uint8_t plain[1 + data_size + sizeof(uint64_t)];
uint8_t shared_key[crypto_box_BEFORENMBYTES];
DHT_get_shared_key_sent(dht, shared_key, packet + 1);
int len = decrypt_data_symmetric(
shared_key,
packet + 1 + crypto_box_PUBLICKEYBYTES,
packet + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES,
1 + data_size + sizeof(uint64_t) + crypto_box_MACBYTES,
plain);
if ((unsigned int)len != sizeof(plain))
return 1;
if (plain[0] > size_plain_nodes)
return 1;
Node_format sendback_node;
uint64_t ping_id;
memcpy(&ping_id, plain + 1 + data_size, sizeof(ping_id));
if (!sent_getnode_to_node(dht, packet + 1, source, ping_id, &sendback_node))
return 1;
uint16_t length_nodes = 0;
int num_nodes = unpack_nodes(plain_nodes, plain[0], &length_nodes, plain + 1, data_size, 0);
if (length_nodes != data_size)
return 1;
if (num_nodes != plain[0])
return 1;
if (num_nodes < 0)
return 1;
/* store the address the *request* was sent to */
addto_lists(dht, source, packet + 1);
*num_nodes_out = num_nodes;
send_hardening_getnode_res(dht, &sendback_node, packet + 1, plain + 1, data_size);
return 0;
}
static int handle_sendnodes_ipv6(void *object, IP_Port source, const uint8_t *packet, uint16_t length)
{
DHT *dht = object;
Node_format plain_nodes[MAX_SENT_NODES];
uint32_t num_nodes;
if (handle_sendnodes_core(object, source, packet, length, plain_nodes, MAX_SENT_NODES, &num_nodes))
return 1;
if (num_nodes == 0)
return 0;
uint32_t i;
for (i = 0; i < num_nodes; i++) {
if (ipport_isset(&plain_nodes[i].ip_port)) {
ping_node_from_getnodes_ok(dht, plain_nodes[i].public_key, plain_nodes[i].ip_port);
returnedip_ports(dht, plain_nodes[i].ip_port, plain_nodes[i].public_key, packet + 1);
}
}
return 0;
}
/*----------------------------------------------------------------------------------*/
/*------------------------END of packet handling functions--------------------------*/
int DHT_addfriend(DHT *dht, const uint8_t *public_key, void (*ip_callback)(void *data, int32_t number, IP_Port),
void *data, int32_t number, uint16_t *lock_count)
{
int friend_num = friend_number(dht, public_key);
uint16_t lock_num;
if (friend_num != -1) { /* Is friend already in DHT? */
DHT_Friend *friend = &dht->friends_list[friend_num];
if (friend->lock_count == DHT_FRIEND_MAX_LOCKS)
return -1;
lock_num = friend->lock_count;
++friend->lock_count;
friend->callbacks[lock_num].ip_callback = ip_callback;
friend->callbacks[lock_num].data = data;
friend->callbacks[lock_num].number = number;
if (lock_count)
*lock_count = lock_num + 1;
return 0;
}
DHT_Friend *temp;
temp = realloc(dht->friends_list, sizeof(DHT_Friend) * (dht->num_friends + 1));
if (temp == NULL)
return -1;
dht->friends_list = temp;
DHT_Friend *friend = &dht->friends_list[dht->num_friends];
memset(friend, 0, sizeof(DHT_Friend));
memcpy(friend->public_key, public_key, crypto_box_PUBLICKEYBYTES);
friend->nat.NATping_id = random_64b();
++dht->num_friends;
lock_num = friend->lock_count;
++friend->lock_count;
friend->callbacks[lock_num].ip_callback = ip_callback;
friend->callbacks[lock_num].data = data;
friend->callbacks[lock_num].number = number;
if (lock_count)
*lock_count = lock_num + 1;
friend->num_to_bootstrap = get_close_nodes(dht, friend->public_key, friend->to_bootstrap, 0, 1, 0);
return 0;
}
int DHT_delfriend(DHT *dht, const uint8_t *public_key, uint16_t lock_count)
{
int friend_num = friend_number(dht, public_key);
if (friend_num == -1) {
return -1;
}
DHT_Friend *friend = &dht->friends_list[friend_num];
--friend->lock_count;
if (friend->lock_count && lock_count) { /* DHT friend is still in use.*/
--lock_count;
friend->callbacks[lock_count].ip_callback = NULL;
friend->callbacks[lock_count].data = NULL;
friend->callbacks[lock_count].number = 0;
return 0;
}
DHT_Friend *temp;
--dht->num_friends;
if (dht->num_friends != friend_num) {
memcpy( &dht->friends_list[friend_num],
&dht->friends_list[dht->num_friends],
sizeof(DHT_Friend) );
}
if (dht->num_friends == 0) {
free(dht->friends_list);
dht->friends_list = NULL;
return 0;
}
temp = realloc(dht->friends_list, sizeof(DHT_Friend) * (dht->num_friends));
if (temp == NULL)
return -1;
dht->friends_list = temp;
return 0;
}
/* TODO: Optimize this. */
int DHT_getfriendip(const DHT *dht, const uint8_t *public_key, IP_Port *ip_port)
{
uint32_t i, j;
ip_reset(&ip_port->ip);
ip_port->port = 0;
for (i = 0; i < dht->num_friends; ++i) {
/* Equal */
if (id_equal(dht->friends_list[i].public_key, public_key)) {
for (j = 0; j < MAX_FRIEND_CLIENTS; ++j) {
Client_data *client = &dht->friends_list[i].client_list[j];
if (id_equal(client->public_key, public_key)) {
IPPTsPng *assoc = NULL;
uint32_t a;
for (a = 0, assoc = &client->assoc6; a < 2; a++, assoc = &client->assoc4)
if (!is_timeout(assoc->timestamp, BAD_NODE_TIMEOUT)) {
*ip_port = assoc->ip_port;
return 1;
}
}
}
return 0;
}
}
return -1;
}
/* returns number of nodes not in kill-timeout */
static uint8_t do_ping_and_sendnode_requests(DHT *dht, uint64_t *lastgetnode, const uint8_t *public_key,
Client_data *list, uint32_t list_count, uint32_t *bootstrap_times, _Bool sortable)
{
uint32_t i;
uint8_t not_kill = 0;
uint64_t temp_time = unix_time();
uint32_t num_nodes = 0;
Client_data *client_list[list_count * 2];
IPPTsPng *assoc_list[list_count * 2];
unsigned int sort = 0;
_Bool sort_ok = 0;
for (i = 0; i < list_count; i++) {
/* If node is not dead. */
Client_data *client = &list[i];
IPPTsPng *assoc;
uint32_t a;
for (a = 0, assoc = &client->assoc6; a < 2; a++, assoc = &client->assoc4)
if (!is_timeout(assoc->timestamp, KILL_NODE_TIMEOUT)) {
sort = 0;
not_kill++;
if (is_timeout(assoc->last_pinged, PING_INTERVAL)) {
getnodes(dht, assoc->ip_port, client->public_key, public_key, NULL);
assoc->last_pinged = temp_time;
}
/* If node is good. */
if (!is_timeout(assoc->timestamp, BAD_NODE_TIMEOUT)) {
client_list[num_nodes] = client;
assoc_list[num_nodes] = assoc;
++num_nodes;
}
} else {
++sort;
/* Timed out should be at beginning, if they are not, sort the list. */
if (sort > 1 && sort < (((i + 1) * 2) - 1)) {
sort_ok = 1;
}
}
}
if (sortable && sort_ok) {
sort_client_list(list, list_count, public_key);
}
if ((num_nodes != 0) && (is_timeout(*lastgetnode, GET_NODE_INTERVAL) || *bootstrap_times < MAX_BOOTSTRAP_TIMES)) {
uint32_t rand_node = rand() % (num_nodes);
if ((num_nodes - 1) != rand_node) {
rand_node += rand() % (num_nodes - (rand_node + 1));
}
getnodes(dht, assoc_list[rand_node]->ip_port, client_list[rand_node]->public_key, public_key, NULL);
*lastgetnode = temp_time;
++*bootstrap_times;
}
return not_kill;
}
/* Ping each client in the "friends" list every PING_INTERVAL seconds. Send a get nodes request
* every GET_NODE_INTERVAL seconds to a random good node for each "friend" in our "friends" list.
*/
static void do_DHT_friends(DHT *dht)
{
unsigned int i, j;
for (i = 0; i < dht->num_friends; ++i) {
DHT_Friend *friend = &dht->friends_list[i];
for (j = 0; j < friend->num_to_bootstrap; ++j) {
getnodes(dht, friend->to_bootstrap[j].ip_port, friend->to_bootstrap[j].public_key, friend->public_key, NULL);
}
friend->num_to_bootstrap = 0;
do_ping_and_sendnode_requests(dht, &friend->lastgetnode, friend->public_key, friend->client_list, MAX_FRIEND_CLIENTS,
&friend->bootstrap_times, 1);
}
}
/* Ping each client in the close nodes list every PING_INTERVAL seconds.
* Send a get nodes request every GET_NODE_INTERVAL seconds to a random good node in the list.
*/
static void do_Close(DHT *dht)
{
unsigned int i;
for (i = 0; i < dht->num_to_bootstrap; ++i) {
getnodes(dht, dht->to_bootstrap[i].ip_port, dht->to_bootstrap[i].public_key, dht->self_public_key, NULL);
}
dht->num_to_bootstrap = 0;
uint8_t not_killed = do_ping_and_sendnode_requests(dht, &dht->close_lastgetnodes, dht->self_public_key,
dht->close_clientlist, LCLIENT_LIST, &dht->close_bootstrap_times, 0);
if (!not_killed) {
/* all existing nodes are at least KILL_NODE_TIMEOUT,
* which means we are mute, as we only send packets to
* nodes NOT in KILL_NODE_TIMEOUT
*
* so: reset all nodes to be BAD_NODE_TIMEOUT, but not
* KILL_NODE_TIMEOUT, so we at least keep trying pings */
uint64_t badonly = unix_time() - BAD_NODE_TIMEOUT;
size_t i, a;
for (i = 0; i < LCLIENT_LIST; i++) {
Client_data *client = &dht->close_clientlist[i];
IPPTsPng *assoc;
for (a = 0, assoc = &client->assoc4; a < 2; a++, assoc = &client->assoc6)
if (assoc->timestamp)
assoc->timestamp = badonly;
}
}
}
void DHT_getnodes(DHT *dht, const IP_Port *from_ipp, const uint8_t *from_id, const uint8_t *which_id)
{
getnodes(dht, *from_ipp, from_id, which_id, NULL);
}
void DHT_bootstrap(DHT *dht, IP_Port ip_port, const uint8_t *public_key)
{
/*#ifdef ENABLE_ASSOC_DHT
if (dht->assoc) {
IPPTs ippts;
ippts.ip_port = ip_port;
ippts.timestamp = 0;
Assoc_add_entry(dht->assoc, public_key, &ippts, NULL, 0);
}
#endif*/
getnodes(dht, ip_port, public_key, dht->self_public_key, NULL);
}
int DHT_bootstrap_from_address(DHT *dht, const char *address, uint8_t ipv6enabled,
uint16_t port, const uint8_t *public_key)
{
IP_Port ip_port_v64;
IP *ip_extra = NULL;
IP_Port ip_port_v4;
ip_init(&ip_port_v64.ip, ipv6enabled);
if (ipv6enabled) {
/* setup for getting BOTH: an IPv6 AND an IPv4 address */
ip_port_v64.ip.family = AF_UNSPEC;
ip_reset(&ip_port_v4.ip);
ip_extra = &ip_port_v4.ip;
}
if (addr_resolve_or_parse_ip(address, &ip_port_v64.ip, ip_extra)) {
ip_port_v64.port = port;
DHT_bootstrap(dht, ip_port_v64, public_key);
if ((ip_extra != NULL) && ip_isset(ip_extra)) {
ip_port_v4.port = port;
DHT_bootstrap(dht, ip_port_v4, public_key);
}
return 1;
} else
return 0;
}
/* Send the given packet to node with public_key
*
* return -1 if failure.
*/
int route_packet(const DHT *dht, const uint8_t *public_key, const uint8_t *packet, uint16_t length)
{
uint32_t i;
for (i = 0; i < LCLIENT_LIST; ++i) {
if (id_equal(public_key, dht->close_clientlist[i].public_key)) {
const Client_data *client = &dht->close_clientlist[i];
if (ip_isset(&client->assoc6.ip_port.ip))
return sendpacket(dht->net, client->assoc6.ip_port, packet, length);
else if (ip_isset(&client->assoc4.ip_port.ip))
return sendpacket(dht->net, client->assoc4.ip_port, packet, length);
else
break;
}
}
return -1;
}
/* Puts all the different ips returned by the nodes for a friend_num into array ip_portlist.
* ip_portlist must be at least MAX_FRIEND_CLIENTS big.
*
* return the number of ips returned.
* return 0 if we are connected to friend or if no ips were found.
* return -1 if no such friend.
*/
static int friend_iplist(const DHT *dht, IP_Port *ip_portlist, uint16_t friend_num)
{
if (friend_num >= dht->num_friends)
return -1;
DHT_Friend *friend = &dht->friends_list[friend_num];
Client_data *client;
IP_Port ipv4s[MAX_FRIEND_CLIENTS];
int num_ipv4s = 0;
IP_Port ipv6s[MAX_FRIEND_CLIENTS];
int num_ipv6s = 0;
int i;
for (i = 0; i < MAX_FRIEND_CLIENTS; ++i) {
client = &(friend->client_list[i]);
/* If ip is not zero and node is good. */
if (ip_isset(&client->assoc4.ret_ip_port.ip) && !is_timeout(client->assoc4.ret_timestamp, BAD_NODE_TIMEOUT)) {
ipv4s[num_ipv4s] = client->assoc4.ret_ip_port;
++num_ipv4s;
}
if (ip_isset(&client->assoc6.ret_ip_port.ip) && !is_timeout(client->assoc6.ret_timestamp, BAD_NODE_TIMEOUT)) {
ipv6s[num_ipv6s] = client->assoc6.ret_ip_port;
++num_ipv6s;
}
if (id_equal(client->public_key, friend->public_key))
if (!is_timeout(client->assoc6.timestamp, BAD_NODE_TIMEOUT) || !is_timeout(client->assoc4.timestamp, BAD_NODE_TIMEOUT))
return 0; /* direct connectivity */
}
#ifdef FRIEND_IPLIST_PAD
memcpy(ip_portlist, ipv6s, num_ipv6s * sizeof(IP_Port));
if (num_ipv6s == MAX_FRIEND_CLIENTS)
return MAX_FRIEND_CLIENTS;
int num_ipv4s_used = MAX_FRIEND_CLIENTS - num_ipv6s;
if (num_ipv4s_used > num_ipv4s)
num_ipv4s_used = num_ipv4s;
memcpy(&ip_portlist[num_ipv6s], ipv4s, num_ipv4s_used * sizeof(IP_Port));
return num_ipv6s + num_ipv4s_used;
#else /* !FRIEND_IPLIST_PAD */
/* there must be some secret reason why we can't pad the longer list
* with the shorter one...
*/
if (num_ipv6s >= num_ipv4s) {
memcpy(ip_portlist, ipv6s, num_ipv6s * sizeof(IP_Port));
return num_ipv6s;
}
memcpy(ip_portlist, ipv4s, num_ipv4s * sizeof(IP_Port));
return num_ipv4s;
#endif /* !FRIEND_IPLIST_PAD */
}
/* Send the following packet to everyone who tells us they are connected to friend_id.
*
* return ip for friend.
* return number of nodes the packet was sent to. (Only works if more than (MAX_FRIEND_CLIENTS / 4).
*/
int route_tofriend(const DHT *dht, const uint8_t *friend_id, const uint8_t *packet, uint16_t length)
{
int num = friend_number(dht, friend_id);
if (num == -1)
return 0;
uint32_t i, sent = 0;
uint8_t friend_sent[MAX_FRIEND_CLIENTS] = {0};
IP_Port ip_list[MAX_FRIEND_CLIENTS];
int ip_num = friend_iplist(dht, ip_list, num);
if (ip_num < (MAX_FRIEND_CLIENTS / 4))
return 0; /* Reason for that? */
DHT_Friend *friend = &dht->friends_list[num];
Client_data *client;
/* extra legwork, because having the outside allocating the space for us
* is *usually* good(tm) (bites us in the behind in this case though) */
uint32_t a;
for (a = 0; a < 2; a++)
for (i = 0; i < MAX_FRIEND_CLIENTS; ++i) {
if (friend_sent[i])/* Send one packet per client.*/
continue;
client = &friend->client_list[i];
IPPTsPng *assoc = NULL;
if (!a)
assoc = &client->assoc4;
else
assoc = &client->assoc6;
/* If ip is not zero and node is good. */
if (ip_isset(&assoc->ret_ip_port.ip) &&
!is_timeout(assoc->ret_timestamp, BAD_NODE_TIMEOUT)) {
int retval = sendpacket(dht->net, assoc->ip_port, packet, length);
if ((unsigned int)retval == length) {
++sent;
friend_sent[i] = 1;
}
}
}
return sent;
}
/* Send the following packet to one random person who tells us they are connected to friend_id.
*
* return number of nodes the packet was sent to.
*/
static int routeone_tofriend(DHT *dht, const uint8_t *friend_id, const uint8_t *packet, uint16_t length)
{
int num = friend_number(dht, friend_id);
if (num == -1)
return 0;
DHT_Friend *friend = &dht->friends_list[num];
Client_data *client;
IP_Port ip_list[MAX_FRIEND_CLIENTS * 2];
int n = 0;
uint32_t i;
/* extra legwork, because having the outside allocating the space for us
* is *usually* good(tm) (bites us in the behind in this case though) */
uint32_t a;
for (a = 0; a < 2; a++)
for (i = 0; i < MAX_FRIEND_CLIENTS; ++i) {
client = &friend->client_list[i];
IPPTsPng *assoc = NULL;
if (!a)
assoc = &client->assoc4;
else
assoc = &client->assoc6;
/* If ip is not zero and node is good. */
if (ip_isset(&assoc->ret_ip_port.ip) && !is_timeout(assoc->ret_timestamp, BAD_NODE_TIMEOUT)) {
ip_list[n] = assoc->ip_port;
++n;
}
}
if (n < 1)
return 0;
int retval = sendpacket(dht->net, ip_list[rand() % n], packet, length);
if ((unsigned int)retval == length)
return 1;
return 0;
}
/*----------------------------------------------------------------------------------*/
/*---------------------BEGINNING OF NAT PUNCHING FUNCTIONS--------------------------*/
static int send_NATping(DHT *dht, const uint8_t *public_key, uint64_t ping_id, uint8_t type)
{
uint8_t data[sizeof(uint64_t) + 1];
uint8_t packet[MAX_CRYPTO_REQUEST_SIZE];
int num = 0;
data[0] = type;
memcpy(data + 1, &ping_id, sizeof(uint64_t));
/* 254 is NAT ping request packet id */
int len = create_request(dht->self_public_key, dht->self_secret_key, packet, public_key, data,
sizeof(uint64_t) + 1, CRYPTO_PACKET_NAT_PING);
if (len == -1)
return -1;
if (type == 0) /* If packet is request use many people to route it. */
num = route_tofriend(dht, public_key, packet, len);
else if (type == 1) /* If packet is response use only one person to route it */
num = routeone_tofriend(dht, public_key, packet, len);
if (num == 0)
return -1;
return num;
}
/* Handle a received ping request for. */
static int handle_NATping(void *object, IP_Port source, const uint8_t *source_pubkey, const uint8_t *packet,
uint16_t length)
{
if (length != sizeof(uint64_t) + 1)
return 1;
DHT *dht = object;
uint64_t ping_id;
memcpy(&ping_id, packet + 1, sizeof(uint64_t));
int friendnumber = friend_number(dht, source_pubkey);
if (friendnumber == -1)
return 1;
DHT_Friend *friend = &dht->friends_list[friendnumber];
if (packet[0] == NAT_PING_REQUEST) {
/* 1 is reply */
send_NATping(dht, source_pubkey, ping_id, NAT_PING_RESPONSE);
friend->nat.recvNATping_timestamp = unix_time();
return 0;
} else if (packet[0] == NAT_PING_RESPONSE) {
if (friend->nat.NATping_id == ping_id) {
friend->nat.NATping_id = random_64b();
friend->nat.hole_punching = 1;
return 0;
}
}
return 1;
}
/* Get the most common ip in the ip_portlist.
* Only return ip if it appears in list min_num or more.
* len must not be bigger than MAX_FRIEND_CLIENTS.
*
* return ip of 0 if failure.
*/
static IP NAT_commonip(IP_Port *ip_portlist, uint16_t len, uint16_t min_num)
{
IP zero;
ip_reset(&zero);
if (len > MAX_FRIEND_CLIENTS)
return zero;
uint32_t i, j;
uint16_t numbers[MAX_FRIEND_CLIENTS] = {0};
for (i = 0; i < len; ++i) {
for (j = 0; j < len; ++j) {
if (ip_equal(&ip_portlist[i].ip, &ip_portlist[j].ip))
++numbers[i];
}
if (numbers[i] >= min_num)
return ip_portlist[i].ip;
}
return zero;
}
/* Return all the ports for one ip in a list.
* portlist must be at least len long,
* where len is the length of ip_portlist.
*
* return number of ports and puts the list of ports in portlist.
*/
static uint16_t NAT_getports(uint16_t *portlist, IP_Port *ip_portlist, uint16_t len, IP ip)
{
uint32_t i;
uint16_t num = 0;
for (i = 0; i < len; ++i) {
if (ip_equal(&ip_portlist[i].ip, &ip)) {
portlist[num] = ntohs(ip_portlist[i].port);
++num;
}
}
return num;
}
static void punch_holes(DHT *dht, IP ip, uint16_t *port_list, uint16_t numports, uint16_t friend_num)
{
if (numports > MAX_FRIEND_CLIENTS || numports == 0)
return;
uint32_t i;
uint32_t top = dht->friends_list[friend_num].nat.punching_index + MAX_PUNCHING_PORTS;
uint16_t firstport = port_list[0];
for (i = 0; i < numports; ++i) {
if (firstport != port_list[i])
break;
}
if (i == numports) { /* If all ports are the same, only try that one port. */
IP_Port pinging;
ip_copy(&pinging.ip, &ip);
pinging.port = htons(firstport);
send_ping_request(dht->ping, pinging, dht->friends_list[friend_num].public_key);
} else {
for (i = dht->friends_list[friend_num].nat.punching_index; i != top; ++i) {
/* TODO: Improve port guessing algorithm. */
uint16_t port = port_list[(i / 2) % numports] + (i / (2 * numports)) * ((i % 2) ? -1 : 1);
IP_Port pinging;
ip_copy(&pinging.ip, &ip);
pinging.port = htons(port);
send_ping_request(dht->ping, pinging, dht->friends_list[friend_num].public_key);
}
dht->friends_list[friend_num].nat.punching_index = i;
}
if (dht->friends_list[friend_num].nat.tries > MAX_NORMAL_PUNCHING_TRIES) {
top = dht->friends_list[friend_num].nat.punching_index2 + MAX_PUNCHING_PORTS;
uint16_t port = 1024;
IP_Port pinging;
ip_copy(&pinging.ip, &ip);
for (i = dht->friends_list[friend_num].nat.punching_index2; i != top; ++i) {
pinging.port = htons(port + i);
send_ping_request(dht->ping, pinging, dht->friends_list[friend_num].public_key);
}
dht->friends_list[friend_num].nat.punching_index2 = i - (MAX_PUNCHING_PORTS / 2);
}
++dht->friends_list[friend_num].nat.tries;
}
static void do_NAT(DHT *dht)
{
uint32_t i;
uint64_t temp_time = unix_time();
for (i = 0; i < dht->num_friends; ++i) {
IP_Port ip_list[MAX_FRIEND_CLIENTS];
int num = friend_iplist(dht, ip_list, i);
/* If already connected or friend is not online don't try to hole punch. */
if (num < MAX_FRIEND_CLIENTS / 2)
continue;
if (dht->friends_list[i].nat.NATping_timestamp + PUNCH_INTERVAL < temp_time) {
send_NATping(dht, dht->friends_list[i].public_key, dht->friends_list[i].nat.NATping_id, NAT_PING_REQUEST);
dht->friends_list[i].nat.NATping_timestamp = temp_time;
}
if (dht->friends_list[i].nat.hole_punching == 1 &&
dht->friends_list[i].nat.punching_timestamp + PUNCH_INTERVAL < temp_time &&
dht->friends_list[i].nat.recvNATping_timestamp + PUNCH_INTERVAL * 2 >= temp_time) {
IP ip = NAT_commonip(ip_list, num, MAX_FRIEND_CLIENTS / 2);
if (!ip_isset(&ip))
continue;
uint16_t port_list[MAX_FRIEND_CLIENTS];
uint16_t numports = NAT_getports(port_list, ip_list, num, ip);
punch_holes(dht, ip, port_list, numports, i);
dht->friends_list[i].nat.punching_timestamp = temp_time;
dht->friends_list[i].nat.hole_punching = 0;
}
}
}
/*----------------------------------------------------------------------------------*/
/*-----------------------END OF NAT PUNCHING FUNCTIONS------------------------------*/
#define HARDREQ_DATA_SIZE 384 /* Attempt to prevent amplification/other attacks*/
#define CHECK_TYPE_ROUTE_REQ 0
#define CHECK_TYPE_ROUTE_RES 1
#define CHECK_TYPE_GETNODE_REQ 2
#define CHECK_TYPE_GETNODE_RES 3
#define CHECK_TYPE_TEST_REQ 4
#define CHECK_TYPE_TEST_RES 5
static int send_hardening_req(DHT *dht, Node_format *sendto, uint8_t type, uint8_t *contents, uint16_t length)
{
if (length > HARDREQ_DATA_SIZE - 1)
return -1;
uint8_t packet[MAX_CRYPTO_REQUEST_SIZE];
uint8_t data[HARDREQ_DATA_SIZE] = {0};
data[0] = type;
memcpy(data + 1, contents, length);
int len = create_request(dht->self_public_key, dht->self_secret_key, packet, sendto->public_key, data,
sizeof(data), CRYPTO_PACKET_HARDENING);
if (len == -1)
return -1;
return sendpacket(dht->net, sendto->ip_port, packet, len);
}
/* Send a get node hardening request */
static int send_hardening_getnode_req(DHT *dht, Node_format *dest, Node_format *node_totest, uint8_t *search_id)
{
uint8_t data[sizeof(Node_format) + crypto_box_PUBLICKEYBYTES];
memcpy(data, node_totest, sizeof(Node_format));
memcpy(data + sizeof(Node_format), search_id, crypto_box_PUBLICKEYBYTES);
return send_hardening_req(dht, dest, CHECK_TYPE_GETNODE_REQ, data, sizeof(Node_format) + crypto_box_PUBLICKEYBYTES);
}
/* Send a get node hardening response */
static int send_hardening_getnode_res(const DHT *dht, const Node_format *sendto, const uint8_t *queried_client_id,
const uint8_t *nodes_data, uint16_t nodes_data_length)
{
if (!ip_isset(&sendto->ip_port.ip))
return -1;
uint8_t packet[MAX_CRYPTO_REQUEST_SIZE];
uint8_t data[1 + crypto_box_PUBLICKEYBYTES + nodes_data_length];
data[0] = CHECK_TYPE_GETNODE_RES;
memcpy(data + 1, queried_client_id, crypto_box_PUBLICKEYBYTES);
memcpy(data + 1 + crypto_box_PUBLICKEYBYTES, nodes_data, nodes_data_length);
int len = create_request(dht->self_public_key, dht->self_secret_key, packet, sendto->public_key, data,
sizeof(data), CRYPTO_PACKET_HARDENING);
if (len == -1)
return -1;
return sendpacket(dht->net, sendto->ip_port, packet, len);
}
/* TODO: improve */
static IPPTsPng *get_closelist_IPPTsPng(DHT *dht, const uint8_t *public_key, sa_family_t sa_family)
{
uint32_t i;
for (i = 0; i < LCLIENT_LIST; ++i) {
if (public_key_cmp(dht->close_clientlist[i].public_key, public_key) != 0)
continue;
if (sa_family == AF_INET)
return &dht->close_clientlist[i].assoc4;
else if (sa_family == AF_INET6)
return &dht->close_clientlist[i].assoc6;
}
return NULL;
}
/*
* check how many nodes in nodes are also present in the closelist.
* TODO: make this function better.
*/
static uint32_t have_nodes_closelist(DHT *dht, Node_format *nodes, uint16_t num)
{
uint32_t counter = 0;
uint32_t i;
for (i = 0; i < num; ++i) {
if (id_equal(nodes[i].public_key, dht->self_public_key)) {
++counter;
continue;
}
IPPTsPng *temp = get_closelist_IPPTsPng(dht, nodes[i].public_key, nodes[i].ip_port.ip.family);
if (temp) {
if (!is_timeout(temp->timestamp, BAD_NODE_TIMEOUT)) {
++counter;
}
}
}
return counter;
}
/* Interval in seconds between hardening checks */
#define HARDENING_INTERVAL 120
#define HARDEN_TIMEOUT 1200
/* Handle a received hardening packet */
static int handle_hardening(void *object, IP_Port source, const uint8_t *source_pubkey, const uint8_t *packet,
uint16_t length)
{
DHT *dht = object;
if (length < 2) {
return 1;
}
switch (packet[0]) {
case CHECK_TYPE_GETNODE_REQ: {
if (length != HARDREQ_DATA_SIZE)
return 1;
Node_format node, tocheck_node;
node.ip_port = source;
memcpy(node.public_key, source_pubkey, crypto_box_PUBLICKEYBYTES);
memcpy(&tocheck_node, packet + 1, sizeof(Node_format));
if (getnodes(dht, tocheck_node.ip_port, tocheck_node.public_key, packet + 1 + sizeof(Node_format), &node) == -1)
return 1;
return 0;
}
case CHECK_TYPE_GETNODE_RES: {
if (length <= crypto_box_PUBLICKEYBYTES + 1)
return 1;
if (length > 1 + crypto_box_PUBLICKEYBYTES + sizeof(Node_format) * MAX_SENT_NODES)
return 1;
uint16_t length_nodes = length - 1 - crypto_box_PUBLICKEYBYTES;
Node_format nodes[MAX_SENT_NODES];
int num_nodes = unpack_nodes(nodes, MAX_SENT_NODES, 0, packet + 1 + crypto_box_PUBLICKEYBYTES, length_nodes, 0);
/* TODO: MAX_SENT_NODES nodes should be returned at all times
(right now we have a small network size so it could cause problems for testing and etc..) */
if (num_nodes <= 0)
return 1;
/* NOTE: This should work for now but should be changed to something better. */
if (have_nodes_closelist(dht, nodes, num_nodes) < (uint32_t)((num_nodes + 2) / 2))
return 1;
IPPTsPng *temp = get_closelist_IPPTsPng(dht, packet + 1, nodes[0].ip_port.ip.family);
if (temp == NULL)
return 1;
if (is_timeout(temp->hardening.send_nodes_timestamp, HARDENING_INTERVAL))
return 1;
if (public_key_cmp(temp->hardening.send_nodes_pingedid, source_pubkey) != 0)
return 1;
/* If Nodes look good and the request checks out */
temp->hardening.send_nodes_ok = 1;
return 0;/* success*/
}
}
return 1;
}
/* Return a random node from all the nodes we are connected to.
* TODO: improve this function.
*/
Node_format random_node(DHT *dht, sa_family_t sa_family)
{
uint8_t id[crypto_box_PUBLICKEYBYTES];
uint32_t i;
for (i = 0; i < crypto_box_PUBLICKEYBYTES / 4; ++i) { /* populate the id with pseudorandom bytes.*/
uint32_t t = rand();
memcpy(id + i * sizeof(t), &t, sizeof(t));
}
Node_format nodes_list[MAX_SENT_NODES];
memset(nodes_list, 0, sizeof(nodes_list));
uint32_t num_nodes = get_close_nodes(dht, id, nodes_list, sa_family, 1, 0);
if (num_nodes == 0)
return nodes_list[0];
else
return nodes_list[rand() % num_nodes];
}
/* Put up to max_num nodes in nodes from the closelist.
*
* return the number of nodes.
*/
uint16_t list_nodes(Client_data *list, unsigned int length, Node_format *nodes, uint16_t max_num)
{
if (max_num == 0)
return 0;
uint16_t count = 0;
unsigned int i;
for (i = length; i != 0; --i) {
IPPTsPng *assoc = NULL;
if (!is_timeout(list[i - 1].assoc4.timestamp, BAD_NODE_TIMEOUT))
assoc = &list[i - 1].assoc4;
if (!is_timeout(list[i - 1].assoc6.timestamp, BAD_NODE_TIMEOUT)) {
if (assoc == NULL)
assoc = &list[i - 1].assoc6;
else if (rand() % 2)
assoc = &list[i - 1].assoc6;
}
if (assoc != NULL) {
memcpy(nodes[count].public_key, list[i - 1].public_key, crypto_box_PUBLICKEYBYTES);
nodes[count].ip_port = assoc->ip_port;
++count;
if (count >= max_num)
return count;
}
}
return count;
}
/* Put up to max_num nodes in nodes from the random friends.
*
* return the number of nodes.
*/
uint16_t randfriends_nodes(DHT *dht, Node_format *nodes, uint16_t max_num)
{
if (max_num == 0)
return 0;
uint16_t count = 0;
unsigned int i, r = rand();
for (i = 0; i < DHT_FAKE_FRIEND_NUMBER; ++i) {
count += list_nodes(dht->friends_list[(i + r) % DHT_FAKE_FRIEND_NUMBER].client_list, MAX_FRIEND_CLIENTS, nodes + count,
max_num - count);
if (count >= max_num)
break;
}
return count;
}
/* Put up to max_num nodes in nodes from the closelist.
*
* return the number of nodes.
*/
uint16_t closelist_nodes(DHT *dht, Node_format *nodes, uint16_t max_num)
{
return list_nodes(dht->close_clientlist, LCLIENT_LIST, nodes, max_num);
}
void do_hardening(DHT *dht)
{
uint32_t i;
for (i = 0; i < LCLIENT_LIST * 2; ++i) {
IPPTsPng *cur_iptspng;
sa_family_t sa_family;
uint8_t *public_key = dht->close_clientlist[i / 2].public_key;
if (i % 2 == 0) {
cur_iptspng = &dht->close_clientlist[i / 2].assoc4;
sa_family = AF_INET;
} else {
cur_iptspng = &dht->close_clientlist[i / 2].assoc6;
sa_family = AF_INET6;
}
if (is_timeout(cur_iptspng->timestamp, BAD_NODE_TIMEOUT))
continue;
if (cur_iptspng->hardening.send_nodes_ok == 0) {
if (is_timeout(cur_iptspng->hardening.send_nodes_timestamp, HARDENING_INTERVAL)) {
Node_format rand_node = random_node(dht, sa_family);
if (!ipport_isset(&rand_node.ip_port))
continue;
if (id_equal(public_key, rand_node.public_key))
continue;
Node_format to_test;
to_test.ip_port = cur_iptspng->ip_port;
memcpy(to_test.public_key, public_key, crypto_box_PUBLICKEYBYTES);
//TODO: The search id should maybe not be ours?
if (send_hardening_getnode_req(dht, &rand_node, &to_test, dht->self_public_key) > 0) {
memcpy(cur_iptspng->hardening.send_nodes_pingedid, rand_node.public_key, crypto_box_PUBLICKEYBYTES);
cur_iptspng->hardening.send_nodes_timestamp = unix_time();
}
}
} else {
if (is_timeout(cur_iptspng->hardening.send_nodes_timestamp, HARDEN_TIMEOUT)) {
cur_iptspng->hardening.send_nodes_ok = 0;
}
}
//TODO: add the 2 other testers.
}
}
/*----------------------------------------------------------------------------------*/
void cryptopacket_registerhandler(DHT *dht, uint8_t byte, cryptopacket_handler_callback cb, void *object)
{
dht->cryptopackethandlers[byte].function = cb;
dht->cryptopackethandlers[byte].object = object;
}
static int cryptopacket_handle(void *object, IP_Port source, const uint8_t *packet, uint16_t length)
{
DHT *dht = object;
if (packet[0] == NET_PACKET_CRYPTO) {
if (length <= crypto_box_PUBLICKEYBYTES * 2 + crypto_box_NONCEBYTES + 1 + crypto_box_MACBYTES ||
length > MAX_CRYPTO_REQUEST_SIZE + crypto_box_MACBYTES)
return 1;
if (public_key_cmp(packet + 1, dht->self_public_key) == 0) { // Check if request is for us.
uint8_t public_key[crypto_box_PUBLICKEYBYTES];
uint8_t data[MAX_CRYPTO_REQUEST_SIZE];
uint8_t number;
int len = handle_request(dht->self_public_key, dht->self_secret_key, public_key, data, &number, packet, length);
if (len == -1 || len == 0)
return 1;
if (!dht->cryptopackethandlers[number].function) return 1;
return dht->cryptopackethandlers[number].function(dht->cryptopackethandlers[number].object, source, public_key,
data, len);
} else { /* If request is not for us, try routing it. */
int retval = route_packet(dht, packet + 1, packet, length);
if ((unsigned int)retval == length)
return 0;
}
}
return 1;
}
/*----------------------------------------------------------------------------------*/
DHT *new_DHT(Networking_Core *net)
{
/* init time */
unix_time_update();
if (net == NULL)
return NULL;
DHT *dht = calloc(1, sizeof(DHT));
if (dht == NULL)
return NULL;
dht->net = net;
dht->ping = new_ping(dht);
if (dht->ping == NULL) {
kill_DHT(dht);
return NULL;
}
networking_registerhandler(dht->net, NET_PACKET_GET_NODES, &handle_getnodes, dht);
networking_registerhandler(dht->net, NET_PACKET_SEND_NODES_IPV6, &handle_sendnodes_ipv6, dht);
networking_registerhandler(dht->net, NET_PACKET_CRYPTO, &cryptopacket_handle, dht);
cryptopacket_registerhandler(dht, CRYPTO_PACKET_NAT_PING, &handle_NATping, dht);
cryptopacket_registerhandler(dht, CRYPTO_PACKET_HARDENING, &handle_hardening, dht);
new_symmetric_key(dht->secret_symmetric_key);
crypto_box_keypair(dht->self_public_key, dht->self_secret_key);
ping_array_init(&dht->dht_ping_array, DHT_PING_ARRAY_SIZE, PING_TIMEOUT);
ping_array_init(&dht->dht_harden_ping_array, DHT_PING_ARRAY_SIZE, PING_TIMEOUT);
#ifdef ENABLE_ASSOC_DHT
dht->assoc = new_Assoc_default(dht->self_public_key);
#endif
uint32_t i;
for (i = 0; i < DHT_FAKE_FRIEND_NUMBER; ++i) {
uint8_t random_key_bytes[crypto_box_PUBLICKEYBYTES];
randombytes(random_key_bytes, sizeof(random_key_bytes));
if (DHT_addfriend(dht, random_key_bytes, 0, 0, 0, 0) != 0) {
kill_DHT(dht);
return NULL;
}
}
return dht;
}
void do_DHT(DHT *dht)
{
unix_time_update();
if (dht->last_run == unix_time()) {
return;
}
// Load friends/clients if first call to do_DHT
if (dht->loaded_num_nodes) {
DHT_connect_after_load(dht);
}
do_Close(dht);
do_DHT_friends(dht);
do_NAT(dht);
do_to_ping(dht->ping);
//do_hardening(dht);
#ifdef ENABLE_ASSOC_DHT
if (dht->assoc)
do_Assoc(dht->assoc, dht);
#endif
dht->last_run = unix_time();
}
void kill_DHT(DHT *dht)
{
#ifdef ENABLE_ASSOC_DHT
kill_Assoc(dht->assoc);
#endif
networking_registerhandler(dht->net, NET_PACKET_GET_NODES, NULL, NULL);
networking_registerhandler(dht->net, NET_PACKET_SEND_NODES_IPV6, NULL, NULL);
cryptopacket_registerhandler(dht, CRYPTO_PACKET_NAT_PING, NULL, NULL);
cryptopacket_registerhandler(dht, CRYPTO_PACKET_HARDENING, NULL, NULL);
ping_array_free_all(&dht->dht_ping_array);
ping_array_free_all(&dht->dht_harden_ping_array);
kill_ping(dht->ping);
free(dht->friends_list);
free(dht->loaded_nodes_list);
free(dht);
}
/* new DHT format for load/save, more robust and forward compatible */
//TODO: Move this closer to Messenger.
#define DHT_STATE_COOKIE_GLOBAL 0x159000d
#define DHT_STATE_COOKIE_TYPE 0x11ce
#define DHT_STATE_TYPE_NODES 4
#define MAX_SAVED_DHT_NODES (((DHT_FAKE_FRIEND_NUMBER * MAX_FRIEND_CLIENTS) + LCLIENT_LIST) * 2)
/* Get the size of the DHT (for saving). */
uint32_t DHT_size(const DHT *dht)
{
uint32_t numv4 = 0, numv6 = 0, i, j;
for (i = 0; i < LCLIENT_LIST; ++i) {
numv4 += (dht->close_clientlist[i].assoc4.timestamp != 0);
numv6 += (dht->close_clientlist[i].assoc6.timestamp != 0);
}
for (i = 0; i < DHT_FAKE_FRIEND_NUMBER && i < dht->num_friends; ++i) {
DHT_Friend *fr = &dht->friends_list[i];
for (j = 0; j < MAX_FRIEND_CLIENTS; ++j) {
numv4 += (fr->client_list[j].assoc4.timestamp != 0);
numv6 += (fr->client_list[j].assoc6.timestamp != 0);
}
}
uint32_t size32 = sizeof(uint32_t), sizesubhead = size32 * 2;
return size32 + sizesubhead + (packed_node_size(AF_INET) * numv4) + (packed_node_size(AF_INET6) * numv6);
}
static uint8_t *z_state_save_subheader(uint8_t *data, uint32_t len, uint16_t type)
{
host_to_lendian32(data, len);
data += sizeof(uint32_t);
host_to_lendian32(data, (host_tolendian16(DHT_STATE_COOKIE_TYPE) << 16) | host_tolendian16(type));
data += sizeof(uint32_t);
return data;
}
/* Save the DHT in data where data is an array of size DHT_size(). */
void DHT_save(DHT *dht, uint8_t *data)
{
host_to_lendian32(data, DHT_STATE_COOKIE_GLOBAL);
data += sizeof(uint32_t);
uint32_t num, i, j;
uint8_t *old_data = data;
/* get right offset. we write the actual header later. */
data = z_state_save_subheader(data, 0, 0);
Node_format clients[MAX_SAVED_DHT_NODES];
for (num = 0, i = 0; i < LCLIENT_LIST; ++i) {
if (dht->close_clientlist[i].assoc4.timestamp != 0) {
memcpy(clients[num].public_key, dht->close_clientlist[i].public_key, crypto_box_PUBLICKEYBYTES);
clients[num].ip_port = dht->close_clientlist[i].assoc4.ip_port;
++num;
}
if (dht->close_clientlist[i].assoc6.timestamp != 0) {
memcpy(clients[num].public_key, dht->close_clientlist[i].public_key, crypto_box_PUBLICKEYBYTES);
clients[num].ip_port = dht->close_clientlist[i].assoc6.ip_port;
++num;
}
}
for (i = 0; i < DHT_FAKE_FRIEND_NUMBER && i < dht->num_friends; ++i) {
DHT_Friend *fr = &dht->friends_list[i];
for (j = 0; j < MAX_FRIEND_CLIENTS; ++j) {
if (fr->client_list[j].assoc4.timestamp != 0) {
memcpy(clients[num].public_key, fr->client_list[j].public_key, crypto_box_PUBLICKEYBYTES);
clients[num].ip_port = fr->client_list[j].assoc4.ip_port;
++num;
}
if (fr->client_list[j].assoc6.timestamp != 0) {
memcpy(clients[num].public_key, fr->client_list[j].public_key, crypto_box_PUBLICKEYBYTES);
clients[num].ip_port = fr->client_list[j].assoc6.ip_port;
++num;
}
}
}
z_state_save_subheader(old_data, pack_nodes(data, sizeof(Node_format) * num, clients, num), DHT_STATE_TYPE_NODES);
}
/* Bootstrap from this number of nodes every time DHT_connect_after_load() is called */
#define SAVE_BOOTSTAP_FREQUENCY 8
/* Start sending packets after DHT loaded_friends_list and loaded_clients_list are set */
int DHT_connect_after_load(DHT *dht)
{
if (dht == NULL)
return -1;
if (!dht->loaded_nodes_list)
return -1;
/* DHT is connected, stop. */
if (DHT_non_lan_connected(dht)) {
free(dht->loaded_nodes_list);
dht->loaded_nodes_list = NULL;
dht->loaded_num_nodes = 0;
return 0;
}
unsigned int i;
for (i = 0; i < dht->loaded_num_nodes && i < SAVE_BOOTSTAP_FREQUENCY; ++i) {
unsigned int index = dht->loaded_nodes_index % dht->loaded_num_nodes;
DHT_bootstrap(dht, dht->loaded_nodes_list[index].ip_port, dht->loaded_nodes_list[index].public_key);
++dht->loaded_nodes_index;
}
return 0;
}
static int dht_load_state_callback(void *outer, const uint8_t *data, uint32_t length, uint16_t type)
{
DHT *dht = outer;
switch (type) {
case DHT_STATE_TYPE_NODES:
if (length == 0)
break;
{
free(dht->loaded_nodes_list);
// Copy to loaded_clients_list
dht->loaded_nodes_list = calloc(MAX_SAVED_DHT_NODES, sizeof(Node_format));
int num = unpack_nodes(dht->loaded_nodes_list, MAX_SAVED_DHT_NODES, NULL, data, length, 0);
if (num > 0) {
dht->loaded_num_nodes = num;
} else {
dht->loaded_num_nodes = 0;
}
} /* localize declarations */
break;
#ifdef DEBUG
default:
fprintf(stderr, "Load state (DHT): contains unrecognized part (len %u, type %u)\n",
length, type);
break;
#endif
}
return 0;
}
/* Load the DHT from data of size size.
*
* return -1 if failure.
* return 0 if success.
*/
int DHT_load(DHT *dht, const uint8_t *data, uint32_t length)
{
uint32_t cookie_len = sizeof(uint32_t);
if (length > cookie_len) {
uint32_t data32;
lendian_to_host32(&data32, data);
if (data32 == DHT_STATE_COOKIE_GLOBAL)
return load_state(dht_load_state_callback, dht, data + cookie_len,
length - cookie_len, DHT_STATE_COOKIE_TYPE);
}
return -1;
}
/* return 0 if we are not connected to the DHT.
* return 1 if we are.
*/
int DHT_isconnected(const DHT *dht)
{
uint32_t i;
unix_time_update();
for (i = 0; i < LCLIENT_LIST; ++i) {
const Client_data *client = &dht->close_clientlist[i];
if (!is_timeout(client->assoc4.timestamp, BAD_NODE_TIMEOUT) ||
!is_timeout(client->assoc6.timestamp, BAD_NODE_TIMEOUT))
return 1;
}
return 0;
}
/* return 0 if we are not connected or only connected to lan peers with the DHT.
* return 1 if we are.
*/
int DHT_non_lan_connected(const DHT *dht)
{
uint32_t i;
unix_time_update();
for (i = 0; i < LCLIENT_LIST; ++i) {
const Client_data *client = &dht->close_clientlist[i];
if (!is_timeout(client->assoc4.timestamp, BAD_NODE_TIMEOUT) && LAN_ip(client->assoc4.ip_port.ip) == -1)
return 1;
if (!is_timeout(client->assoc6.timestamp, BAD_NODE_TIMEOUT) && LAN_ip(client->assoc6.ip_port.ip) == -1)
return 1;
}
return 0;
}
================================================
FILE: toxcore/DHT.h
================================================
/* DHT.h
*
* An implementation of the DHT as seen in docs/updates/DHT.md
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef DHT_H
#define DHT_H
#include "crypto_core.h"
#include "network.h"
#include "ping_array.h"
/* Maximum number of clients stored per friend. */
#define MAX_FRIEND_CLIENTS 8
#define LCLIENT_NODES (MAX_FRIEND_CLIENTS)
#define LCLIENT_LENGTH 128
/* A list of the clients mathematically closest to ours. */
#define LCLIENT_LIST (LCLIENT_LENGTH * LCLIENT_NODES)
#define MAX_CLOSE_TO_BOOTSTRAP_NODES 8
/* The max number of nodes to send with send nodes. */
#define MAX_SENT_NODES 4
/* Ping timeout in seconds */
#define PING_TIMEOUT 5
/* size of DHT ping arrays. */
#define DHT_PING_ARRAY_SIZE 512
/* Ping interval in seconds for each node in our lists. */
#define PING_INTERVAL 60
/* The number of seconds for a non responsive node to become bad. */
#define PINGS_MISSED_NODE_GOES_BAD 1
#define PING_ROUNDTRIP 2
#define BAD_NODE_TIMEOUT (PING_INTERVAL + PINGS_MISSED_NODE_GOES_BAD * (PING_INTERVAL + PING_ROUNDTRIP))
/* Redefinitions of variables for safe transfer over wire. */
#define TOX_AF_INET 2
#define TOX_AF_INET6 10
#define TOX_TCP_INET 130
#define TOX_TCP_INET6 138
/* The number of "fake" friends to add (for optimization purposes and so our paths for the onion part are more random) */
#define DHT_FAKE_FRIEND_NUMBER 2
/* Functions to transfer ips safely across wire. */
void to_net_family(IP *ip);
/* return 0 on success, -1 on failure. */
int to_host_family(IP *ip);
typedef struct {
IP_Port ip_port;
uint64_t timestamp;
} IPPTs;
typedef struct {
/* Node routes request correctly (true (1) or false/didn't check (0)) */
uint8_t routes_requests_ok;
/* Time which we last checked this.*/
uint64_t routes_requests_timestamp;
uint8_t routes_requests_pingedid[crypto_box_PUBLICKEYBYTES];
/* Node sends correct send_node (true (1) or false/didn't check (0)) */
uint8_t send_nodes_ok;
/* Time which we last checked this.*/
uint64_t send_nodes_timestamp;
uint8_t send_nodes_pingedid[crypto_box_PUBLICKEYBYTES];
/* Node can be used to test other nodes (true (1) or false/didn't check (0)) */
uint8_t testing_requests;
/* Time which we last checked this.*/
uint64_t testing_timestamp;
uint8_t testing_pingedid[crypto_box_PUBLICKEYBYTES];
} Hardening;
typedef struct {
IP_Port ip_port;
uint64_t timestamp;
uint64_t last_pinged;
Hardening hardening;
/* Returned by this node. Either our friend or us. */
IP_Port ret_ip_port;
uint64_t ret_timestamp;
} IPPTsPng;
typedef struct {
uint8_t public_key[crypto_box_PUBLICKEYBYTES];
IPPTsPng assoc4;
IPPTsPng assoc6;
} Client_data;
/*----------------------------------------------------------------------------------*/
typedef struct {
/* 1 if currently hole punching, otherwise 0 */
uint8_t hole_punching;
uint32_t punching_index;
uint32_t tries;
uint32_t punching_index2;
uint64_t punching_timestamp;
uint64_t recvNATping_timestamp;
uint64_t NATping_id;
uint64_t NATping_timestamp;
} NAT;
#define DHT_FRIEND_MAX_LOCKS 32
typedef struct {
uint8_t public_key[crypto_box_PUBLICKEYBYTES];
IP_Port ip_port;
}
Node_format;
typedef struct {
uint8_t public_key[crypto_box_PUBLICKEYBYTES];
Client_data client_list[MAX_FRIEND_CLIENTS];
/* Time at which the last get_nodes request was sent. */
uint64_t lastgetnode;
/* number of times get_node packets were sent. */
uint32_t bootstrap_times;
/* Symetric NAT hole punching stuff. */
NAT nat;
uint16_t lock_count;
struct {
void (*ip_callback)(void *, int32_t, IP_Port);
void *data;
int32_t number;
} callbacks[DHT_FRIEND_MAX_LOCKS];
Node_format to_bootstrap[MAX_SENT_NODES];
unsigned int num_to_bootstrap;
} DHT_Friend;
/* Return packet size of packed node with ip_family on success.
* Return -1 on failure.
*/
int packed_node_size(uint8_t ip_family);
/* Pack number of nodes into data of maxlength length.
*
* return length of packed nodes on success.
* return -1 on failure.
*/
int pack_nodes(uint8_t *data, uint16_t length, const Node_format *nodes, uint16_t number);
/* Unpack data of length into nodes of size max_num_nodes.
* Put the length of the data processed in processed_data_len.
* tcp_enabled sets if TCP nodes are expected (true) or not (false).
*
* return number of unpacked nodes on success.
* return -1 on failure.
*/
int unpack_nodes(Node_format *nodes, uint16_t max_num_nodes, uint16_t *processed_data_len, const uint8_t *data,
uint16_t length, uint8_t tcp_enabled);
/*----------------------------------------------------------------------------------*/
/* struct to store some shared keys so we don't have to regenerate them for each request. */
#define MAX_KEYS_PER_SLOT 4
#define KEYS_TIMEOUT 600
typedef struct {
struct {
uint8_t public_key[crypto_box_PUBLICKEYBYTES];
uint8_t shared_key[crypto_box_BEFORENMBYTES];
uint32_t times_requested;
uint8_t stored; /* 0 if not, 1 if is */
uint64_t time_last_requested;
} keys[256 * MAX_KEYS_PER_SLOT];
} Shared_Keys;
/*----------------------------------------------------------------------------------*/
typedef int (*cryptopacket_handler_callback)(void *object, IP_Port ip_port, const uint8_t *source_pubkey,
const uint8_t *data, uint16_t len);
typedef struct {
cryptopacket_handler_callback function;
void *object;
} Cryptopacket_Handles;
typedef struct {
Networking_Core *net;
Client_data close_clientlist[LCLIENT_LIST];
uint64_t close_lastgetnodes;
uint32_t close_bootstrap_times;
/* Note: this key should not be/is not used to transmit any sensitive materials */
uint8_t secret_symmetric_key[crypto_box_KEYBYTES];
/* DHT keypair */
uint8_t self_public_key[crypto_box_PUBLICKEYBYTES];
uint8_t self_secret_key[crypto_box_SECRETKEYBYTES];
DHT_Friend *friends_list;
uint16_t num_friends;
Node_format *loaded_nodes_list;
uint32_t loaded_num_nodes;
unsigned int loaded_nodes_index;
Shared_Keys shared_keys_recv;
Shared_Keys shared_keys_sent;
struct PING *ping;
Ping_Array dht_ping_array;
Ping_Array dht_harden_ping_array;
#ifdef ENABLE_ASSOC_DHT
struct Assoc *assoc;
#endif
uint64_t last_run;
Cryptopacket_Handles cryptopackethandlers[256];
Node_format to_bootstrap[MAX_CLOSE_TO_BOOTSTRAP_NODES];
unsigned int num_to_bootstrap;
} DHT;
/*----------------------------------------------------------------------------------*/
/* Shared key generations are costly, it is therefor smart to store commonly used
* ones so that they can re used later without being computed again.
*
* If shared key is already in shared_keys, copy it to shared_key.
* else generate it into shared_key and copy it to shared_keys
*/
void get_shared_key(Shared_Keys *shared_keys, uint8_t *shared_key, const uint8_t *secret_key,
const uint8_t *public_key);
/* Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key
* for packets that we receive.
*/
void DHT_get_shared_key_recv(DHT *dht, uint8_t *shared_key, const uint8_t *public_key);
/* Copy shared_key to encrypt/decrypt DHT packet from public_key into shared_key
* for packets that we send.
*/
void DHT_get_shared_key_sent(DHT *dht, uint8_t *shared_key, const uint8_t *public_key);
void DHT_getnodes(DHT *dht, const IP_Port *from_ipp, const uint8_t *from_id, const uint8_t *which_id);
/* Add a new friend to the friends list.
* public_key must be crypto_box_PUBLICKEYBYTES bytes long.
*
* ip_callback is the callback of a function that will be called when the ip address
* is found along with arguments data and number.
*
* lock_count will be set to a non zero number that must be passed to DHT_delfriend()
* to properly remove the callback.
*
* return 0 if success.
* return -1 if failure (friends list is full).
*/
int DHT_addfriend(DHT *dht, const uint8_t *public_key, void (*ip_callback)(void *data, int32_t number, IP_Port),
void *data, int32_t number, uint16_t *lock_count);
/* Delete a friend from the friends list.
* public_key must be crypto_box_PUBLICKEYBYTES bytes long.
*
* return 0 if success.
* return -1 if failure (public_key not in friends list).
*/
int DHT_delfriend(DHT *dht, const uint8_t *public_key, uint16_t lock_count);
/* Get ip of friend.
* public_key must be crypto_box_PUBLICKEYBYTES bytes long.
* ip must be 4 bytes long.
* port must be 2 bytes long.
*
* int DHT_getfriendip(DHT *dht, uint8_t *public_key, IP_Port *ip_port);
*
* return -1, -- if public_key does NOT refer to a friend
* return 0, -- if public_key refers to a friend and we failed to find the friend (yet)
* return 1, ip if public_key refers to a friend and we found him
*/
int DHT_getfriendip(const DHT *dht, const uint8_t *public_key, IP_Port *ip_port);
/* Compares pk1 and pk2 with pk.
*
* return 0 if both are same distance.
* return 1 if pk1 is closer.
* return 2 if pk2 is closer.
*/
int id_closest(const uint8_t *pk, const uint8_t *pk1, const uint8_t *pk2);
/* Add node to the node list making sure only the nodes closest to cmp_pk are in the list.
*/
_Bool add_to_list(Node_format *nodes_list, unsigned int length, const uint8_t *pk, IP_Port ip_port,
const uint8_t *cmp_pk);
/* Return 1 if node can be added to close list, 0 if it can't.
*/
_Bool node_addable_to_close_list(DHT *dht, const uint8_t *public_key, IP_Port ip_port);
/* Get the (maximum MAX_SENT_NODES) closest nodes to public_key we know
* and put them in nodes_list (must be MAX_SENT_NODES big).
*
* sa_family = family (IPv4 or IPv6) (0 if we don't care)?
* is_LAN = return some LAN ips (true or false)
* want_good = do we want tested nodes or not? (TODO)
*
* return the number of nodes returned.
*
*/
int get_close_nodes(const DHT *dht, const uint8_t *public_key, Node_format *nodes_list, sa_family_t sa_family,
uint8_t is_LAN, uint8_t want_good);
/* Put up to max_num nodes in nodes from the random friends.
*
* return the number of nodes.
*/
uint16_t randfriends_nodes(DHT *dht, Node_format *nodes, uint16_t max_num);
/* Put up to max_num nodes in nodes from the closelist.
*
* return the number of nodes.
*/
uint16_t closelist_nodes(DHT *dht, Node_format *nodes, uint16_t max_num);
/* Run this function at least a couple times per second (It's the main loop). */
void do_DHT(DHT *dht);
/*
* Use these two functions to bootstrap the client.
*/
/* Sends a "get nodes" request to the given node with ip, port and public_key
* to setup connections
*/
void DHT_bootstrap(DHT *dht, IP_Port ip_port, const uint8_t *public_key);
/* Resolves address into an IP address. If successful, sends a "get nodes"
* request to the given node with ip, port and public_key to setup connections
*
* address can be a hostname or an IP address (IPv4 or IPv6).
* if ipv6enabled is 0 (zero), the resolving sticks STRICTLY to IPv4 addresses
* if ipv6enabled is not 0 (zero), the resolving looks for IPv6 addresses first,
* then IPv4 addresses.
*
* returns 1 if the address could be converted into an IP address
* returns 0 otherwise
*/
int DHT_bootstrap_from_address(DHT *dht, const char *address, uint8_t ipv6enabled,
uint16_t port, const uint8_t *public_key);
/* Start sending packets after DHT loaded_friends_list and loaded_clients_list are set.
*
* returns 0 if successful
* returns -1 otherwise
*/
int DHT_connect_after_load(DHT *dht);
/* ROUTING FUNCTIONS */
/* Send the given packet to node with public_key.
*
* return -1 if failure.
*/
int route_packet(const DHT *dht, const uint8_t *public_key, const uint8_t *packet, uint16_t length);
/* Send the following packet to everyone who tells us they are connected to friend_id.
*
* return number of nodes it sent the packet to.
*/
int route_tofriend(const DHT *dht, const uint8_t *friend_id, const uint8_t *packet, uint16_t length);
/* Function to handle crypto packets.
*/
void cryptopacket_registerhandler(DHT *dht, uint8_t byte, cryptopacket_handler_callback cb, void *object);
/* SAVE/LOAD functions */
/* Get the size of the DHT (for saving). */
uint32_t DHT_size(const DHT *dht);
/* Save the DHT in data where data is an array of size DHT_size(). */
void DHT_save(DHT *dht, uint8_t *data);
/* Load the DHT from data of size size.
*
* return -1 if failure.
* return 0 if success.
*/
int DHT_load(DHT *dht, const uint8_t *data, uint32_t length);
/* Initialize DHT. */
DHT *new_DHT(Networking_Core *net);
void kill_DHT(DHT *dht);
/* return 0 if we are not connected to the DHT.
* return 1 if we are.
*/
int DHT_isconnected(const DHT *dht);
/* return 0 if we are not connected or only connected to lan peers with the DHT.
* return 1 if we are.
*/
int DHT_non_lan_connected(const DHT *dht);
int addto_lists(DHT *dht, IP_Port ip_port, const uint8_t *public_key);
#endif
================================================
FILE: toxcore/LAN_discovery.c
================================================
/* LAN_discovery.c
*
* LAN discovery implementation.
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "LAN_discovery.h"
#include "util.h"
/* Used for get_broadcast(). */
#ifdef __linux
#include
#include
#include
#endif
#define MAX_INTERFACES 16
static int broadcast_count = -1;
static IP_Port broadcast_ip_port[MAX_INTERFACES];
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
#include
static void fetch_broadcast_info(uint16_t port)
{
broadcast_count = 0;
IP_ADAPTER_INFO *pAdapterInfo = malloc(sizeof(IP_ADAPTER_INFO));
unsigned long ulOutBufLen = sizeof(IP_ADAPTER_INFO);
if (pAdapterInfo == NULL) {
return;
}
if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
free(pAdapterInfo);
pAdapterInfo = malloc(ulOutBufLen);
if (pAdapterInfo == NULL) {
return;
}
}
int ret;
if ((ret = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) {
IP_ADAPTER_INFO *pAdapter = pAdapterInfo;
while (pAdapter) {
IP gateway = {0}, subnet_mask = {0};
if (addr_parse_ip(pAdapter->IpAddressList.IpMask.String, &subnet_mask)
&& addr_parse_ip(pAdapter->GatewayList.IpAddress.String, &gateway)) {
if (gateway.family == AF_INET && subnet_mask.family == AF_INET) {
IP_Port *ip_port = &broadcast_ip_port[broadcast_count];
ip_port->ip.family = AF_INET;
uint32_t gateway_ip = ntohl(gateway.ip4.uint32), subnet_ip = ntohl(subnet_mask.ip4.uint32);
uint32_t broadcast_ip = gateway_ip + ~subnet_ip - 1;
ip_port->ip.ip4.uint32 = htonl(broadcast_ip);
ip_port->port = port;
broadcast_count++;
if (broadcast_count >= MAX_INTERFACES) {
return;
}
}
}
pAdapter = pAdapter->Next;
}
}
if (pAdapterInfo) {
free(pAdapterInfo);
}
}
#elif defined(__linux__)
static void fetch_broadcast_info(uint16_t port)
{
/* Not sure how many platforms this will run on,
* so it's wrapped in __linux for now.
* Definitely won't work like this on Windows...
*/
broadcast_count = 0;
sock_t sock = 0;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return;
/* Configure ifconf for the ioctl call. */
struct ifreq i_faces[MAX_INTERFACES];
memset(i_faces, 0, sizeof(struct ifreq) * MAX_INTERFACES);
struct ifconf ifconf;
ifconf.ifc_buf = (char *)i_faces;
ifconf.ifc_len = sizeof(i_faces);
if (ioctl(sock, SIOCGIFCONF, &ifconf) < 0) {
close(sock);
return;
}
/* ifconf.ifc_len is set by the ioctl() to the actual length used;
* on usage of the complete array the call should be repeated with
* a larger array, not done (640kB and 16 interfaces shall be
* enough, for everybody!)
*/
int i, count = ifconf.ifc_len / sizeof(struct ifreq);
for (i = 0; i < count; i++) {
/* there are interfaces with are incapable of broadcast */
if (ioctl(sock, SIOCGIFBRDADDR, &i_faces[i]) < 0)
continue;
/* moot check: only AF_INET returned (backwards compat.) */
if (i_faces[i].ifr_broadaddr.sa_family != AF_INET)
continue;
struct sockaddr_in *sock4 = (struct sockaddr_in *)&i_faces[i].ifr_broadaddr;
if (broadcast_count >= MAX_INTERFACES) {
close(sock);
return;
}
IP_Port *ip_port = &broadcast_ip_port[broadcast_count];
ip_port->ip.family = AF_INET;
ip_port->ip.ip4.in_addr = sock4->sin_addr;
if (ip_port->ip.ip4.uint32 == 0) {
continue;
}
ip_port->port = port;
broadcast_count++;
}
close(sock);
}
#else //TODO: Other platforms?
static void fetch_broadcast_info(uint16_t port)
{
broadcast_count = 0;
}
#endif
/* Send packet to all IPv4 broadcast addresses
*
* return 1 if sent to at least one broadcast target.
* return 0 on failure to find any valid broadcast target.
*/
static uint32_t send_broadcasts(Networking_Core *net, uint16_t port, const uint8_t *data, uint16_t length)
{
/* fetch only once? on every packet? every X seconds?
* old: every packet, new: once */
if (broadcast_count < 0)
fetch_broadcast_info(port);
if (!broadcast_count)
return 0;
int i;
for (i = 0; i < broadcast_count; i++)
sendpacket(net, broadcast_ip_port[i], data, length);
return 1;
}
/* Return the broadcast ip. */
static IP broadcast_ip(sa_family_t family_socket, sa_family_t family_broadcast)
{
IP ip;
ip_reset(&ip);
if (family_socket == AF_INET6) {
if (family_broadcast == AF_INET6) {
ip.family = AF_INET6;
/* FF02::1 is - according to RFC 4291 - multicast all-nodes link-local */
/* FE80::*: MUST be exact, for that we would need to look over all
* interfaces and check in which status they are */
ip.ip6.uint8[ 0] = 0xFF;
ip.ip6.uint8[ 1] = 0x02;
ip.ip6.uint8[15] = 0x01;
} else if (family_broadcast == AF_INET) {
ip.family = AF_INET6;
ip.ip6.uint32[0] = 0;
ip.ip6.uint32[1] = 0;
ip.ip6.uint32[2] = htonl(0xFFFF);
ip.ip6.uint32[3] = INADDR_BROADCAST;
}
} else if (family_socket == AF_INET) {
if (family_broadcast == AF_INET) {
ip.family = AF_INET;
ip.ip4.uint32 = INADDR_BROADCAST;
}
}
return ip;
}
/* Is IP a local ip or not. */
_Bool Local_ip(IP ip)
{
if (ip.family == AF_INET) {
IP4 ip4 = ip.ip4;
/* Loopback. */
if (ip4.uint8[0] == 127)
return 1;
} else {
/* embedded IPv4-in-IPv6 */
if (IPV6_IPV4_IN_V6(ip.ip6)) {
IP ip4;
ip4.family = AF_INET;
ip4.ip4.uint32 = ip.ip6.uint32[3];
return Local_ip(ip4);
}
/* localhost in IPv6 (::1) */
if (ip.ip6.uint64[0] == 0 && ip.ip6.uint32[2] == 0 && ip.ip6.uint32[3] == htonl(1))
return 1;
}
return 0;
}
/* return 0 if ip is a LAN ip.
* return -1 if it is not.
*/
int LAN_ip(IP ip)
{
if (Local_ip(ip))
return 0;
if (ip.family == AF_INET) {
IP4 ip4 = ip.ip4;
/* 10.0.0.0 to 10.255.255.255 range. */
if (ip4.uint8[0] == 10)
return 0;
/* 172.16.0.0 to 172.31.255.255 range. */
if (ip4.uint8[0] == 172 && ip4.uint8[1] >= 16 && ip4.uint8[1] <= 31)
return 0;
/* 192.168.0.0 to 192.168.255.255 range. */
if (ip4.uint8[0] == 192 && ip4.uint8[1] == 168)
return 0;
/* 169.254.1.0 to 169.254.254.255 range. */
if (ip4.uint8[0] == 169 && ip4.uint8[1] == 254 && ip4.uint8[2] != 0
&& ip4.uint8[2] != 255)
return 0;
/* RFC 6598: 100.64.0.0 to 100.127.255.255 (100.64.0.0/10)
* (shared address space to stack another layer of NAT) */
if ((ip4.uint8[0] == 100) && ((ip4.uint8[1] & 0xC0) == 0x40))
return 0;
} else if (ip.family == AF_INET6) {
/* autogenerated for each interface: FE80::* (up to FEBF::*)
FF02::1 is - according to RFC 4291 - multicast all-nodes link-local */
if (((ip.ip6.uint8[0] == 0xFF) && (ip.ip6.uint8[1] < 3) && (ip.ip6.uint8[15] == 1)) ||
((ip.ip6.uint8[0] == 0xFE) && ((ip.ip6.uint8[1] & 0xC0) == 0x80)))
return 0;
/* embedded IPv4-in-IPv6 */
if (IPV6_IPV4_IN_V6(ip.ip6)) {
IP ip4;
ip4.family = AF_INET;
ip4.ip4.uint32 = ip.ip6.uint32[3];
return LAN_ip(ip4);
}
}
return -1;
}
static int handle_LANdiscovery(void *object, IP_Port source, const uint8_t *packet, uint16_t length)
{
DHT *dht = object;
if (LAN_ip(source.ip) == -1)
return 1;
if (length != crypto_box_PUBLICKEYBYTES + 1)
return 1;
DHT_bootstrap(dht, source, packet + 1);
return 0;
}
int send_LANdiscovery(uint16_t port, DHT *dht)
{
uint8_t data[crypto_box_PUBLICKEYBYTES + 1];
data[0] = NET_PACKET_LAN_DISCOVERY;
id_copy(data + 1, dht->self_public_key);
send_broadcasts(dht->net, port, data, 1 + crypto_box_PUBLICKEYBYTES);
int res = -1;
IP_Port ip_port;
ip_port.port = port;
/* IPv6 multicast */
if (dht->net->family == AF_INET6) {
ip_port.ip = broadcast_ip(AF_INET6, AF_INET6);
if (ip_isset(&ip_port.ip))
if (sendpacket(dht->net, ip_port, data, 1 + crypto_box_PUBLICKEYBYTES) > 0)
res = 1;
}
/* IPv4 broadcast (has to be IPv4-in-IPv6 mapping if socket is AF_INET6 */
ip_port.ip = broadcast_ip(dht->net->family, AF_INET);
if (ip_isset(&ip_port.ip))
if (sendpacket(dht->net, ip_port, data, 1 + crypto_box_PUBLICKEYBYTES))
res = 1;
return res;
}
void LANdiscovery_init(DHT *dht)
{
networking_registerhandler(dht->net, NET_PACKET_LAN_DISCOVERY, &handle_LANdiscovery, dht);
}
void LANdiscovery_kill(DHT *dht)
{
networking_registerhandler(dht->net, NET_PACKET_LAN_DISCOVERY, NULL, NULL);
}
================================================
FILE: toxcore/LAN_discovery.h
================================================
/* LAN_discovery.h
*
* LAN discovery implementation.
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef LAN_DISCOVERY_H
#define LAN_DISCOVERY_H
#include "DHT.h"
/* Interval in seconds between LAN discovery packet sending. */
#define LAN_DISCOVERY_INTERVAL 10
/* Send a LAN discovery pcaket to the broadcast address with port port. */
int send_LANdiscovery(uint16_t port, DHT *dht);
/* Sets up packet handlers. */
void LANdiscovery_init(DHT *dht);
/* Clear packet handlers. */
void LANdiscovery_kill(DHT *dht);
/* Is IP a local ip or not. */
_Bool Local_ip(IP ip);
/* checks if a given IP isn't routable
*
* return 0 if ip is a LAN ip.
* return -1 if it is not.
*/
int LAN_ip(IP ip);
#endif
================================================
FILE: toxcore/Makefile.inc
================================================
lib_LTLIBRARIES += libtoxcore.la
libtoxcore_la_include_HEADERS = \
../toxcore/tox.h \
../toxcore/tox_old.h
libtoxcore_la_includedir = $(includedir)/tox
libtoxcore_la_SOURCES = ../toxcore/DHT.h \
../toxcore/DHT.c \
../toxcore/network.h \
../toxcore/network.c \
../toxcore/crypto_core.h \
../toxcore/crypto_core.c \
../toxcore/ping_array.h \
../toxcore/ping_array.c \
../toxcore/net_crypto.h \
../toxcore/net_crypto.c \
../toxcore/friend_requests.h \
../toxcore/friend_requests.c \
../toxcore/LAN_discovery.h \
../toxcore/LAN_discovery.c \
../toxcore/friend_connection.h \
../toxcore/friend_connection.c \
../toxcore/Messenger.h \
../toxcore/Messenger.c \
../toxcore/ping.h \
../toxcore/ping.c \
../toxcore/tox.h \
../toxcore/tox.c \
../toxcore/util.h \
../toxcore/util.c \
../toxcore/group.h \
../toxcore/group.c \
../toxcore/assoc.h \
../toxcore/assoc.c \
../toxcore/onion.h \
../toxcore/onion.c \
../toxcore/logger.h \
../toxcore/logger.c \
../toxcore/onion_announce.h \
../toxcore/onion_announce.c \
../toxcore/onion_client.h \
../toxcore/onion_client.c \
../toxcore/TCP_client.h \
../toxcore/TCP_client.c \
../toxcore/TCP_server.h \
../toxcore/TCP_server.c \
../toxcore/TCP_connection.h \
../toxcore/TCP_connection.c \
../toxcore/list.c \
../toxcore/list.h \
../toxcore/misc_tools.h \
../toxcore/tox_old_code.h
libtoxcore_la_CFLAGS = -I$(top_srcdir) \
-I$(top_srcdir)/toxcore \
$(LIBSODIUM_CFLAGS) \
$(NACL_CFLAGS) \
$(PTHREAD_CFLAGS)
libtoxcore_la_LDFLAGS = $(TOXCORE_LT_LDFLAGS) \
$(EXTRA_LT_LDFLAGS) \
$(LIBSODIUM_LDFLAGS) \
$(NACL_LDFLAGS) \
$(MATH_LDFLAGS) \
$(RT_LIBS) \
$(WINSOCK2_LIBS)
libtoxcore_la_LIBADD = $(LIBSODIUM_LIBS) \
$(NACL_OBJECTS) \
$(NAC_LIBS) \
$(PTHREAD_LIBS)
================================================
FILE: toxcore/Messenger.c
================================================
/* Messenger.c
*
* An implementation of a simple text chat only messenger on the tox network core.
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef DEBUG
#include
#endif
#include "logger.h"
#include "Messenger.h"
#include "assoc.h"
#include "network.h"
#include "util.h"
static void set_friend_status(Messenger *m, int32_t friendnumber, uint8_t status);
static int write_cryptpacket_id(const Messenger *m, int32_t friendnumber, uint8_t packet_id, const uint8_t *data,
uint32_t length, uint8_t congestion_control);
// friend_not_valid determines if the friendnumber passed is valid in the Messenger object
static uint8_t friend_not_valid(const Messenger *m, int32_t friendnumber)
{
if ((unsigned int)friendnumber < m->numfriends) {
if (m->friendlist[friendnumber].status != 0) {
return 0;
}
}
return 1;
}
/* Set the size of the friend list to numfriends.
*
* return -1 if realloc fails.
*/
int realloc_friendlist(Messenger *m, uint32_t num)
{
if (num == 0) {
free(m->friendlist);
m->friendlist = NULL;
return 0;
}
Friend *newfriendlist = realloc(m->friendlist, num * sizeof(Friend));
if (newfriendlist == NULL)
return -1;
m->friendlist = newfriendlist;
return 0;
}
/* return the friend id associated to that public key.
* return -1 if no such friend.
*/
int32_t getfriend_id(const Messenger *m, const uint8_t *real_pk)
{
uint32_t i;
for (i = 0; i < m->numfriends; ++i) {
if (m->friendlist[i].status > 0)
if (id_equal(real_pk, m->friendlist[i].real_pk))
return i;
}
return -1;
}
/* Copies the public key associated to that friend id into real_pk buffer.
* Make sure that real_pk is of size crypto_box_PUBLICKEYBYTES.
*
* return 0 if success.
* return -1 if failure.
*/
int get_real_pk(const Messenger *m, int32_t friendnumber, uint8_t *real_pk)
{
if (friend_not_valid(m, friendnumber))
return -1;
memcpy(real_pk, m->friendlist[friendnumber].real_pk, crypto_box_PUBLICKEYBYTES);
return 0;
}
/* return friend connection id on success.
* return -1 if failure.
*/
int getfriendcon_id(const Messenger *m, int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return -1;
return m->friendlist[friendnumber].friendcon_id;
}
/*
* return a uint16_t that represents the checksum of address of length len.
*/
static uint16_t address_checksum(const uint8_t *address, uint32_t len)
{
uint8_t checksum[2] = {0};
uint16_t check;
uint32_t i;
for (i = 0; i < len; ++i)
checksum[i % 2] ^= address[i];
memcpy(&check, checksum, sizeof(check));
return check;
}
/* Format: [real_pk (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)]
*
* return FRIEND_ADDRESS_SIZE byte address to give to others.
*/
void getaddress(const Messenger *m, uint8_t *address)
{
id_copy(address, m->net_crypto->self_public_key);
uint32_t nospam = get_nospam(&(m->fr));
memcpy(address + crypto_box_PUBLICKEYBYTES, &nospam, sizeof(nospam));
uint16_t checksum = address_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum));
memcpy(address + crypto_box_PUBLICKEYBYTES + sizeof(nospam), &checksum, sizeof(checksum));
}
static int send_online_packet(Messenger *m, int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return 0;
uint8_t packet = PACKET_ID_ONLINE;
return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c,
m->friendlist[friendnumber].friendcon_id), &packet, sizeof(packet), 0) != -1;
}
static int send_offline_packet(Messenger *m, int friendcon_id)
{
uint8_t packet = PACKET_ID_OFFLINE;
return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c, friendcon_id), &packet,
sizeof(packet), 0) != -1;
}
static int handle_status(void *object, int i, uint8_t status);
static int handle_packet(void *object, int i, uint8_t *temp, uint16_t len);
static int handle_custom_lossy_packet(void *object, int friend_num, const uint8_t *packet, uint16_t length);
static int32_t init_new_friend(Messenger *m, const uint8_t *real_pk, uint8_t status)
{
/* Resize the friend list if necessary. */
if (realloc_friendlist(m, m->numfriends + 1) != 0)
return FAERR_NOMEM;
memset(&(m->friendlist[m->numfriends]), 0, sizeof(Friend));
int friendcon_id = new_friend_connection(m->fr_c, real_pk);
if (friendcon_id == -1)
return FAERR_NOMEM;
uint32_t i;
for (i = 0; i <= m->numfriends; ++i) {
if (m->friendlist[i].status == NOFRIEND) {
m->friendlist[i].status = status;
m->friendlist[i].friendcon_id = friendcon_id;
m->friendlist[i].friendrequest_lastsent = 0;
id_copy(m->friendlist[i].real_pk, real_pk);
m->friendlist[i].statusmessage_length = 0;
m->friendlist[i].userstatus = USERSTATUS_NONE;
m->friendlist[i].is_typing = 0;
m->friendlist[i].message_id = 0;
friend_connection_callbacks(m->fr_c, friendcon_id, MESSENGER_CALLBACK_INDEX, &handle_status, &handle_packet,
&handle_custom_lossy_packet, m, i);
if (m->numfriends == i)
++m->numfriends;
if (friend_con_connected(m->fr_c, friendcon_id) == FRIENDCONN_STATUS_CONNECTED) {
send_online_packet(m, i);
}
return i;
}
}
return FAERR_NOMEM;
}
/*
* Add a friend.
* Set the data that will be sent along with friend request.
* Address is the address of the friend (returned by getaddress of the friend you wish to add) it must be FRIEND_ADDRESS_SIZE bytes.
* data is the data and length is the length.
*
* return the friend number if success.
* return FA_TOOLONG if message length is too long.
* return FAERR_NOMESSAGE if no message (message length must be >= 1 byte).
* return FAERR_OWNKEY if user's own key.
* return FAERR_ALREADYSENT if friend request already sent or already a friend.
* return FAERR_BADCHECKSUM if bad checksum in address.
* return FAERR_SETNEWNOSPAM if the friend was already there but the nospam was different.
* (the nospam for that friend was set to the new one).
* return FAERR_NOMEM if increasing the friend list size fails.
*/
int32_t m_addfriend(Messenger *m, const uint8_t *address, const uint8_t *data, uint16_t length)
{
if (length > MAX_FRIEND_REQUEST_DATA_SIZE)
return FAERR_TOOLONG;
uint8_t real_pk[crypto_box_PUBLICKEYBYTES];
id_copy(real_pk, address);
if (!public_key_valid(real_pk))
return FAERR_BADCHECKSUM;
uint16_t check, checksum = address_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum));
memcpy(&check, address + crypto_box_PUBLICKEYBYTES + sizeof(uint32_t), sizeof(check));
if (check != checksum)
return FAERR_BADCHECKSUM;
if (length < 1)
return FAERR_NOMESSAGE;
if (id_equal(real_pk, m->net_crypto->self_public_key))
return FAERR_OWNKEY;
int32_t friend_id = getfriend_id(m, real_pk);
if (friend_id != -1) {
if (m->friendlist[friend_id].status >= FRIEND_CONFIRMED)
return FAERR_ALREADYSENT;
uint32_t nospam;
memcpy(&nospam, address + crypto_box_PUBLICKEYBYTES, sizeof(nospam));
if (m->friendlist[friend_id].friendrequest_nospam == nospam)
return FAERR_ALREADYSENT;
m->friendlist[friend_id].friendrequest_nospam = nospam;
return FAERR_SETNEWNOSPAM;
}
int32_t ret = init_new_friend(m, real_pk, FRIEND_ADDED);
if (ret < 0) {
return ret;
}
m->friendlist[ret].friendrequest_timeout = FRIENDREQUEST_TIMEOUT;
memcpy(m->friendlist[ret].info, data, length);
m->friendlist[ret].info_size = length;
memcpy(&(m->friendlist[ret].friendrequest_nospam), address + crypto_box_PUBLICKEYBYTES, sizeof(uint32_t));
return ret;
}
int32_t m_addfriend_norequest(Messenger *m, const uint8_t *real_pk)
{
if (getfriend_id(m, real_pk) != -1)
return FAERR_ALREADYSENT;
if (!public_key_valid(real_pk))
return FAERR_BADCHECKSUM;
if (id_equal(real_pk, m->net_crypto->self_public_key))
return FAERR_OWNKEY;
return init_new_friend(m, real_pk, FRIEND_CONFIRMED);
}
static int clear_receipts(Messenger *m, int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return -1;
struct Receipts *receipts = m->friendlist[friendnumber].receipts_start;
while (receipts) {
struct Receipts *temp_r = receipts->next;
free(receipts);
receipts = temp_r;
}
m->friendlist[friendnumber].receipts_start = NULL;
m->friendlist[friendnumber].receipts_end = NULL;
return 0;
}
static int add_receipt(Messenger *m, int32_t friendnumber, uint32_t packet_num, uint32_t msg_id)
{
if (friend_not_valid(m, friendnumber))
return -1;
struct Receipts *new = calloc(1, sizeof(struct Receipts));
if (!new)
return -1;
new->packet_num = packet_num;
new->msg_id = msg_id;
if (!m->friendlist[friendnumber].receipts_start) {
m->friendlist[friendnumber].receipts_start = new;
} else {
m->friendlist[friendnumber].receipts_end->next = new;
}
m->friendlist[friendnumber].receipts_end = new;
new->next = NULL;
return 0;
}
/*
* return -1 on failure.
* return 0 if packet was received.
*/
static int friend_received_packet(const Messenger *m, int32_t friendnumber, uint32_t number)
{
if (friend_not_valid(m, friendnumber))
return -1;
return cryptpacket_received(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c,
m->friendlist[friendnumber].friendcon_id), number);
}
static int do_receipts(Messenger *m, int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return -1;
struct Receipts *receipts = m->friendlist[friendnumber].receipts_start;
while (receipts) {
struct Receipts *temp_r = receipts->next;
if (friend_received_packet(m, friendnumber, receipts->packet_num) == -1)
break;
if (m->read_receipt)
(*m->read_receipt)(m, friendnumber, receipts->msg_id, m->read_receipt_userdata);
free(receipts);
m->friendlist[friendnumber].receipts_start = temp_r;
receipts = temp_r;
}
if (!m->friendlist[friendnumber].receipts_start)
m->friendlist[friendnumber].receipts_end = NULL;
return 0;
}
/* Remove a friend.
*
* return 0 if success.
* return -1 if failure.
*/
int m_delfriend(Messenger *m, int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return -1;
if (m->friend_connectionstatuschange_internal)
m->friend_connectionstatuschange_internal(m, friendnumber, 0, m->friend_connectionstatuschange_internal_userdata);
clear_receipts(m, friendnumber);
remove_request_received(&(m->fr), m->friendlist[friendnumber].real_pk);
friend_connection_callbacks(m->fr_c, m->friendlist[friendnumber].friendcon_id, MESSENGER_CALLBACK_INDEX, 0, 0, 0, 0, 0);
if (friend_con_connected(m->fr_c, m->friendlist[friendnumber].friendcon_id) == FRIENDCONN_STATUS_CONNECTED) {
send_offline_packet(m, m->friendlist[friendnumber].friendcon_id);
}
kill_friend_connection(m->fr_c, m->friendlist[friendnumber].friendcon_id);
memset(&(m->friendlist[friendnumber]), 0, sizeof(Friend));
uint32_t i;
for (i = m->numfriends; i != 0; --i) {
if (m->friendlist[i - 1].status != NOFRIEND)
break;
}
m->numfriends = i;
if (realloc_friendlist(m, m->numfriends) != 0)
return FAERR_NOMEM;
return 0;
}
int m_get_friend_connectionstatus(const Messenger *m, int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return -1;
if (m->friendlist[friendnumber].status == FRIEND_ONLINE) {
_Bool direct_connected = 0;
unsigned int num_online_relays = 0;
crypto_connection_status(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c,
m->friendlist[friendnumber].friendcon_id), &direct_connected, &num_online_relays);
if (direct_connected) {
return CONNECTION_UDP;
} else {
if (num_online_relays) {
return CONNECTION_TCP;
} else {
return CONNECTION_UNKNOWN;
}
}
} else {
return CONNECTION_NONE;
}
}
int m_friend_exists(const Messenger *m, int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return 0;
return 1;
}
/* Send a message of type.
*
* return -1 if friend not valid.
* return -2 if too large.
* return -3 if friend not online.
* return -4 if send failed (because queue is full).
* return -5 if bad type.
* return 0 if success.
*/
int m_send_message_generic(Messenger *m, int32_t friendnumber, uint8_t type, const uint8_t *message, uint32_t length,
uint32_t *message_id)
{
if (type > MESSAGE_ACTION)
return -5;
if (friend_not_valid(m, friendnumber))
return -1;
if (length >= MAX_CRYPTO_DATA_SIZE)
return -2;
if (m->friendlist[friendnumber].status != FRIEND_ONLINE)
return -3;
uint8_t packet[length + 1];
packet[0] = type + PACKET_ID_MESSAGE;
if (length != 0)
memcpy(packet + 1, message, length);
int64_t packet_num = write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c,
m->friendlist[friendnumber].friendcon_id), packet, length + 1, 0);
if (packet_num == -1)
return -4;
uint32_t msg_id = ++m->friendlist[friendnumber].message_id;
add_receipt(m, friendnumber, packet_num, msg_id);
if (message_id)
*message_id = msg_id;
return 0;
}
/* Send a name packet to friendnumber.
* length is the length with the NULL terminator.
*/
static int m_sendname(const Messenger *m, int32_t friendnumber, const uint8_t *name, uint16_t length)
{
if (length > MAX_NAME_LENGTH)
return 0;
return write_cryptpacket_id(m, friendnumber, PACKET_ID_NICKNAME, name, length, 0);
}
/* Set the name and name_length of a friend.
*
* return 0 if success.
* return -1 if failure.
*/
int setfriendname(Messenger *m, int32_t friendnumber, const uint8_t *name, uint16_t length)
{
if (friend_not_valid(m, friendnumber))
return -1;
if (length > MAX_NAME_LENGTH || length == 0)
return -1;
m->friendlist[friendnumber].name_length = length;
memcpy(m->friendlist[friendnumber].name, name, length);
return 0;
}
/* Set our nickname
* name must be a string of maximum MAX_NAME_LENGTH length.
* length must be at least 1 byte.
* length is the length of name with the NULL terminator.
*
* return 0 if success.
* return -1 if failure.
*/
int setname(Messenger *m, const uint8_t *name, uint16_t length)
{
if (length > MAX_NAME_LENGTH)
return -1;
if (m->name_length == length && (length == 0 || memcmp(name, m->name, length) == 0))
return 0;
if (length)
memcpy(m->name, name, length);
m->name_length = length;
uint32_t i;
for (i = 0; i < m->numfriends; ++i)
m->friendlist[i].name_sent = 0;
return 0;
}
/* Get our nickname and put it in name.
* name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH bytes.
*
* return the length of the name.
*/
uint16_t getself_name(const Messenger *m, uint8_t *name)
{
if (name == NULL) {
return 0;
}
memcpy(name, m->name, m->name_length);
return m->name_length;
}
/* Get name of friendnumber and put it in name.
* name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH bytes.
*
* return length of name if success.
* return -1 if failure.
*/
int getname(const Messenger *m, int32_t friendnumber, uint8_t *name)
{
if (friend_not_valid(m, friendnumber))
return -1;
memcpy(name, m->friendlist[friendnumber].name, m->friendlist[friendnumber].name_length);
return m->friendlist[friendnumber].name_length;
}
int m_get_name_size(const Messenger *m, int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return -1;
return m->friendlist[friendnumber].name_length;
}
int m_get_self_name_size(const Messenger *m)
{
return m->name_length;
}
int m_set_statusmessage(Messenger *m, const uint8_t *status, uint16_t length)
{
if (length > MAX_STATUSMESSAGE_LENGTH)
return -1;
if (m->statusmessage_length == length && (length == 0 || memcmp(m->statusmessage, status, length) == 0))
return 0;
if (length)
memcpy(m->statusmessage, status, length);
m->statusmessage_length = length;
uint32_t i;
for (i = 0; i < m->numfriends; ++i)
m->friendlist[i].statusmessage_sent = 0;
return 0;
}
int m_set_userstatus(Messenger *m, uint8_t status)
{
if (status >= USERSTATUS_INVALID)
return -1;
if (m->userstatus == status)
return 0;
m->userstatus = status;
uint32_t i;
for (i = 0; i < m->numfriends; ++i)
m->friendlist[i].userstatus_sent = 0;
return 0;
}
/* return the size of friendnumber's user status.
* Guaranteed to be at most MAX_STATUSMESSAGE_LENGTH.
*/
int m_get_statusmessage_size(const Messenger *m, int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return -1;
return m->friendlist[friendnumber].statusmessage_length;
}
/* Copy the user status of friendnumber into buf, truncating if needed to maxlen
* bytes, use m_get_statusmessage_size to find out how much you need to allocate.
*/
int m_copy_statusmessage(const Messenger *m, int32_t friendnumber, uint8_t *buf, uint32_t maxlen)
{
if (friend_not_valid(m, friendnumber))
return -1;
int msglen = MIN(maxlen, m->friendlist[friendnumber].statusmessage_length);
memcpy(buf, m->friendlist[friendnumber].statusmessage, msglen);
memset(buf + msglen, 0, maxlen - msglen);
return msglen;
}
/* return the size of friendnumber's user status.
* Guaranteed to be at most MAX_STATUSMESSAGE_LENGTH.
*/
int m_get_self_statusmessage_size(const Messenger *m)
{
return m->statusmessage_length;
}
int m_copy_self_statusmessage(const Messenger *m, uint8_t *buf)
{
memcpy(buf, m->statusmessage, m->statusmessage_length);
return m->statusmessage_length;
}
uint8_t m_get_userstatus(const Messenger *m, int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return USERSTATUS_INVALID;
uint8_t status = m->friendlist[friendnumber].userstatus;
if (status >= USERSTATUS_INVALID) {
status = USERSTATUS_NONE;
}
return status;
}
uint8_t m_get_self_userstatus(const Messenger *m)
{
return m->userstatus;
}
uint64_t m_get_last_online(const Messenger *m, int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return UINT64_MAX;
return m->friendlist[friendnumber].last_seen_time;
}
int m_set_usertyping(Messenger *m, int32_t friendnumber, uint8_t is_typing)
{
if (is_typing != 0 && is_typing != 1)
return -1;
if (friend_not_valid(m, friendnumber))
return -1;
if (m->friendlist[friendnumber].user_istyping == is_typing)
return 0;
m->friendlist[friendnumber].user_istyping = is_typing;
m->friendlist[friendnumber].user_istyping_sent = 0;
return 0;
}
int m_get_istyping(const Messenger *m, int32_t friendnumber)
{
if (friend_not_valid(m, friendnumber))
return -1;
return m->friendlist[friendnumber].is_typing;
}
static int send_statusmessage(const Messenger *m, int32_t friendnumber, const uint8_t *status, uint16_t length)
{
return write_cryptpacket_id(m, friendnumber, PACKET_ID_STATUSMESSAGE, status, length, 0);
}
static int send_userstatus(const Messenger *m, int32_t friendnumber, uint8_t status)
{
return write_cryptpacket_id(m, friendnumber, PACKET_ID_USERSTATUS, &status, sizeof(status), 0);
}
static int send_user_istyping(const Messenger *m, int32_t friendnumber, uint8_t is_typing)
{
uint8_t typing = is_typing;
return write_cryptpacket_id(m, friendnumber, PACKET_ID_TYPING, &typing, sizeof(typing), 0);
}
static int set_friend_statusmessage(const Messenger *m, int32_t friendnumber, const uint8_t *status, uint16_t length)
{
if (friend_not_valid(m, friendnumber))
return -1;
if (length > MAX_STATUSMESSAGE_LENGTH)
return -1;
if (length)
memcpy(m->friendlist[friendnumber].statusmessage, status, length);
m->friendlist[friendnumber].statusmessage_length = length;
return 0;
}
static void set_friend_userstatus(const Messenger *m, int32_t friendnumber, uint8_t status)
{
m->friendlist[friendnumber].userstatus = status;
}
static void set_friend_typing(const Messenger *m, int32_t friendnumber, uint8_t is_typing)
{
m->friendlist[friendnumber].is_typing = is_typing;
}
/* Set the function that will be executed when a friend request is received. */
void m_callback_friendrequest(Messenger *m, void (*function)(Messenger *m, const uint8_t *, const uint8_t *, size_t,
void *), void *userdata)
{
void (*handle_friendrequest)(void *, const uint8_t *, const uint8_t *, size_t, void *) = (void *)function;
callback_friendrequest(&(m->fr), handle_friendrequest, m, userdata);
}
/* Set the function that will be executed when a message from a friend is received. */
void m_callback_friendmessage(Messenger *m, void (*function)(Messenger *m, uint32_t, unsigned int, const uint8_t *,
size_t, void *), void *userdata)
{
m->friend_message = function;
m->friend_message_userdata = userdata;
}
void m_callback_namechange(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, size_t, void *),
void *userdata)
{
m->friend_namechange = function;
m->friend_namechange_userdata = userdata;
}
void m_callback_statusmessage(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, size_t, void *),
void *userdata)
{
m->friend_statusmessagechange = function;
m->friend_statusmessagechange_userdata = userdata;
}
void m_callback_userstatus(Messenger *m, void (*function)(Messenger *m, uint32_t, unsigned int, void *), void *userdata)
{
m->friend_userstatuschange = function;
m->friend_userstatuschange_userdata = userdata;
}
void m_callback_typingchange(Messenger *m, void(*function)(Messenger *m, uint32_t, _Bool, void *), void *userdata)
{
m->friend_typingchange = function;
m->friend_typingchange_userdata = userdata;
}
void m_callback_read_receipt(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, void *), void *userdata)
{
m->read_receipt = function;
m->read_receipt_userdata = userdata;
}
void m_callback_connectionstatus(Messenger *m, void (*function)(Messenger *m, uint32_t, unsigned int, void *),
void *userdata)
{
m->friend_connectionstatuschange = function;
m->friend_connectionstatuschange_userdata = userdata;
}
void m_callback_core_connection(Messenger *m, void (*function)(Messenger *m, unsigned int, void *), void *userdata)
{
m->core_connection_change = function;
m->core_connection_change_userdata = userdata;
}
void m_callback_connectionstatus_internal_av(Messenger *m, void (*function)(Messenger *m, uint32_t, uint8_t, void *),
void *userdata)
{
m->friend_connectionstatuschange_internal = function;
m->friend_connectionstatuschange_internal_userdata = userdata;
}
static void check_friend_tcp_udp(Messenger *m, int32_t friendnumber)
{
int last_connection_udp_tcp = m->friendlist[friendnumber].last_connection_udp_tcp;
int ret = m_get_friend_connectionstatus(m, friendnumber);
if (ret == -1)
return;
if (ret == CONNECTION_UNKNOWN) {
if (last_connection_udp_tcp == CONNECTION_UDP) {
return;
} else {
ret = CONNECTION_TCP;
}
}
if (last_connection_udp_tcp != ret) {
if (m->friend_connectionstatuschange)
m->friend_connectionstatuschange(m, friendnumber, ret, m->friend_connectionstatuschange_userdata);
}
m->friendlist[friendnumber].last_connection_udp_tcp = ret;
}
static void break_files(const Messenger *m, int32_t friendnumber);
static void check_friend_connectionstatus(Messenger *m, int32_t friendnumber, uint8_t status)
{
if (status == NOFRIEND)
return;
const uint8_t was_online = m->friendlist[friendnumber].status == FRIEND_ONLINE;
const uint8_t is_online = status == FRIEND_ONLINE;
if (is_online != was_online) {
if (was_online) {
break_files(m, friendnumber);
clear_receipts(m, friendnumber);
} else {
m->friendlist[friendnumber].name_sent = 0;
m->friendlist[friendnumber].userstatus_sent = 0;
m->friendlist[friendnumber].statusmessage_sent = 0;
m->friendlist[friendnumber].user_istyping_sent = 0;
}
m->friendlist[friendnumber].status = status;
check_friend_tcp_udp(m, friendnumber);
if (m->friend_connectionstatuschange_internal)
m->friend_connectionstatuschange_internal(m, friendnumber, is_online,
m->friend_connectionstatuschange_internal_userdata);
}
}
void set_friend_status(Messenger *m, int32_t friendnumber, uint8_t status)
{
check_friend_connectionstatus(m, friendnumber, status);
m->friendlist[friendnumber].status = status;
}
static int write_cryptpacket_id(const Messenger *m, int32_t friendnumber, uint8_t packet_id, const uint8_t *data,
uint32_t length, uint8_t congestion_control)
{
if (friend_not_valid(m, friendnumber))
return 0;
if (length >= MAX_CRYPTO_DATA_SIZE || m->friendlist[friendnumber].status != FRIEND_ONLINE)
return 0;
uint8_t packet[length + 1];
packet[0] = packet_id;
if (length != 0)
memcpy(packet + 1, data, length);
return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c,
m->friendlist[friendnumber].friendcon_id), packet, length + 1, congestion_control) != -1;
}
/**********GROUP CHATS************/
/* Set the callback for group invites.
*
* Function(Messenger *m, uint32_t friendnumber, uint8_t *data, uint16_t length)
*/
void m_callback_group_invite(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, uint16_t))
{
m->group_invite = function;
}
/* Send a group invite packet.
*
* return 1 on success
* return 0 on failure
*/
int send_group_invite_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length)
{
return write_cryptpacket_id(m, friendnumber, PACKET_ID_INVITE_GROUPCHAT, data, length, 0);
}
/****************FILE SENDING*****************/
/* Set the callback for file send requests.
*
* Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint32_t filetype, uint64_t filesize, uint8_t *filename, size_t filename_length, void *userdata)
*/
void callback_file_sendrequest(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, uint32_t, uint64_t,
const uint8_t *, size_t, void *), void *userdata)
{
m->file_sendrequest = function;
m->file_sendrequest_userdata = userdata;
}
/* Set the callback for file control requests.
*
* Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, unsigned int control_type, void *userdata)
*
*/
void callback_file_control(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, unsigned int, void *),
void *userdata)
{
m->file_filecontrol = function;
m->file_filecontrol_userdata = userdata;
}
/* Set the callback for file data.
*
* Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, uint8_t *data, size_t length, void *userdata)
*
*/
void callback_file_data(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, uint64_t, const uint8_t *,
size_t, void *), void *userdata)
{
m->file_filedata = function;
m->file_filedata_userdata = userdata;
}
/* Set the callback for file request chunk.
*
* Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, size_t length, void *userdata)
*
*/
void callback_file_reqchunk(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, uint64_t, size_t, void *),
void *userdata)
{
m->file_reqchunk = function;
m->file_reqchunk_userdata = userdata;
}
#define MAX_FILENAME_LENGTH 255
/* Copy the file transfer file id to file_id
*
* return 0 on success.
* return -1 if friend not valid.
* return -2 if filenumber not valid
*/
int file_get_id(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint8_t *file_id)
{
if (friend_not_valid(m, friendnumber))
return -1;
if (m->friendlist[friendnumber].status != FRIEND_ONLINE)
return -2;
uint32_t temp_filenum;
uint8_t send_receive, file_number;
if (filenumber >= (1 << 16)) {
send_receive = 1;
temp_filenum = (filenumber >> 16) - 1;
} else {
send_receive = 0;
temp_filenum = filenumber;
}
if (temp_filenum >= MAX_CONCURRENT_FILE_PIPES)
return -2;
file_number = temp_filenum;
struct File_Transfers *ft;
if (send_receive) {
ft = &m->friendlist[friendnumber].file_receiving[file_number];
} else {
ft = &m->friendlist[friendnumber].file_sending[file_number];
}
if (ft->status == FILESTATUS_NONE)
return -2;
memcpy(file_id, ft->id, FILE_ID_LENGTH);
return 0;
}
/* Send a file send request.
* Maximum filename length is 255 bytes.
* return 1 on success
* return 0 on failure
*/
static int file_sendrequest(const Messenger *m, int32_t friendnumber, uint8_t filenumber, uint32_t file_type,
uint64_t filesize, const uint8_t *file_id, const uint8_t *filename, uint16_t filename_length)
{
if (friend_not_valid(m, friendnumber))
return 0;
if (filename_length > MAX_FILENAME_LENGTH)
return 0;
uint8_t packet[1 + sizeof(file_type) + sizeof(filesize) + FILE_ID_LENGTH + filename_length];
packet[0] = filenumber;
file_type = htonl(file_type);
memcpy(packet + 1, &file_type, sizeof(file_type));
host_to_net((uint8_t *)&filesize, sizeof(filesize));
memcpy(packet + 1 + sizeof(file_type), &filesize, sizeof(filesize));
memcpy(packet + 1 + sizeof(file_type) + sizeof(filesize), file_id, FILE_ID_LENGTH);
if (filename_length) {
memcpy(packet + 1 + sizeof(file_type) + sizeof(filesize) + FILE_ID_LENGTH, filename, filename_length);
}
return write_cryptpacket_id(m, friendnumber, PACKET_ID_FILE_SENDREQUEST, packet, sizeof(packet), 0);
}
/* Send a file send request.
* Maximum filename length is 255 bytes.
* return file number on success
* return -1 if friend not found.
* return -2 if filename length invalid.
* return -3 if no more file sending slots left.
* return -4 if could not send packet (friend offline).
*
*/
long int new_filesender(const Messenger *m, int32_t friendnumber, uint32_t file_type, uint64_t filesize,
const uint8_t *file_id, const uint8_t *filename, uint16_t filename_length)
{
if (friend_not_valid(m, friendnumber))
return -1;
if (filename_length > MAX_FILENAME_LENGTH)
return -2;
uint32_t i;
for (i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) {
if (m->friendlist[friendnumber].file_sending[i].status == FILESTATUS_NONE)
break;
}
if (i == MAX_CONCURRENT_FILE_PIPES)
return -3;
if (file_sendrequest(m, friendnumber, i, file_type, filesize, file_id, filename, filename_length) == 0)
return -4;
struct File_Transfers *ft = &m->friendlist[friendnumber].file_sending[i];
ft->status = FILESTATUS_NOT_ACCEPTED;
ft->size = filesize;
ft->transferred = 0;
ft->requested = 0;
ft->slots_allocated = 0;
ft->paused = FILE_PAUSE_NOT;
memcpy(ft->id, file_id, FILE_ID_LENGTH);
++m->friendlist[friendnumber].num_sending_files;
return i;
}
int send_file_control_packet(const Messenger *m, int32_t friendnumber, uint8_t send_receive, uint8_t filenumber,
uint8_t control_type, uint8_t *data, uint16_t data_length)
{
if ((unsigned int)(1 + 3 + data_length) > MAX_CRYPTO_DATA_SIZE)
return -1;
uint8_t packet[3 + data_length];
packet[0] = send_receive;
packet[1] = filenumber;
packet[2] = control_type;
if (data_length) {
memcpy(packet + 3, data, data_length);
}
return write_cryptpacket_id(m, friendnumber, PACKET_ID_FILE_CONTROL, packet, sizeof(packet), 0);
}
/* Send a file control request.
*
* return 0 on success
* return -1 if friend not valid.
* return -2 if friend not online.
* return -3 if file number invalid.
* return -4 if file control is bad.
* return -5 if file already paused.
* return -6 if resume file failed because it was only paused by the other.
* return -7 if resume file failed because it wasn't paused.
* return -8 if packet failed to send.
*/
int file_control(const Messenger *m, int32_t friendnumber, uint32_t filenumber, unsigned int control)
{
if (friend_not_valid(m, friendnumber))
return -1;
if (m->friendlist[friendnumber].status != FRIEND_ONLINE)
return -2;
uint32_t temp_filenum;
uint8_t send_receive, file_number;
if (filenumber >= (1 << 16)) {
send_receive = 1;
temp_filenum = (filenumber >> 16) - 1;
} else {
send_receive = 0;
temp_filenum = filenumber;
}
if (temp_filenum >= MAX_CONCURRENT_FILE_PIPES)
return -3;
file_number = temp_filenum;
struct File_Transfers *ft;
if (send_receive) {
ft = &m->friendlist[friendnumber].file_receiving[file_number];
} else {
ft = &m->friendlist[friendnumber].file_sending[file_number];
}
if (ft->status == FILESTATUS_NONE)
return -3;
if (control > FILECONTROL_KILL)
return -4;
if (control == FILECONTROL_PAUSE && ((ft->paused & FILE_PAUSE_US) || ft->status != FILESTATUS_TRANSFERRING))
return -5;
if (control == FILECONTROL_ACCEPT) {
if (ft->status == FILESTATUS_TRANSFERRING) {
if (!(ft->paused & FILE_PAUSE_US)) {
if (ft->paused & FILE_PAUSE_OTHER) {
return -6;
} else {
return -7;
}
}
} else {
if (ft->status != FILESTATUS_NOT_ACCEPTED)
return -7;
if (!send_receive)
return -6;
}
}
if (send_file_control_packet(m, friendnumber, send_receive, file_number, control, 0, 0)) {
if (control == FILECONTROL_KILL) {
ft->status = FILESTATUS_NONE;
if (send_receive == 0) {
--m->friendlist[friendnumber].num_sending_files;
}
} else if (control == FILECONTROL_PAUSE) {
ft->paused |= FILE_PAUSE_US;
} else if (control == FILECONTROL_ACCEPT) {
ft->status = FILESTATUS_TRANSFERRING;
if (ft->paused & FILE_PAUSE_US) {
ft->paused ^= FILE_PAUSE_US;
}
}
} else {
return -8;
}
return 0;
}
/* Send a seek file control request.
*
* return 0 on success
* return -1 if friend not valid.
* return -2 if friend not online.
* return -3 if file number invalid.
* return -4 if not receiving file.
* return -5 if file status wrong.
* return -6 if position bad.
* return -8 if packet failed to send.
*/
int file_seek(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position)
{
if (friend_not_valid(m, friendnumber))
return -1;
if (m->friendlist[friendnumber].status != FRIEND_ONLINE)
return -2;
uint32_t temp_filenum;
uint8_t send_receive, file_number;
if (filenumber >= (1 << 16)) {
send_receive = 1;
temp_filenum = (filenumber >> 16) - 1;
} else {
return -4;
}
if (temp_filenum >= MAX_CONCURRENT_FILE_PIPES)
return -3;
file_number = temp_filenum;
struct File_Transfers *ft;
if (send_receive) {
ft = &m->friendlist[friendnumber].file_receiving[file_number];
} else {
ft = &m->friendlist[friendnumber].file_sending[file_number];
}
if (ft->status == FILESTATUS_NONE)
return -3;
if (ft->status != FILESTATUS_NOT_ACCEPTED)
return -5;
if (position >= ft->size) {
return -6;
}
uint64_t sending_pos = position;
host_to_net((uint8_t *)&sending_pos, sizeof(sending_pos));
if (send_file_control_packet(m, friendnumber, send_receive, file_number, FILECONTROL_SEEK, (uint8_t *)&sending_pos,
sizeof(sending_pos))) {
ft->transferred = position;
} else {
return -8;
}
return 0;
}
/* return packet number on success.
* return -1 on failure.
*/
static int64_t send_file_data_packet(const Messenger *m, int32_t friendnumber, uint8_t filenumber, const uint8_t *data,
uint16_t length)
{
if (friend_not_valid(m, friendnumber))
return -1;
uint8_t packet[2 + length];
packet[0] = PACKET_ID_FILE_DATA;
packet[1] = filenumber;
if (length) {
memcpy(packet + 2, data, length);
}
return write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c,
m->friendlist[friendnumber].friendcon_id), packet, sizeof(packet), 1);
}
#define MAX_FILE_DATA_SIZE (MAX_CRYPTO_DATA_SIZE - 2)
#define MIN_SLOTS_FREE (CRYPTO_MIN_QUEUE_LENGTH / 4)
/* Send file data.
*
* return 0 on success
* return -1 if friend not valid.
* return -2 if friend not online.
* return -3 if filenumber invalid.
* return -4 if file transfer not transferring.
* return -5 if bad data size.
* return -6 if packet queue full.
* return -7 if wrong position.
*/
int file_data(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position, const uint8_t *data,
uint16_t length)
{
if (friend_not_valid(m, friendnumber))
return -1;
if (m->friendlist[friendnumber].status != FRIEND_ONLINE)
return -2;
if (filenumber >= MAX_CONCURRENT_FILE_PIPES)
return -3;
struct File_Transfers *ft = &m->friendlist[friendnumber].file_sending[filenumber];
if (ft->status != FILESTATUS_TRANSFERRING)
return -4;
if (length > MAX_FILE_DATA_SIZE)
return -5;
if (ft->size - ft->transferred < length) {
return -5;
}
if (ft->size != UINT64_MAX && length != MAX_FILE_DATA_SIZE && (ft->transferred + length) != ft->size) {
return -5;
}
if (position != ft->transferred || (ft->requested <= position && ft->size != 0)) {
return -7;
}
/* Prevent file sending from filling up the entire buffer preventing messages from being sent. TODO: remove */
if (crypto_num_free_sendqueue_slots(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c,
m->friendlist[friendnumber].friendcon_id)) < MIN_SLOTS_FREE)
return -6;
int64_t ret = send_file_data_packet(m, friendnumber, filenumber, data, length);
if (ret != -1) {
//TODO record packet ids to check if other received complete file.
ft->transferred += length;
if (ft->slots_allocated) {
--ft->slots_allocated;
}
if (length != MAX_FILE_DATA_SIZE || ft->size == ft->transferred) {
ft->status = FILESTATUS_FINISHED;
ft->last_packet_number = ret;
}
return 0;
}
return -6;
}
/* Give the number of bytes left to be sent/received.
*
* send_receive is 0 if we want the sending files, 1 if we want the receiving.
*
* return number of bytes remaining to be sent/received on success
* return 0 on failure
*/
uint64_t file_dataremaining(const Messenger *m, int32_t friendnumber, uint8_t filenumber, uint8_t send_receive)
{
if (friend_not_valid(m, friendnumber))
return 0;
if (send_receive == 0) {
if (m->friendlist[friendnumber].file_sending[filenumber].status == FILESTATUS_NONE)
return 0;
return m->friendlist[friendnumber].file_sending[filenumber].size -
m->friendlist[friendnumber].file_sending[filenumber].transferred;
} else {
if (m->friendlist[friendnumber].file_receiving[filenumber].status == FILESTATUS_NONE)
return 0;
return m->friendlist[friendnumber].file_receiving[filenumber].size -
m->friendlist[friendnumber].file_receiving[filenumber].transferred;
}
}
static void do_reqchunk_filecb(Messenger *m, int32_t friendnumber)
{
if (!m->friendlist[friendnumber].num_sending_files)
return;
int free_slots = crypto_num_free_sendqueue_slots(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c,
m->friendlist[friendnumber].friendcon_id));
if (free_slots < MIN_SLOTS_FREE) {
free_slots = 0;
} else {
free_slots -= MIN_SLOTS_FREE;
}
unsigned int i, num = m->friendlist[friendnumber].num_sending_files;
for (i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) {
struct File_Transfers *ft = &m->friendlist[friendnumber].file_sending[i];
if (ft->status != FILESTATUS_NONE) {
--num;
if (ft->status == FILESTATUS_FINISHED) {
/* Check if file was entirely sent. */
if (friend_received_packet(m, friendnumber, ft->last_packet_number) == 0) {
if (m->file_reqchunk)
(*m->file_reqchunk)(m, friendnumber, i, ft->transferred, 0, m->file_reqchunk_userdata);
ft->status = FILESTATUS_NONE;
--m->friendlist[friendnumber].num_sending_files;
}
}
/* TODO: if file is too slow, switch to the next. */
if (ft->slots_allocated > (unsigned int)free_slots) {
free_slots = 0;
} else {
free_slots -= ft->slots_allocated;
}
}
while (ft->status == FILESTATUS_TRANSFERRING && (ft->paused == FILE_PAUSE_NOT)) {
if (max_speed_reached(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c,
m->friendlist[friendnumber].friendcon_id))) {
free_slots = 0;
}
if (free_slots == 0)
break;
uint16_t length = MAX_FILE_DATA_SIZE;
if (ft->size == 0) {
/* Send 0 data to friend if file is 0 length. */
file_data(m, friendnumber, i, 0, 0, 0);
break;
}
if (ft->size == ft->requested) {
break;
}
if (ft->size - ft->requested < length) {
length = ft->size - ft->requested;
}
++ft->slots_allocated;
uint64_t position = ft->requested;
ft->requested += length;
if (m->file_reqchunk)
(*m->file_reqchunk)(m, friendnumber, i, position, length, m->file_reqchunk_userdata);
--free_slots;
}
if (num == 0)
break;
}
}
/* Run this when the friend disconnects.
* Kill all current file transfers.
*/
static void break_files(const Messenger *m, int32_t friendnumber)
{
uint32_t i;
//TODO: Inform the client which file transfers get killed with a callback?
for (i = 0; i < MAX_CONCURRENT_FILE_PIPES; ++i) {
if (m->friendlist[friendnumber].file_sending[i].status != FILESTATUS_NONE)
m->friendlist[friendnumber].file_sending[i].status = FILESTATUS_NONE;
if (m->friendlist[friendnumber].file_receiving[i].status != FILESTATUS_NONE)
m->friendlist[friendnumber].file_receiving[i].status = FILESTATUS_NONE;
}
}
/* return -1 on failure, 0 on success.
*/
static int handle_filecontrol(Messenger *m, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber,
uint8_t control_type, uint8_t *data, uint16_t length)
{
if (receive_send > 1)
return -1;
if (control_type > FILECONTROL_SEEK)
return -1;
uint32_t real_filenumber = filenumber;
struct File_Transfers *ft;
if (receive_send == 0) {
real_filenumber += 1;
real_filenumber <<= 16;
ft = &m->friendlist[friendnumber].file_receiving[filenumber];
} else {
ft = &m->friendlist[friendnumber].file_sending[filenumber];
}
if (ft->status == FILESTATUS_NONE) {
/* File transfer doesn't exist, tell the other to kill it. */
send_file_control_packet(m, friendnumber, !receive_send, filenumber, FILECONTROL_KILL, 0, 0);
return -1;
}
if (control_type == FILECONTROL_ACCEPT) {
if (receive_send && ft->status == FILESTATUS_NOT_ACCEPTED) {
ft->status = FILESTATUS_TRANSFERRING;
} else {
if (ft->paused & FILE_PAUSE_OTHER) {
ft->paused ^= FILE_PAUSE_OTHER;
} else {
return -1;
}
}
if (m->file_filecontrol)
(*m->file_filecontrol)(m, friendnumber, real_filenumber, control_type, m->file_filecontrol_userdata);
} else if (control_type == FILECONTROL_PAUSE) {
if ((ft->paused & FILE_PAUSE_OTHER) || ft->status != FILESTATUS_TRANSFERRING) {
return -1;
}
ft->paused |= FILE_PAUSE_OTHER;
if (m->file_filecontrol)
(*m->file_filecontrol)(m, friendnumber, real_filenumber, control_type, m->file_filecontrol_userdata);
} else if (control_type == FILECONTROL_KILL) {
if (m->file_filecontrol)
(*m->file_filecontrol)(m, friendnumber, real_filenumber, control_type, m->file_filecontrol_userdata);
ft->status = FILESTATUS_NONE;
if (receive_send) {
--m->friendlist[friendnumber].num_sending_files;
}
} else if (control_type == FILECONTROL_SEEK) {
uint64_t position;
if (length != sizeof(position)) {
return -1;
}
/* seek can only be sent by the receiver to seek before resuming broken transfers. */
if (ft->status != FILESTATUS_NOT_ACCEPTED || !receive_send) {
return -1;
}
memcpy(&position, data, sizeof(position));
net_to_host((uint8_t *) &position, sizeof(position));
if (position >= ft->size) {
return -1;
}
ft->transferred = ft->requested = position;
} else {
return -1;
}
return 0;
}
/**************************************/
/* Set the callback for msi packets.
*
* Function(Messenger *m, int friendnumber, uint8_t *data, uint16_t length, void *userdata)
*/
void m_callback_msi_packet(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, uint16_t, void *),
void *userdata)
{
m->msi_packet = function;
m->msi_packet_userdata = userdata;
}
/* Send an msi packet.
*
* return 1 on success
* return 0 on failure
*/
int m_msi_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length)
{
return write_cryptpacket_id(m, friendnumber, PACKET_ID_MSI, data, length, 0);
}
static int handle_custom_lossy_packet(void *object, int friend_num, const uint8_t *packet, uint16_t length)
{
Messenger *m = object;
if (friend_not_valid(m, friend_num))
return 1;
if (packet[0] < (PACKET_ID_LOSSY_RANGE_START + PACKET_LOSSY_AV_RESERVED)) {
if (m->friendlist[friend_num].lossy_rtp_packethandlers[packet[0] % PACKET_LOSSY_AV_RESERVED].function)
return m->friendlist[friend_num].lossy_rtp_packethandlers[packet[0] % PACKET_LOSSY_AV_RESERVED].function(
m, friend_num, packet, length, m->friendlist[friend_num].lossy_rtp_packethandlers[packet[0] %
PACKET_LOSSY_AV_RESERVED].object);
return 1;
}
if (m->lossy_packethandler)
m->lossy_packethandler(m, friend_num, packet, length, m->lossy_packethandler_userdata);
return 1;
}
void custom_lossy_packet_registerhandler(Messenger *m, void (*packet_handler_callback)(Messenger *m,
uint32_t friendnumber, const uint8_t *data, size_t len, void *object), void *object)
{
m->lossy_packethandler = packet_handler_callback;
m->lossy_packethandler_userdata = object;
}
int m_callback_rtp_packet(Messenger *m, int32_t friendnumber, uint8_t byte, int (*packet_handler_callback)(Messenger *m,
uint32_t friendnumber, const uint8_t *data, uint16_t len, void *object), void *object)
{
if (friend_not_valid(m, friendnumber))
return -1;
if (byte < PACKET_ID_LOSSY_RANGE_START)
return -1;
if (byte >= (PACKET_ID_LOSSY_RANGE_START + PACKET_LOSSY_AV_RESERVED))
return -1;
m->friendlist[friendnumber].lossy_rtp_packethandlers[byte % PACKET_LOSSY_AV_RESERVED].function =
packet_handler_callback;
m->friendlist[friendnumber].lossy_rtp_packethandlers[byte % PACKET_LOSSY_AV_RESERVED].object = object;
return 0;
}
int send_custom_lossy_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length)
{
if (friend_not_valid(m, friendnumber))
return -1;
if (length == 0 || length > MAX_CRYPTO_DATA_SIZE)
return -2;
if (data[0] < PACKET_ID_LOSSY_RANGE_START)
return -3;
if (data[0] >= (PACKET_ID_LOSSY_RANGE_START + PACKET_ID_LOSSY_RANGE_SIZE))
return -3;
if (m->friendlist[friendnumber].status != FRIEND_ONLINE)
return -4;
if (send_lossy_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c,
m->friendlist[friendnumber].friendcon_id), data, length) == -1) {
return -5;
} else {
return 0;
}
}
static int handle_custom_lossless_packet(void *object, int friend_num, const uint8_t *packet, uint16_t length)
{
Messenger *m = object;
if (friend_not_valid(m, friend_num))
return -1;
if (packet[0] < PACKET_ID_LOSSLESS_RANGE_START)
return -1;
if (packet[0] >= (PACKET_ID_LOSSLESS_RANGE_START + PACKET_ID_LOSSLESS_RANGE_SIZE))
return -1;
if (m->lossless_packethandler)
m->lossless_packethandler(m, friend_num, packet, length, m->lossless_packethandler_userdata);
return 1;
}
void custom_lossless_packet_registerhandler(Messenger *m, void (*packet_handler_callback)(Messenger *m,
uint32_t friendnumber, const uint8_t *data, size_t len, void *object), void *object)
{
m->lossless_packethandler = packet_handler_callback;
m->lossless_packethandler_userdata = object;
}
int send_custom_lossless_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length)
{
if (friend_not_valid(m, friendnumber))
return -1;
if (length == 0 || length > MAX_CRYPTO_DATA_SIZE)
return -2;
if (data[0] < PACKET_ID_LOSSLESS_RANGE_START)
return -3;
if (data[0] >= (PACKET_ID_LOSSLESS_RANGE_START + PACKET_ID_LOSSLESS_RANGE_SIZE))
return -3;
if (m->friendlist[friendnumber].status != FRIEND_ONLINE)
return -4;
if (write_cryptpacket(m->net_crypto, friend_connection_crypt_connection_id(m->fr_c,
m->friendlist[friendnumber].friendcon_id), data, length, 1) == -1) {
return -5;
} else {
return 0;
}
}
/* Function to filter out some friend requests*/
static int friend_already_added(const uint8_t *real_pk, void *data)
{
const Messenger *m = data;
if (getfriend_id(m, real_pk) == -1)
return 0;
return -1;
}
/* Run this at startup. */
Messenger *new_messenger(Messenger_Options *options, unsigned int *error)
{
Messenger *m = calloc(1, sizeof(Messenger));
if (error)
*error = MESSENGER_ERROR_OTHER;
if ( ! m )
return NULL;
unsigned int net_err = 0;
if (options->udp_disabled) {
/* this is the easiest way to completely disable UDP without changing too much code. */
m->net = calloc(1, sizeof(Networking_Core));
} else {
IP ip;
ip_init(&ip, options->ipv6enabled);
m->net = new_networking_ex(ip, options->port_range[0], options->port_range[1], &net_err);
}
if (m->net == NULL) {
free(m);
if (error && net_err == 1) {
*error = MESSENGER_ERROR_PORT;
}
return NULL;
}
m->dht = new_DHT(m->net);
if (m->dht == NULL) {
kill_networking(m->net);
free(m);
return NULL;
}
m->net_crypto = new_net_crypto(m->dht, &options->proxy_info);
if (m->net_crypto == NULL) {
kill_networking(m->net);
kill_DHT(m->dht);
free(m);
return NULL;
}
m->onion = new_onion(m->dht);
m->onion_a = new_onion_announce(m->dht);
m->onion_c = new_onion_client(m->net_crypto);
m->fr_c = new_friend_connections(m->onion_c);
if (!(m->onion && m->onion_a && m->onion_c)) {
kill_friend_connections(m->fr_c);
kill_onion(m->onion);
kill_onion_announce(m->onion_a);
kill_onion_client(m->onion_c);
kill_net_crypto(m->net_crypto);
kill_DHT(m->dht);
kill_networking(m->net);
free(m);
return NULL;
}
if (options->tcp_server_port) {
m->tcp_server = new_TCP_server(options->ipv6enabled, 1, &options->tcp_server_port, m->dht->self_secret_key, m->onion);
if (m->tcp_server == NULL) {
kill_friend_connections(m->fr_c);
kill_onion(m->onion);
kill_onion_announce(m->onion_a);
kill_onion_client(m->onion_c);
kill_net_crypto(m->net_crypto);
kill_DHT(m->dht);
kill_networking(m->net);
free(m);
if (error)
*error = MESSENGER_ERROR_TCP_SERVER;
return NULL;
}
}
m->options = *options;
friendreq_init(&(m->fr), m->fr_c);
set_nospam(&(m->fr), random_int());
set_filter_function(&(m->fr), &friend_already_added, m);
if (error)
*error = MESSENGER_ERROR_NONE;
return m;
}
/* Run this before closing shop. */
void kill_messenger(Messenger *m)
{
if (!m)
return;
uint32_t i;
if (m->tcp_server) {
kill_TCP_server(m->tcp_server);
}
kill_friend_connections(m->fr_c);
kill_onion(m->onion);
kill_onion_announce(m->onion_a);
kill_onion_client(m->onion_c);
kill_net_crypto(m->net_crypto);
kill_DHT(m->dht);
kill_networking(m->net);
for (i = 0; i < m->numfriends; ++i) {
clear_receipts(m, i);
}
free(m->friendlist);
free(m);
}
/* Check for and handle a timed-out friend request. If the request has
* timed-out then the friend status is set back to FRIEND_ADDED.
* i: friendlist index of the timed-out friend
* t: time
*/
static void check_friend_request_timed_out(Messenger *m, uint32_t i, uint64_t t)
{
Friend *f = &m->friendlist[i];
if (f->friendrequest_lastsent + f->friendrequest_timeout < t) {
set_friend_status(m, i, FRIEND_ADDED);
/* Double the default timeout every time if friendrequest is assumed
* to have been sent unsuccessfully.
*/
f->friendrequest_timeout *= 2;
}
}
static int handle_status(void *object, int i, uint8_t status)
{
Messenger *m = object;
if (status) { /* Went online. */
send_online_packet(m, i);
} else { /* Went offline. */
if (m->friendlist[i].status == FRIEND_ONLINE) {
set_friend_status(m, i, FRIEND_CONFIRMED);
}
}
return 0;
}
static int handle_packet(void *object, int i, uint8_t *temp, uint16_t len)
{
if (len == 0)
return -1;
Messenger *m = object;
uint8_t packet_id = temp[0];
uint8_t *data = temp + 1;
uint32_t data_length = len - 1;
if (m->friendlist[i].status != FRIEND_ONLINE) {
if (packet_id == PACKET_ID_ONLINE && len == 1) {
set_friend_status(m, i, FRIEND_ONLINE);
send_online_packet(m, i);
} else {
return -1;
}
}
switch (packet_id) {
case PACKET_ID_OFFLINE: {
if (data_length != 0)
break;
set_friend_status(m, i, FRIEND_CONFIRMED);
break;
}
case PACKET_ID_NICKNAME: {
if (data_length > MAX_NAME_LENGTH)
break;
/* Make sure the NULL terminator is present. */
uint8_t data_terminated[data_length + 1];
memcpy(data_terminated, data, data_length);
data_terminated[data_length] = 0;
/* inform of namechange before we overwrite the old name */
if (m->friend_namechange)
m->friend_namechange(m, i, data_terminated, data_length, m->friend_namechange_userdata);
memcpy(m->friendlist[i].name, data_terminated, data_length);
m->friendlist[i].name_length = data_length;
break;
}
case PACKET_ID_STATUSMESSAGE: {
if (data_length > MAX_STATUSMESSAGE_LENGTH)
break;
/* Make sure the NULL terminator is present. */
uint8_t data_terminated[data_length + 1];
memcpy(data_terminated, data, data_length);
data_terminated[data_length] = 0;
if (m->friend_statusmessagechange)
m->friend_statusmessagechange(m, i, data_terminated, data_length,
m->friend_statusmessagechange_userdata);
set_friend_statusmessage(m, i, data_terminated, data_length);
break;
}
case PACKET_ID_USERSTATUS: {
if (data_length != 1)
break;
USERSTATUS status = data[0];
if (status >= USERSTATUS_INVALID)
break;
if (m->friend_userstatuschange)
m->friend_userstatuschange(m, i, status, m->friend_userstatuschange_userdata);
set_friend_userstatus(m, i, status);
break;
}
case PACKET_ID_TYPING: {
if (data_length != 1)
break;
_Bool typing = !!data[0];
set_friend_typing(m, i, typing);
if (m->friend_typingchange)
m->friend_typingchange(m, i, typing, m->friend_typingchange_userdata);
break;
}
case PACKET_ID_MESSAGE:
case PACKET_ID_ACTION: {
if (data_length == 0)
break;
const uint8_t *message = data;
uint16_t message_length = data_length;
/* Make sure the NULL terminator is present. */
uint8_t message_terminated[message_length + 1];
memcpy(message_terminated, message, message_length);
message_terminated[message_length] = 0;
uint8_t type = packet_id - PACKET_ID_MESSAGE;
if (m->friend_message)
(*m->friend_message)(m, i, type, message_terminated, message_length, m->friend_message_userdata);
break;
}
case PACKET_ID_INVITE_GROUPCHAT: {
if (data_length == 0)
break;
if (m->group_invite)
(*m->group_invite)(m, i, data, data_length);
break;
}
case PACKET_ID_FILE_SENDREQUEST: {
const unsigned int head_length = 1 + sizeof(uint32_t) + sizeof(uint64_t) + FILE_ID_LENGTH;
if (data_length < head_length)
break;
uint8_t filenumber = data[0];
if (filenumber >= MAX_CONCURRENT_FILE_PIPES)
break;
uint64_t filesize;
uint32_t file_type;
uint16_t filename_length = data_length - head_length;
if (filename_length > MAX_FILENAME_LENGTH)
break;
memcpy(&file_type, data + 1, sizeof(file_type));
file_type = ntohl(file_type);
memcpy(&filesize, data + 1 + sizeof(uint32_t), sizeof(filesize));
net_to_host((uint8_t *) &filesize, sizeof(filesize));
struct File_Transfers *ft = &m->friendlist[i].file_receiving[filenumber];
if (ft->status != FILESTATUS_NONE)
break;
ft->status = FILESTATUS_NOT_ACCEPTED;
ft->size = filesize;
ft->transferred = 0;
ft->paused = FILE_PAUSE_NOT;
memcpy(ft->id, data + 1 + sizeof(uint32_t) + sizeof(uint64_t), FILE_ID_LENGTH);
uint8_t filename_terminated[filename_length + 1];
uint8_t *filename = NULL;
if (filename_length) {
/* Force NULL terminate file name. */
memcpy(filename_terminated, data + head_length, filename_length);
filename_terminated[filename_length] = 0;
filename = filename_terminated;
}
uint32_t real_filenumber = filenumber;
real_filenumber += 1;
real_filenumber <<= 16;
if (m->file_sendrequest)
(*m->file_sendrequest)(m, i, real_filenumber, file_type, filesize, filename, filename_length,
m->file_sendrequest_userdata);
break;
}
case PACKET_ID_FILE_CONTROL: {
if (data_length < 3)
break;
uint8_t send_receive = data[0];
uint8_t filenumber = data[1];
uint8_t control_type = data[2];
if (filenumber >= MAX_CONCURRENT_FILE_PIPES)
break;
if (handle_filecontrol(m, i, send_receive, filenumber, control_type, data + 3, data_length - 3) == -1)
break;
break;
}
case PACKET_ID_FILE_DATA: {
if (data_length < 1)
break;
uint8_t filenumber = data[0];
if (filenumber >= MAX_CONCURRENT_FILE_PIPES)
break;
struct File_Transfers *ft = &m->friendlist[i].file_receiving[filenumber];
if (ft->status != FILESTATUS_TRANSFERRING)
break;
uint64_t position = ft->transferred;
uint32_t real_filenumber = filenumber;
real_filenumber += 1;
real_filenumber <<= 16;
uint16_t file_data_length = (data_length - 1);
uint8_t *file_data;
if (file_data_length == 0) {
file_data = NULL;
} else {
file_data = data + 1;
}
/* Prevent more data than the filesize from being passed to clients. */
if ((ft->transferred + file_data_length) > ft->size) {
file_data_length = ft->size - ft->transferred;
}
if (m->file_filedata)
(*m->file_filedata)(m, i, real_filenumber, position, file_data, file_data_length, m->file_filedata_userdata);
ft->transferred += file_data_length;
if (file_data_length && (ft->transferred >= ft->size || file_data_length != MAX_FILE_DATA_SIZE)) {
file_data_length = 0;
file_data = NULL;
position = ft->transferred;
/* Full file received. */
if (m->file_filedata)
(*m->file_filedata)(m, i, real_filenumber, position, file_data, file_data_length, m->file_filedata_userdata);
}
/* Data is zero, filetransfer is over. */
if (file_data_length == 0) {
ft->status = FILESTATUS_NONE;
}
break;
}
case PACKET_ID_MSI: {
if (data_length == 0)
break;
if (m->msi_packet)
(*m->msi_packet)(m, i, data, data_length, m->msi_packet_userdata);
break;
}
default: {
handle_custom_lossless_packet(object, i, temp, len);
break;
}
}
return 0;
}
void do_friends(Messenger *m)
{
uint32_t i;
uint64_t temp_time = unix_time();
for (i = 0; i < m->numfriends; ++i) {
if (m->friendlist[i].status == FRIEND_ADDED) {
int fr = send_friend_request_packet(m->fr_c, m->friendlist[i].friendcon_id, m->friendlist[i].friendrequest_nospam,
m->friendlist[i].info,
m->friendlist[i].info_size);
if (fr >= 0) {
set_friend_status(m, i, FRIEND_REQUESTED);
m->friendlist[i].friendrequest_lastsent = temp_time;
}
}
if (m->friendlist[i].status == FRIEND_REQUESTED
|| m->friendlist[i].status == FRIEND_CONFIRMED) { /* friend is not online. */
if (m->friendlist[i].status == FRIEND_REQUESTED) {
/* If we didn't connect to friend after successfully sending him a friend request the request is deemed
* unsuccessful so we set the status back to FRIEND_ADDED and try again.
*/
check_friend_request_timed_out(m, i, temp_time);
}
}
if (m->friendlist[i].status == FRIEND_ONLINE) { /* friend is online. */
if (m->friendlist[i].name_sent == 0) {
if (m_sendname(m, i, m->name, m->name_length))
m->friendlist[i].name_sent = 1;
}
if (m->friendlist[i].statusmessage_sent == 0) {
if (send_statusmessage(m, i, m->statusmessage, m->statusmessage_length))
m->friendlist[i].statusmessage_sent = 1;
}
if (m->friendlist[i].userstatus_sent == 0) {
if (send_userstatus(m, i, m->userstatus))
m->friendlist[i].userstatus_sent = 1;
}
if (m->friendlist[i].user_istyping_sent == 0) {
if (send_user_istyping(m, i, m->friendlist[i].user_istyping))
m->friendlist[i].user_istyping_sent = 1;
}
check_friend_tcp_udp(m, i);
do_receipts(m, i);
do_reqchunk_filecb(m, i);
m->friendlist[i].last_seen_time = (uint64_t) time(NULL);
}
}
}
static void connection_status_cb(Messenger *m)
{
unsigned int conn_status = onion_connection_status(m->onion_c);
if (conn_status != m->last_connection_status) {
if (m->core_connection_change)
(*m->core_connection_change)(m, conn_status, m->core_connection_change_userdata);
m->last_connection_status = conn_status;
}
}
#ifdef TOX_LOGGER
#define DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS 60UL
static time_t lastdump = 0;
static char IDString[crypto_box_PUBLICKEYBYTES * 2 + 1];
static char *ID2String(const uint8_t *pk)
{
uint32_t i;
for (i = 0; i < crypto_box_PUBLICKEYBYTES; i++)
sprintf(&IDString[i * 2], "%02X", pk[i]);
IDString[crypto_box_PUBLICKEYBYTES * 2] = 0;
return IDString;
}
#endif
/* Minimum messenger run interval in ms
TODO: A/V */
#define MIN_RUN_INTERVAL 50
/* Return the time in milliseconds before do_messenger() should be called again
* for optimal performance.
*
* returns time (in ms) before the next do_messenger() needs to be run on success.
*/
uint32_t messenger_run_interval(const Messenger *m)
{
uint32_t crypto_interval = crypto_run_interval(m->net_crypto);
if (crypto_interval > MIN_RUN_INTERVAL) {
return MIN_RUN_INTERVAL;
} else {
return crypto_interval;
}
}
/* The main loop that needs to be run at least 20 times per second. */
void do_messenger(Messenger *m)
{
// Add the TCP relays, but only if this is the first time calling do_messenger
if (m->has_added_relays == 0) {
m->has_added_relays = 1;
int i;
for (i = 0; i < NUM_SAVED_TCP_RELAYS; ++i) {
add_tcp_relay(m->net_crypto, m->loaded_relays[i].ip_port, m->loaded_relays[i].public_key);
}
if (m->tcp_server) {
/* Add self tcp server. */
IP_Port local_ip_port;
local_ip_port.port = m->options.tcp_server_port;
local_ip_port.ip.family = AF_INET;
local_ip_port.ip.ip4.uint32 = INADDR_LOOPBACK;
add_tcp_relay(m->net_crypto, local_ip_port, m->tcp_server->public_key);
}
}
unix_time_update();
if (!m->options.udp_disabled) {
networking_poll(m->net);
do_DHT(m->dht);
}
if (m->tcp_server) {
do_TCP_server(m->tcp_server);
}
do_net_crypto(m->net_crypto);
do_onion_client(m->onion_c);
do_friend_connections(m->fr_c);
do_friends(m);
connection_status_cb(m);
#ifdef TOX_LOGGER
if (unix_time() > lastdump + DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS) {
#ifdef ENABLE_ASSOC_DHT
Assoc_status(m->dht->assoc);
#endif
lastdump = unix_time();
uint32_t client, last_pinged;
for (client = 0; client < LCLIENT_LIST; client++) {
Client_data *cptr = &m->dht->close_clientlist[client];
IPPTsPng *assoc = NULL;
uint32_t a;
for (a = 0, assoc = &cptr->assoc4; a < 2; a++, assoc = &cptr->assoc6)
if (ip_isset(&assoc->ip_port.ip)) {
last_pinged = lastdump - assoc->last_pinged;
if (last_pinged > 999)
last_pinged = 999;
LOGGER_TRACE("C[%2u] %s:%u [%3u] %s",
client, ip_ntoa(&assoc->ip_port.ip), ntohs(assoc->ip_port.port),
last_pinged, ID2String(cptr->public_key));
}
}
uint32_t friend, dhtfriend;
/* dht contains additional "friends" (requests) */
uint32_t num_dhtfriends = m->dht->num_friends;
int32_t m2dht[num_dhtfriends];
int32_t dht2m[num_dhtfriends];
for (friend = 0; friend < num_dhtfriends; friend++) {
m2dht[friend] = -1;
dht2m[friend] = -1;
if (friend >= m->numfriends)
continue;
for (dhtfriend = 0; dhtfriend < m->dht->num_friends; dhtfriend++)
if (id_equal(m->friendlist[friend].real_pk, m->dht->friends_list[dhtfriend].public_key)) {
m2dht[friend] = dhtfriend;
break;
}
}
for (friend = 0; friend < num_dhtfriends; friend++)
if (m2dht[friend] >= 0)
dht2m[m2dht[friend]] = friend;
if (m->numfriends != m->dht->num_friends) {
LOGGER_TRACE("Friend num in DHT %u != friend num in msger %u\n", m->dht->num_friends, m->numfriends);
}
Friend *msgfptr;
DHT_Friend *dhtfptr;
for (friend = 0; friend < num_dhtfriends; friend++) {
if (dht2m[friend] >= 0)
msgfptr = &m->friendlist[dht2m[friend]];
else
msgfptr = NULL;
dhtfptr = &m->dht->friends_list[friend];
if (msgfptr) {
LOGGER_TRACE("F[%2u:%2u] <%s> %s",
dht2m[friend], friend, msgfptr->name,
ID2String(msgfptr->real_pk));
} else {
LOGGER_TRACE("F[--:%2u] %s", friend, ID2String(dhtfptr->public_key));
}
for (client = 0; client < MAX_FRIEND_CLIENTS; client++) {
Client_data *cptr = &dhtfptr->client_list[client];
IPPTsPng *assoc = NULL;
uint32_t a;
for (a = 0, assoc = &cptr->assoc4; a < 2; a++, assoc = &cptr->assoc6)
if (ip_isset(&assoc->ip_port.ip)) {
last_pinged = lastdump - assoc->last_pinged;
if (last_pinged > 999)
last_pinged = 999;
LOGGER_TRACE("F[%2u] => C[%2u] %s:%u [%3u] %s",
friend, client, ip_ntoa(&assoc->ip_port.ip),
ntohs(assoc->ip_port.port), last_pinged,
ID2String(cptr->public_key));
}
}
}
}
#endif /* TOX_LOGGER */
}
/* new messenger format for load/save, more robust and forward compatible */
#define MESSENGER_STATE_COOKIE_GLOBAL 0x15ed1b1f
#define MESSENGER_STATE_COOKIE_TYPE 0x01ce
#define MESSENGER_STATE_TYPE_NOSPAMKEYS 1
#define MESSENGER_STATE_TYPE_DHT 2
#define MESSENGER_STATE_TYPE_FRIENDS 3
#define MESSENGER_STATE_TYPE_NAME 4
#define MESSENGER_STATE_TYPE_STATUSMESSAGE 5
#define MESSENGER_STATE_TYPE_STATUS 6
#define MESSENGER_STATE_TYPE_TCP_RELAY 10
#define MESSENGER_STATE_TYPE_PATH_NODE 11
#define MESSENGER_STATE_TYPE_END 255
#define SAVED_FRIEND_REQUEST_SIZE 1024
#define NUM_SAVED_PATH_NODES 8
struct SAVED_FRIEND {
uint8_t status;
uint8_t real_pk[crypto_box_PUBLICKEYBYTES];
uint8_t info[SAVED_FRIEND_REQUEST_SIZE]; // the data that is sent during the friend requests we do.
uint16_t info_size; // Length of the info.
uint8_t name[MAX_NAME_LENGTH];
uint16_t name_length;
uint8_t statusmessage[MAX_STATUSMESSAGE_LENGTH];
uint16_t statusmessage_length;
uint8_t userstatus;
uint32_t friendrequest_nospam;
uint64_t last_seen_time;
};
static uint32_t saved_friendslist_size(const Messenger *m)
{
return count_friendlist(m) * sizeof(struct SAVED_FRIEND);
}
static uint32_t friends_list_save(const Messenger *m, uint8_t *data)
{
uint32_t i;
uint32_t num = 0;
for (i = 0; i < m->numfriends; i++) {
if (m->friendlist[i].status > 0) {
struct SAVED_FRIEND temp;
memset(&temp, 0, sizeof(struct SAVED_FRIEND));
temp.status = m->friendlist[i].status;
memcpy(temp.real_pk, m->friendlist[i].real_pk, crypto_box_PUBLICKEYBYTES);
if (temp.status < 3) {
if (m->friendlist[i].info_size > SAVED_FRIEND_REQUEST_SIZE) {
memcpy(temp.info, m->friendlist[i].info, SAVED_FRIEND_REQUEST_SIZE);
} else {
memcpy(temp.info, m->friendlist[i].info, m->friendlist[i].info_size);
}
temp.info_size = htons(m->friendlist[i].info_size);
temp.friendrequest_nospam = m->friendlist[i].friendrequest_nospam;
} else {
memcpy(temp.name, m->friendlist[i].name, m->friendlist[i].name_length);
temp.name_length = htons(m->friendlist[i].name_length);
memcpy(temp.statusmessage, m->friendlist[i].statusmessage, m->friendlist[i].statusmessage_length);
temp.statusmessage_length = htons(m->friendlist[i].statusmessage_length);
temp.userstatus = m->friendlist[i].userstatus;
uint8_t last_seen_time[sizeof(uint64_t)];
memcpy(last_seen_time, &m->friendlist[i].last_seen_time, sizeof(uint64_t));
host_to_net(last_seen_time, sizeof(uint64_t));
memcpy(&temp.last_seen_time, last_seen_time, sizeof(uint64_t));
}
memcpy(data + num * sizeof(struct SAVED_FRIEND), &temp, sizeof(struct SAVED_FRIEND));
num++;
}
}
return num * sizeof(struct SAVED_FRIEND);
}
static int friends_list_load(Messenger *m, const uint8_t *data, uint32_t length)
{
if (length % sizeof(struct SAVED_FRIEND) != 0) {
return -1;
}
uint32_t num = length / sizeof(struct SAVED_FRIEND);
uint32_t i;
for (i = 0; i < num; ++i) {
struct SAVED_FRIEND temp;
memcpy(&temp, data + i * sizeof(struct SAVED_FRIEND), sizeof(struct SAVED_FRIEND));
if (temp.status >= 3) {
int fnum = m_addfriend_norequest(m, temp.real_pk);
if (fnum < 0)
continue;
setfriendname(m, fnum, temp.name, ntohs(temp.name_length));
set_friend_statusmessage(m, fnum, temp.statusmessage, ntohs(temp.statusmessage_length));
set_friend_userstatus(m, fnum, temp.userstatus);
uint8_t last_seen_time[sizeof(uint64_t)];
memcpy(last_seen_time, &temp.last_seen_time, sizeof(uint64_t));
net_to_host(last_seen_time, sizeof(uint64_t));
memcpy(&m->friendlist[fnum].last_seen_time, last_seen_time, sizeof(uint64_t));
} else if (temp.status != 0) {
/* TODO: This is not a good way to do this. */
uint8_t address[FRIEND_ADDRESS_SIZE];
id_copy(address, temp.real_pk);
memcpy(address + crypto_box_PUBLICKEYBYTES, &(temp.friendrequest_nospam), sizeof(uint32_t));
uint16_t checksum = address_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum));
memcpy(address + crypto_box_PUBLICKEYBYTES + sizeof(uint32_t), &checksum, sizeof(checksum));
m_addfriend(m, address, temp.info, ntohs(temp.info_size));
}
}
return num;
}
/* return size of the messenger data (for saving) */
uint32_t messenger_size(const Messenger *m)
{
uint32_t size32 = sizeof(uint32_t), sizesubhead = size32 * 2;
return size32 * 2 // global cookie
+ sizesubhead + sizeof(uint32_t) + crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES
+ sizesubhead + DHT_size(m->dht) // DHT
+ sizesubhead + saved_friendslist_size(m) // Friendlist itself.
+ sizesubhead + m->name_length // Own nickname.
+ sizesubhead + m->statusmessage_length // status message
+ sizesubhead + 1 // status
+ sizesubhead + NUM_SAVED_TCP_RELAYS * packed_node_size(TCP_INET6) //TCP relays
+ sizesubhead + NUM_SAVED_PATH_NODES * packed_node_size(TCP_INET6) //saved path nodes
+ sizesubhead;
}
static uint8_t *z_state_save_subheader(uint8_t *data, uint32_t len, uint16_t type)
{
host_to_lendian32(data, len);
data += sizeof(uint32_t);
host_to_lendian32(data, (host_tolendian16(MESSENGER_STATE_COOKIE_TYPE) << 16) | host_tolendian16(type));
data += sizeof(uint32_t);
return data;
}
/* Save the messenger in data of size Messenger_size(). */
void messenger_save(const Messenger *m, uint8_t *data)
{
memset(data, 0, messenger_size(m));
uint32_t len;
uint16_t type;
uint32_t size32 = sizeof(uint32_t);
memset(data, 0, size32);
data += size32;
host_to_lendian32(data, MESSENGER_STATE_COOKIE_GLOBAL);
data += size32;
#ifdef DEBUG
assert(sizeof(get_nospam(&(m->fr))) == sizeof(uint32_t));
#endif
len = size32 + crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES;
type = MESSENGER_STATE_TYPE_NOSPAMKEYS;
data = z_state_save_subheader(data, len, type);
*(uint32_t *)data = get_nospam(&(m->fr));
save_keys(m->net_crypto, data + size32);
data += len;
len = saved_friendslist_size(m);
type = MESSENGER_STATE_TYPE_FRIENDS;
data = z_state_save_subheader(data, len, type);
friends_list_save(m, data);
data += len;
len = m->name_length;
type = MESSENGER_STATE_TYPE_NAME;
data = z_state_save_subheader(data, len, type);
memcpy(data, m->name, len);
data += len;
len = m->statusmessage_length;
type = MESSENGER_STATE_TYPE_STATUSMESSAGE;
data = z_state_save_subheader(data, len, type);
memcpy(data, m->statusmessage, len);
data += len;
len = 1;
type = MESSENGER_STATE_TYPE_STATUS;
data = z_state_save_subheader(data, len, type);
*data = m->userstatus;
data += len;
len = DHT_size(m->dht);
type = MESSENGER_STATE_TYPE_DHT;
data = z_state_save_subheader(data, len, type);
DHT_save(m->dht, data);
data += len;
Node_format relays[NUM_SAVED_TCP_RELAYS];
type = MESSENGER_STATE_TYPE_TCP_RELAY;
uint8_t *temp_data = data;
data = z_state_save_subheader(temp_data, 0, type);
unsigned int num = copy_connected_tcp_relays(m->net_crypto, relays, NUM_SAVED_TCP_RELAYS);
int l = pack_nodes(data, NUM_SAVED_TCP_RELAYS * packed_node_size(TCP_INET6), relays, num);
if (l > 0) {
len = l;
data = z_state_save_subheader(temp_data, len, type);
data += len;
}
Node_format nodes[NUM_SAVED_PATH_NODES];
type = MESSENGER_STATE_TYPE_PATH_NODE;
temp_data = data;
data = z_state_save_subheader(data, 0, type);
memset(nodes, 0, sizeof(nodes));
num = onion_backup_nodes(m->onion_c, nodes, NUM_SAVED_PATH_NODES);
l = pack_nodes(data, NUM_SAVED_PATH_NODES * packed_node_size(TCP_INET6), nodes, num);
if (l > 0) {
len = l;
data = z_state_save_subheader(temp_data, len, type);
data += len;
}
z_state_save_subheader(data, 0, MESSENGER_STATE_TYPE_END);
}
static int messenger_load_state_callback(void *outer, const uint8_t *data, uint32_t length, uint16_t type)
{
Messenger *m = outer;
switch (type) {
case MESSENGER_STATE_TYPE_NOSPAMKEYS:
if (length == crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES + sizeof(uint32_t)) {
set_nospam(&(m->fr), *(uint32_t *)data);
load_secret_key(m->net_crypto, (&data[sizeof(uint32_t)]) + crypto_box_PUBLICKEYBYTES);
if (public_key_cmp((&data[sizeof(uint32_t)]), m->net_crypto->self_public_key) != 0) {
return -1;
}
} else
return -1; /* critical */
break;
case MESSENGER_STATE_TYPE_DHT:
DHT_load(m->dht, data, length);
break;
case MESSENGER_STATE_TYPE_FRIENDS:
friends_list_load(m, data, length);
break;
case MESSENGER_STATE_TYPE_NAME:
if ((length > 0) && (length <= MAX_NAME_LENGTH)) {
setname(m, data, length);
}
break;
case MESSENGER_STATE_TYPE_STATUSMESSAGE:
if ((length > 0) && (length < MAX_STATUSMESSAGE_LENGTH)) {
m_set_statusmessage(m, data, length);
}
break;
case MESSENGER_STATE_TYPE_STATUS:
if (length == 1) {
m_set_userstatus(m, *data);
}
break;
case MESSENGER_STATE_TYPE_TCP_RELAY: {
if (length == 0) {
break;
}
unpack_nodes(m->loaded_relays, NUM_SAVED_TCP_RELAYS, 0, data, length, 1);
m->has_added_relays = 0;
break;
}
case MESSENGER_STATE_TYPE_PATH_NODE: {
Node_format nodes[NUM_SAVED_PATH_NODES];
if (length == 0) {
break;
}
int i, num = unpack_nodes(nodes, NUM_SAVED_PATH_NODES, 0, data, length, 0);
for (i = 0; i < num; ++i) {
onion_add_bs_path_node(m->onion_c, nodes[i].ip_port, nodes[i].public_key);
}
break;
}
case MESSENGER_STATE_TYPE_END: {
if (length != 0) {
return -1;
}
return -2;
break;
}
#ifdef DEBUG
default:
fprintf(stderr, "Load state: contains unrecognized part (len %u, type %u)\n",
length, type);
break;
#endif
}
return 0;
}
/* Load the messenger from data of size length. */
int messenger_load(Messenger *m, const uint8_t *data, uint32_t length)
{
uint32_t data32[2];
uint32_t cookie_len = sizeof(data32);
if (length < cookie_len)
return -1;
memcpy(data32, data, sizeof(uint32_t));
lendian_to_host32(data32 + 1, data + sizeof(uint32_t));
if (!data32[0] && (data32[1] == MESSENGER_STATE_COOKIE_GLOBAL))
return load_state(messenger_load_state_callback, m, data + cookie_len,
length - cookie_len, MESSENGER_STATE_COOKIE_TYPE);
else
return -1;
}
/* Return the number of friends in the instance m.
* You should use this to determine how much memory to allocate
* for copy_friendlist. */
uint32_t count_friendlist(const Messenger *m)
{
uint32_t ret = 0;
uint32_t i;
for (i = 0; i < m->numfriends; i++) {
if (m->friendlist[i].status > 0) {
ret++;
}
}
return ret;
}
/* Copy a list of valid friend IDs into the array out_list.
* If out_list is NULL, returns 0.
* Otherwise, returns the number of elements copied.
* If the array was too small, the contents
* of out_list will be truncated to list_size. */
uint32_t copy_friendlist(Messenger const *m, uint32_t *out_list, uint32_t list_size)
{
if (!out_list)
return 0;
if (m->numfriends == 0) {
return 0;
}
uint32_t i;
uint32_t ret = 0;
for (i = 0; i < m->numfriends; i++) {
if (ret >= list_size) {
break; /* Abandon ship */
}
if (m->friendlist[i].status > 0) {
out_list[ret] = i;
ret++;
}
}
return ret;
}
================================================
FILE: toxcore/Messenger.h
================================================
/* Messenger.h
*
* An implementation of a simple text chat only messenger on the tox network core.
*
* NOTE: All the text in the messages must be encoded using UTF-8
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef MESSENGER_H
#define MESSENGER_H
#include "friend_requests.h"
#include "friend_connection.h"
#define MAX_NAME_LENGTH 128
/* TODO: this must depend on other variable. */
#define MAX_STATUSMESSAGE_LENGTH 1007
#define FRIEND_ADDRESS_SIZE (crypto_box_PUBLICKEYBYTES + sizeof(uint32_t) + sizeof(uint16_t))
enum {
MESSAGE_NORMAL,
MESSAGE_ACTION
};
/* NOTE: Packet ids below 24 must never be used. */
#define PACKET_ID_ONLINE 24
#define PACKET_ID_OFFLINE 25
#define PACKET_ID_NICKNAME 48
#define PACKET_ID_STATUSMESSAGE 49
#define PACKET_ID_USERSTATUS 50
#define PACKET_ID_TYPING 51
#define PACKET_ID_MESSAGE 64
#define PACKET_ID_ACTION (PACKET_ID_MESSAGE + MESSAGE_ACTION) /* 65 */
#define PACKET_ID_MSI 69
#define PACKET_ID_FILE_SENDREQUEST 80
#define PACKET_ID_FILE_CONTROL 81
#define PACKET_ID_FILE_DATA 82
#define PACKET_ID_INVITE_GROUPCHAT 96
#define PACKET_ID_ONLINE_PACKET 97
#define PACKET_ID_DIRECT_GROUPCHAT 98
#define PACKET_ID_MESSAGE_GROUPCHAT 99
#define PACKET_ID_LOSSY_GROUPCHAT 199
/* All packets starting with a byte in this range can be used for anything. */
#define PACKET_ID_LOSSLESS_RANGE_START 160
#define PACKET_ID_LOSSLESS_RANGE_SIZE 32
#define PACKET_LOSSY_AV_RESERVED 8 /* Number of lossy packet types at start of range reserved for A/V. */
typedef struct {
uint8_t ipv6enabled;
uint8_t udp_disabled;
TCP_Proxy_Info proxy_info;
uint16_t port_range[2];
uint16_t tcp_server_port;
} Messenger_Options;
struct Receipts {
uint32_t packet_num;
uint32_t msg_id;
struct Receipts *next;
};
/* Status definitions. */
enum {
NOFRIEND,
FRIEND_ADDED,
FRIEND_REQUESTED,
FRIEND_CONFIRMED,
FRIEND_ONLINE,
};
/* Errors for m_addfriend
* FAERR - Friend Add Error
*/
enum {
FAERR_TOOLONG = -1,
FAERR_NOMESSAGE = -2,
FAERR_OWNKEY = -3,
FAERR_ALREADYSENT = -4,
FAERR_BADCHECKSUM = -6,
FAERR_SETNEWNOSPAM = -7,
FAERR_NOMEM = -8
};
/* Default start timeout in seconds between friend requests. */
#define FRIENDREQUEST_TIMEOUT 5;
enum {
CONNECTION_NONE,
CONNECTION_TCP,
CONNECTION_UDP,
CONNECTION_UNKNOWN
};
/* USERSTATUS -
* Represents userstatuses someone can have.
*/
typedef enum {
USERSTATUS_NONE,
USERSTATUS_AWAY,
USERSTATUS_BUSY,
USERSTATUS_INVALID
}
USERSTATUS;
#define FILE_ID_LENGTH 32
struct File_Transfers {
uint64_t size;
uint64_t transferred;
uint8_t status; /* 0 == no transfer, 1 = not accepted, 3 = transferring, 4 = broken, 5 = finished */
uint8_t paused; /* 0: not paused, 1 = paused by us, 2 = paused by other, 3 = paused by both. */
uint32_t last_packet_number; /* number of the last packet sent. */
uint64_t requested; /* total data requested by the request chunk callback */
unsigned int slots_allocated; /* number of slots allocated to this transfer. */
uint8_t id[FILE_ID_LENGTH];
};
enum {
FILESTATUS_NONE,
FILESTATUS_NOT_ACCEPTED,
FILESTATUS_TRANSFERRING,
//FILESTATUS_BROKEN,
FILESTATUS_FINISHED
};
enum {
FILE_PAUSE_NOT,
FILE_PAUSE_US,
FILE_PAUSE_OTHER,
FILE_PAUSE_BOTH
};
/* This cannot be bigger than 256 */
#define MAX_CONCURRENT_FILE_PIPES 256
enum {
FILECONTROL_ACCEPT,
FILECONTROL_PAUSE,
FILECONTROL_KILL,
FILECONTROL_SEEK
};
enum {
FILEKIND_DATA,
FILEKIND_AVATAR
};
typedef struct Messenger Messenger;
typedef struct {
uint8_t real_pk[crypto_box_PUBLICKEYBYTES];
int friendcon_id;
uint64_t friendrequest_lastsent; // Time at which the last friend request was sent.
uint32_t friendrequest_timeout; // The timeout between successful friendrequest sending attempts.
uint8_t status; // 0 if no friend, 1 if added, 2 if friend request sent, 3 if confirmed friend, 4 if online.
uint8_t info[MAX_FRIEND_REQUEST_DATA_SIZE]; // the data that is sent during the friend requests we do.
uint8_t name[MAX_NAME_LENGTH];
uint16_t name_length;
uint8_t name_sent; // 0 if we didn't send our name to this friend 1 if we have.
uint8_t statusmessage[MAX_STATUSMESSAGE_LENGTH];
uint16_t statusmessage_length;
uint8_t statusmessage_sent;
USERSTATUS userstatus;
uint8_t userstatus_sent;
uint8_t user_istyping;
uint8_t user_istyping_sent;
uint8_t is_typing;
uint16_t info_size; // Length of the info.
uint32_t message_id; // a semi-unique id used in read receipts.
uint32_t friendrequest_nospam; // The nospam number used in the friend request.
uint64_t last_seen_time;
uint8_t last_connection_udp_tcp;
struct File_Transfers file_sending[MAX_CONCURRENT_FILE_PIPES];
unsigned int num_sending_files;
struct File_Transfers file_receiving[MAX_CONCURRENT_FILE_PIPES];
struct {
int (*function)(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t len, void *object);
void *object;
} lossy_rtp_packethandlers[PACKET_LOSSY_AV_RESERVED];
struct Receipts *receipts_start;
struct Receipts *receipts_end;
} Friend;
struct Messenger {
Networking_Core *net;
Net_Crypto *net_crypto;
DHT *dht;
Onion *onion;
Onion_Announce *onion_a;
Onion_Client *onion_c;
Friend_Connections *fr_c;
TCP_Server *tcp_server;
Friend_Requests fr;
uint8_t name[MAX_NAME_LENGTH];
uint16_t name_length;
uint8_t statusmessage[MAX_STATUSMESSAGE_LENGTH];
uint16_t statusmessage_length;
USERSTATUS userstatus;
Friend *friendlist;
uint32_t numfriends;
#define NUM_SAVED_TCP_RELAYS 8
uint8_t has_added_relays; // If the first connection has occurred in do_messenger
Node_format loaded_relays[NUM_SAVED_TCP_RELAYS]; // Relays loaded from config
void (*friend_message)(struct Messenger *m, uint32_t, unsigned int, const uint8_t *, size_t, void *);
void *friend_message_userdata;
void (*friend_namechange)(struct Messenger *m, uint32_t, const uint8_t *, size_t, void *);
void *friend_namechange_userdata;
void (*friend_statusmessagechange)(struct Messenger *m, uint32_t, const uint8_t *, size_t, void *);
void *friend_statusmessagechange_userdata;
void (*friend_userstatuschange)(struct Messenger *m, uint32_t, unsigned int, void *);
void *friend_userstatuschange_userdata;
void (*friend_typingchange)(struct Messenger *m, uint32_t, _Bool, void *);
void *friend_typingchange_userdata;
void (*read_receipt)(struct Messenger *m, uint32_t, uint32_t, void *);
void *read_receipt_userdata;
void (*friend_connectionstatuschange)(struct Messenger *m, uint32_t, unsigned int, void *);
void *friend_connectionstatuschange_userdata;
void (*friend_connectionstatuschange_internal)(struct Messenger *m, uint32_t, uint8_t, void *);
void *friend_connectionstatuschange_internal_userdata;
void *group_chat_object; /* Set by new_groupchats()*/
void (*group_invite)(struct Messenger *m, uint32_t, const uint8_t *, uint16_t);
void (*group_message)(struct Messenger *m, uint32_t, const uint8_t *, uint16_t);
void (*file_sendrequest)(struct Messenger *m, uint32_t, uint32_t, uint32_t, uint64_t, const uint8_t *, size_t,
void *);
void *file_sendrequest_userdata;
void (*file_filecontrol)(struct Messenger *m, uint32_t, uint32_t, unsigned int, void *);
void *file_filecontrol_userdata;
void (*file_filedata)(struct Messenger *m, uint32_t, uint32_t, uint64_t, const uint8_t *, size_t, void *);
void *file_filedata_userdata;
void (*file_reqchunk)(struct Messenger *m, uint32_t, uint32_t, uint64_t, size_t, void *);
void *file_reqchunk_userdata;
void (*msi_packet)(struct Messenger *m, uint32_t, const uint8_t *, uint16_t, void *);
void *msi_packet_userdata;
void (*lossy_packethandler)(struct Messenger *m, uint32_t, const uint8_t *, size_t, void *);
void *lossy_packethandler_userdata;
void (*lossless_packethandler)(struct Messenger *m, uint32_t, const uint8_t *, size_t, void *);
void *lossless_packethandler_userdata;
void (*core_connection_change)(struct Messenger *m, unsigned int, void *);
void *core_connection_change_userdata;
unsigned int last_connection_status;
Messenger_Options options;
};
/* Format: [real_pk (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)]
*
* return FRIEND_ADDRESS_SIZE byte address to give to others.
*/
void getaddress(const Messenger *m, uint8_t *address);
/* Add a friend.
* Set the data that will be sent along with friend request.
* address is the address of the friend (returned by getaddress of the friend you wish to add) it must be FRIEND_ADDRESS_SIZE bytes. TODO: add checksum.
* data is the data and length is the length.
*
* return the friend number if success.
* return -1 if message length is too long.
* return -2 if no message (message length must be >= 1 byte).
* return -3 if user's own key.
* return -4 if friend request already sent or already a friend.
* return -6 if bad checksum in address.
* return -7 if the friend was already there but the nospam was different.
* (the nospam for that friend was set to the new one).
* return -8 if increasing the friend list size fails.
*/
int32_t m_addfriend(Messenger *m, const uint8_t *address, const uint8_t *data, uint16_t length);
/* Add a friend without sending a friendrequest.
* return the friend number if success.
* return -3 if user's own key.
* return -4 if friend request already sent or already a friend.
* return -6 if bad checksum in address.
* return -8 if increasing the friend list size fails.
*/
int32_t m_addfriend_norequest(Messenger *m, const uint8_t *real_pk);
/* return the friend number associated to that client id.
* return -1 if no such friend.
*/
int32_t getfriend_id(const Messenger *m, const uint8_t *real_pk);
/* Copies the public key associated to that friend id into real_pk buffer.
* Make sure that real_pk is of size crypto_box_PUBLICKEYBYTES.
*
* return 0 if success
* return -1 if failure
*/
int get_real_pk(const Messenger *m, int32_t friendnumber, uint8_t *real_pk);
/* return friend connection id on success.
* return -1 if failure.
*/
int getfriendcon_id(const Messenger *m, int32_t friendnumber);
/* Remove a friend.
*
* return 0 if success
* return -1 if failure
*/
int m_delfriend(Messenger *m, int32_t friendnumber);
/* Checks friend's connecting status.
*
* return CONNECTION_UDP (2) if friend is directly connected to us (Online UDP).
* return CONNECTION_TCP (1) if friend is connected to us (Online TCP).
* return CONNECTION_NONE (0) if friend is not connected to us (Offline).
* return -1 on failure.
*/
int m_get_friend_connectionstatus(const Messenger *m, int32_t friendnumber);
/* Checks if there exists a friend with given friendnumber.
*
* return 1 if friend exists.
* return 0 if friend doesn't exist.
*/
int m_friend_exists(const Messenger *m, int32_t friendnumber);
/* Send a message of type to an online friend.
*
* return -1 if friend not valid.
* return -2 if too large.
* return -3 if friend not online.
* return -4 if send failed (because queue is full).
* return -5 if bad type.
* return 0 if success.
*
* the value in message_id will be passed to your read_receipt callback when the other receives the message.
*/
int m_send_message_generic(Messenger *m, int32_t friendnumber, uint8_t type, const uint8_t *message, uint32_t length,
uint32_t *message_id);
/* Set the name and name_length of a friend.
* name must be a string of maximum MAX_NAME_LENGTH length.
* length must be at least 1 byte.
* length is the length of name with the NULL terminator.
*
* return 0 if success.
* return -1 if failure.
*/
int setfriendname(Messenger *m, int32_t friendnumber, const uint8_t *name, uint16_t length);
/* Set our nickname.
* name must be a string of maximum MAX_NAME_LENGTH length.
* length must be at least 1 byte.
* length is the length of name with the NULL terminator.
*
* return 0 if success.
* return -1 if failure.
*/
int setname(Messenger *m, const uint8_t *name, uint16_t length);
/*
* Get your nickname.
* m - The messenger context to use.
* name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH bytes.
*
* return length of the name.
* return 0 on error.
*/
uint16_t getself_name(const Messenger *m, uint8_t *name);
/* Get name of friendnumber and put it in name.
* name needs to be a valid memory location with a size of at least MAX_NAME_LENGTH (128) bytes.
*
* return length of name if success.
* return -1 if failure.
*/
int getname(const Messenger *m, int32_t friendnumber, uint8_t *name);
/* return the length of name, including null on success.
* return -1 on failure.
*/
int m_get_name_size(const Messenger *m, int32_t friendnumber);
int m_get_self_name_size(const Messenger *m);
/* Set our user status.
* You are responsible for freeing status after.
*
* returns 0 on success.
* returns -1 on failure.
*/
int m_set_statusmessage(Messenger *m, const uint8_t *status, uint16_t length);
int m_set_userstatus(Messenger *m, uint8_t status);
/* return the length of friendnumber's status message, including null on success.
* return -1 on failure.
*/
int m_get_statusmessage_size(const Messenger *m, int32_t friendnumber);
int m_get_self_statusmessage_size(const Messenger *m);
/* Copy friendnumber's status message into buf, truncating if size is over maxlen.
* Get the size you need to allocate from m_get_statusmessage_size.
* The self variant will copy our own status message.
*
* returns the length of the copied data on success
* retruns -1 on failure.
*/
int m_copy_statusmessage(const Messenger *m, int32_t friendnumber, uint8_t *buf, uint32_t maxlen);
int m_copy_self_statusmessage(const Messenger *m, uint8_t *buf);
/* return one of USERSTATUS values.
* Values unknown to your application should be represented as USERSTATUS_NONE.
* As above, the self variant will return our own USERSTATUS.
* If friendnumber is invalid, this shall return USERSTATUS_INVALID.
*/
uint8_t m_get_userstatus(const Messenger *m, int32_t friendnumber);
uint8_t m_get_self_userstatus(const Messenger *m);
/* returns timestamp of last time friendnumber was seen online or 0 if never seen.
* if friendnumber is invalid this function will return UINT64_MAX.
*/
uint64_t m_get_last_online(const Messenger *m, int32_t friendnumber);
/* Set our typing status for a friend.
* You are responsible for turning it on or off.
*
* returns 0 on success.
* returns -1 on failure.
*/
int m_set_usertyping(Messenger *m, int32_t friendnumber, uint8_t is_typing);
/* Get the typing status of a friend.
*
* returns 0 if friend is not typing.
* returns 1 if friend is typing.
*/
int m_get_istyping(const Messenger *m, int32_t friendnumber);
/* Set the function that will be executed when a friend request is received.
* Function format is function(uint8_t * public_key, uint8_t * data, size_t length)
*/
void m_callback_friendrequest(Messenger *m, void (*function)(Messenger *m, const uint8_t *, const uint8_t *, size_t,
void *), void *userdata);
/* Set the function that will be executed when a message from a friend is received.
* Function format is: function(uint32_t friendnumber, unsigned int type, uint8_t * message, uint32_t length)
*/
void m_callback_friendmessage(Messenger *m, void (*function)(Messenger *m, uint32_t, unsigned int, const uint8_t *,
size_t, void *), void *userdata);
/* Set the callback for name changes.
* Function(uint32_t friendnumber, uint8_t *newname, size_t length)
* You are not responsible for freeing newname.
*/
void m_callback_namechange(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, size_t, void *),
void *userdata);
/* Set the callback for status message changes.
* Function(uint32_t friendnumber, uint8_t *newstatus, size_t length)
*
* You are not responsible for freeing newstatus
*/
void m_callback_statusmessage(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, size_t, void *),
void *userdata);
/* Set the callback for status type changes.
* Function(uint32_t friendnumber, USERSTATUS kind)
*/
void m_callback_userstatus(Messenger *m, void (*function)(Messenger *m, uint32_t, unsigned int, void *),
void *userdata);
/* Set the callback for typing changes.
* Function(uint32_t friendnumber, uint8_t is_typing)
*/
void m_callback_typingchange(Messenger *m, void(*function)(Messenger *m, uint32_t, _Bool, void *), void *userdata);
/* Set the callback for read receipts.
* Function(uint32_t friendnumber, uint32_t receipt)
*
* If you are keeping a record of returns from m_sendmessage,
* receipt might be one of those values, meaning the message
* has been received on the other side.
* Since core doesn't track ids for you, receipt may not correspond to any message.
* In that case, you should discard it.
*/
void m_callback_read_receipt(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, void *), void *userdata);
/* Set the callback for connection status changes.
* function(uint32_t friendnumber, uint8_t status)
*
* Status:
* 0 -- friend went offline after being previously online.
* 1 -- friend went online.
*
* Note that this callback is not called when adding friends, thus the "after
* being previously online" part.
* It's assumed that when adding friends, their connection status is offline.
*/
void m_callback_connectionstatus(Messenger *m, void (*function)(Messenger *m, uint32_t, unsigned int, void *),
void *userdata);
/* Same as previous but for internal A/V core usage only */
void m_callback_connectionstatus_internal_av(Messenger *m, void (*function)(Messenger *m, uint32_t, uint8_t, void *),
void *userdata);
/* Set the callback for typing changes.
* Function(unsigned int connection_status (0 = not connected, 1 = TCP only, 2 = UDP + TCP))
*/
void m_callback_core_connection(Messenger *m, void (*function)(Messenger *m, unsigned int, void *), void *userdata);
/**********GROUP CHATS************/
/* Set the callback for group invites.
*
* Function(Messenger *m, uint32_t friendnumber, uint8_t *data, uint16_t length)
*/
void m_callback_group_invite(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, uint16_t));
/* Send a group invite packet.
*
* return 1 on success
* return 0 on failure
*/
int send_group_invite_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length);
/****************FILE SENDING*****************/
/* Set the callback for file send requests.
*
* Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint32_t filetype, uint64_t filesize, uint8_t *filename, size_t filename_length, void *userdata)
*/
void callback_file_sendrequest(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, uint32_t, uint64_t,
const uint8_t *, size_t, void *), void *userdata);
/* Set the callback for file control requests.
*
* Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, unsigned int control_type, void *userdata)
*
*/
void callback_file_control(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, unsigned int, void *),
void *userdata);
/* Set the callback for file data.
*
* Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, uint8_t *data, size_t length, void *userdata)
*
*/
void callback_file_data(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, uint64_t, const uint8_t *,
size_t, void *), void *userdata);
/* Set the callback for file request chunk.
*
* Function(Tox *tox, uint32_t friendnumber, uint32_t filenumber, uint64_t position, size_t length, void *userdata)
*
*/
void callback_file_reqchunk(Messenger *m, void (*function)(Messenger *m, uint32_t, uint32_t, uint64_t, size_t, void *),
void *userdata);
/* Copy the file transfer file id to file_id
*
* return 0 on success.
* return -1 if friend not valid.
* return -2 if filenumber not valid
*/
int file_get_id(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint8_t *file_id);
/* Send a file send request.
* Maximum filename length is 255 bytes.
* return file number on success
* return -1 if friend not found.
* return -2 if filename length invalid.
* return -3 if no more file sending slots left.
* return -4 if could not send packet (friend offline).
*
*/
long int new_filesender(const Messenger *m, int32_t friendnumber, uint32_t file_type, uint64_t filesize,
const uint8_t *file_id, const uint8_t *filename, uint16_t filename_length);
/* Send a file control request.
*
* return 0 on success
* return -1 if friend not valid.
* return -2 if friend not online.
* return -3 if file number invalid.
* return -4 if file control is bad.
* return -5 if file already paused.
* return -6 if resume file failed because it was only paused by the other.
* return -7 if resume file failed because it wasn't paused.
* return -8 if packet failed to send.
*/
int file_control(const Messenger *m, int32_t friendnumber, uint32_t filenumber, unsigned int control);
/* Send a seek file control request.
*
* return 0 on success
* return -1 if friend not valid.
* return -2 if friend not online.
* return -3 if file number invalid.
* return -4 if not receiving file.
* return -5 if file status wrong.
* return -6 if position bad.
* return -8 if packet failed to send.
*/
int file_seek(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position);
/* Send file data.
*
* return 0 on success
* return -1 if friend not valid.
* return -2 if friend not online.
* return -3 if filenumber invalid.
* return -4 if file transfer not transferring.
* return -5 if bad data size.
* return -6 if packet queue full.
* return -7 if wrong position.
*/
int file_data(const Messenger *m, int32_t friendnumber, uint32_t filenumber, uint64_t position, const uint8_t *data,
uint16_t length);
/* Give the number of bytes left to be sent/received.
*
* send_receive is 0 if we want the sending files, 1 if we want the receiving.
*
* return number of bytes remaining to be sent/received on success
* return 0 on failure
*/
uint64_t file_dataremaining(const Messenger *m, int32_t friendnumber, uint8_t filenumber, uint8_t send_receive);
/*************** A/V related ******************/
/* Set the callback for msi packets.
*
* Function(Messenger *m, uint32_t friendnumber, uint8_t *data, uint16_t length, void *userdata)
*/
void m_callback_msi_packet(Messenger *m, void (*function)(Messenger *m, uint32_t, const uint8_t *, uint16_t, void *),
void *userdata);
/* Send an msi packet.
*
* return 1 on success
* return 0 on failure
*/
int m_msi_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint16_t length);
/* Set handlers for lossy rtp packets.
*
* return -1 on failure.
* return 0 on success.
*/
int m_callback_rtp_packet(Messenger *m, int32_t friendnumber, uint8_t byte, int (*packet_handler_callback)(Messenger *m,
uint32_t friendnumber, const uint8_t *data, uint16_t len, void *object), void *object);
/**********************************************/
/* Set handlers for custom lossy packets.
*
*/
void custom_lossy_packet_registerhandler(Messenger *m, void (*packet_handler_callback)(Messenger *m,
uint32_t friendnumber, const uint8_t *data, size_t len, void *object), void *object);
/* High level function to send custom lossy packets.
*
* return -1 if friend invalid.
* return -2 if length wrong.
* return -3 if first byte invalid.
* return -4 if friend offline.
* return -5 if packet failed to send because of other error.
* return 0 on success.
*/
int send_custom_lossy_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length);
/* Set handlers for custom lossless packets.
*
*/
void custom_lossless_packet_registerhandler(Messenger *m, void (*packet_handler_callback)(Messenger *m,
uint32_t friendnumber, const uint8_t *data, size_t len, void *object), void *object);
/* High level function to send custom lossless packets.
*
* return -1 if friend invalid.
* return -2 if length wrong.
* return -3 if first byte invalid.
* return -4 if friend offline.
* return -5 if packet failed to send because of other error.
* return 0 on success.
*/
int send_custom_lossless_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length);
/**********************************************/
enum {
MESSENGER_ERROR_NONE,
MESSENGER_ERROR_PORT,
MESSENGER_ERROR_TCP_SERVER,
MESSENGER_ERROR_OTHER
};
/* Run this at startup.
* return allocated instance of Messenger on success.
* return 0 if there are problems.
*
* if error is not NULL it will be set to one of the values in the enum above.
*/
Messenger *new_messenger(Messenger_Options *options, unsigned int *error);
/* Run this before closing shop
* Free all datastructures.
*/
void kill_messenger(Messenger *m);
/* The main loop that needs to be run at least 20 times per second. */
void do_messenger(Messenger *m);
/* Return the time in milliseconds before do_messenger() should be called again
* for optimal performance.
*
* returns time (in ms) before the next do_messenger() needs to be run on success.
*/
uint32_t messenger_run_interval(const Messenger *m);
/* SAVING AND LOADING FUNCTIONS: */
/* return size of the messenger data (for saving). */
uint32_t messenger_size(const Messenger *m);
/* Save the messenger in data (must be allocated memory of size Messenger_size()) */
void messenger_save(const Messenger *m, uint8_t *data);
/* Load the messenger from data of size length. */
int messenger_load(Messenger *m, const uint8_t *data, uint32_t length);
/* Return the number of friends in the instance m.
* You should use this to determine how much memory to allocate
* for copy_friendlist. */
uint32_t count_friendlist(const Messenger *m);
/* Copy a list of valid friend IDs into the array out_list.
* If out_list is NULL, returns 0.
* Otherwise, returns the number of elements copied.
* If the array was too small, the contents
* of out_list will be truncated to list_size. */
uint32_t copy_friendlist(const Messenger *m, uint32_t *out_list, uint32_t list_size);
#endif
================================================
FILE: toxcore/TCP_client.c
================================================
/*
* TCP_client.c -- Implementation of the TCP relay client part of Tox.
*
* Copyright (C) 2014 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "TCP_client.h"
#if !defined(_WIN32) && !defined(__WIN32__) && !defined (WIN32)
#include
#endif
#include "util.h"
/* return 1 on success
* return 0 on failure
*/
static int connect_sock_to(sock_t sock, IP_Port ip_port, TCP_Proxy_Info *proxy_info)
{
if (proxy_info->proxy_type != TCP_PROXY_NONE) {
ip_port = proxy_info->ip_port;
}
struct sockaddr_storage addr = {0};
size_t addrsize;
if (ip_port.ip.family == AF_INET) {
struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
addrsize = sizeof(struct sockaddr_in);
addr4->sin_family = AF_INET;
addr4->sin_addr = ip_port.ip.ip4.in_addr;
addr4->sin_port = ip_port.port;
} else if (ip_port.ip.family == AF_INET6) {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr;
addrsize = sizeof(struct sockaddr_in6);
addr6->sin6_family = AF_INET6;
addr6->sin6_addr = ip_port.ip.ip6.in6_addr;
addr6->sin6_port = ip_port.port;
} else {
return 0;
}
/* nonblocking socket, connect will never return success */
connect(sock, (struct sockaddr *)&addr, addrsize);
return 1;
}
/* return 1 on success.
* return 0 on failure.
*/
static int proxy_http_generate_connection_request(TCP_Client_Connection *TCP_conn)
{
char one[] = "CONNECT ";
char two[] = " HTTP/1.1\nHost: ";
char three[] = "\r\n\r\n";
char ip[INET6_ADDRSTRLEN];
if (!ip_parse_addr(&TCP_conn->ip_port.ip, ip, sizeof(ip))) {
return 0;
}
const uint16_t port = ntohs(TCP_conn->ip_port.port);
const int written = snprintf((char *)TCP_conn->last_packet, MAX_PACKET_SIZE, "%s%s:%hu%s%s:%hu%s", one, ip, port, two,
ip, port, three);
if (written < 0 || MAX_PACKET_SIZE < written) {
return 0;
}
TCP_conn->last_packet_length = written;
TCP_conn->last_packet_sent = 0;
return 1;
}
/* return 1 on success.
* return 0 if no data received.
* return -1 on failure (connection refused).
*/
static int proxy_http_read_connection_response(TCP_Client_Connection *TCP_conn)
{
char success[] = "200";
uint8_t data[16]; // draining works the best if the length is a power of 2
int ret = read_TCP_packet(TCP_conn->sock, data, sizeof(data) - 1);
if (ret == -1) {
return 0;
}
data[sizeof(data) - 1] = 0;
if (strstr((char *)data, success)) {
// drain all data
unsigned int data_left = TCP_socket_data_recv_buffer(TCP_conn->sock);
if (data_left) {
uint8_t temp_data[data_left];
read_TCP_packet(TCP_conn->sock, temp_data, data_left);
}
return 1;
}
return -1;
}
static void proxy_socks5_generate_handshake(TCP_Client_Connection *TCP_conn)
{
TCP_conn->last_packet[0] = 5; /* SOCKSv5 */
TCP_conn->last_packet[1] = 1; /* number of authentication methods supported */
TCP_conn->last_packet[2] = 0; /* No authentication */
TCP_conn->last_packet_length = 3;
TCP_conn->last_packet_sent = 0;
}
/* return 1 on success.
* return 0 if no data received.
* return -1 on failure (connection refused).
*/
static int socks5_read_handshake_response(TCP_Client_Connection *TCP_conn)
{
uint8_t data[2];
int ret = read_TCP_packet(TCP_conn->sock, data, sizeof(data));
if (ret == -1)
return 0;
if (data[0] == 5 && data[1] == 0) // FIXME magic numbers
return 1;
return -1;
}
static void proxy_socks5_generate_connection_request(TCP_Client_Connection *TCP_conn)
{
TCP_conn->last_packet[0] = 5; /* SOCKSv5 */
TCP_conn->last_packet[1] = 1; /* command code: establish a TCP/IP stream connection */
TCP_conn->last_packet[2] = 0; /* reserved, must be 0 */
uint16_t length = 3;
if (TCP_conn->ip_port.ip.family == AF_INET) {
TCP_conn->last_packet[3] = 1; /* IPv4 address */
++length;
memcpy(TCP_conn->last_packet + length, TCP_conn->ip_port.ip.ip4.uint8, sizeof(IP4));
length += sizeof(IP4);
} else {
TCP_conn->last_packet[3] = 4; /* IPv6 address */
++length;
memcpy(TCP_conn->last_packet + length, TCP_conn->ip_port.ip.ip6.uint8, sizeof(IP6));
length += sizeof(IP6);
}
memcpy(TCP_conn->last_packet + length, &TCP_conn->ip_port.port, sizeof(uint16_t));
length += sizeof(uint16_t);
TCP_conn->last_packet_length = length;
TCP_conn->last_packet_sent = 0;
}
/* return 1 on success.
* return 0 if no data received.
* return -1 on failure (connection refused).
*/
static int proxy_socks5_read_connection_response(TCP_Client_Connection *TCP_conn)
{
if (TCP_conn->ip_port.ip.family == AF_INET) {
uint8_t data[4 + sizeof(IP4) + sizeof(uint16_t)];
int ret = read_TCP_packet(TCP_conn->sock, data, sizeof(data));
if (ret == -1)
return 0;
if (data[0] == 5 && data[1] == 0)
return 1;
} else {
uint8_t data[4 + sizeof(IP6) + sizeof(uint16_t)];
int ret = read_TCP_packet(TCP_conn->sock, data, sizeof(data));
if (ret == -1)
return 0;
if (data[0] == 5 && data[1] == 0)
return 1;
}
return -1;
}
/* return 0 on success.
* return -1 on failure.
*/
static int generate_handshake(TCP_Client_Connection *TCP_conn)
{
uint8_t plain[crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES];
crypto_box_keypair(plain, TCP_conn->temp_secret_key);
random_nonce(TCP_conn->sent_nonce);
memcpy(plain + crypto_box_PUBLICKEYBYTES, TCP_conn->sent_nonce, crypto_box_NONCEBYTES);
memcpy(TCP_conn->last_packet, TCP_conn->self_public_key, crypto_box_PUBLICKEYBYTES);
new_nonce(TCP_conn->last_packet + crypto_box_PUBLICKEYBYTES);
int len = encrypt_data_symmetric(TCP_conn->shared_key, TCP_conn->last_packet + crypto_box_PUBLICKEYBYTES, plain,
sizeof(plain), TCP_conn->last_packet + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES);
if (len != sizeof(plain) + crypto_box_MACBYTES)
return -1;
TCP_conn->last_packet_length = crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + sizeof(plain) + crypto_box_MACBYTES;
TCP_conn->last_packet_sent = 0;
return 0;
}
/* data must be of length TCP_SERVER_HANDSHAKE_SIZE
*
* return 0 on success.
* return -1 on failure.
*/
static int handle_handshake(TCP_Client_Connection *TCP_conn, const uint8_t *data)
{
uint8_t plain[crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES];
int len = decrypt_data_symmetric(TCP_conn->shared_key, data, data + crypto_box_NONCEBYTES,
TCP_SERVER_HANDSHAKE_SIZE - crypto_box_NONCEBYTES, plain);
if (len != sizeof(plain))
return -1;
memcpy(TCP_conn->recv_nonce, plain + crypto_box_PUBLICKEYBYTES, crypto_box_NONCEBYTES);
encrypt_precompute(plain, TCP_conn->temp_secret_key, TCP_conn->shared_key);
sodium_memzero(TCP_conn->temp_secret_key, crypto_box_SECRETKEYBYTES);
return 0;
}
/* return 0 if pending data was sent completely
* return -1 if it wasn't
*/
static int send_pending_data_nonpriority(TCP_Client_Connection *con)
{
if (con->last_packet_length == 0) {
return 0;
}
uint16_t left = con->last_packet_length - con->last_packet_sent;
int len = send(con->sock, con->last_packet + con->last_packet_sent, left, MSG_NOSIGNAL);
if (len <= 0)
return -1;
if (len == left) {
con->last_packet_length = 0;
con->last_packet_sent = 0;
return 0;
}
con->last_packet_sent += len;
return -1;
}
/* return 0 if pending data was sent completely
* return -1 if it wasn't
*/
static int send_pending_data(TCP_Client_Connection *con)
{
/* finish sending current non-priority packet */
if (send_pending_data_nonpriority(con) == -1) {
return -1;
}
TCP_Priority_List *p = con->priority_queue_start;
while (p) {
uint16_t left = p->size - p->sent;
int len = send(con->sock, p->data + p->sent, left, MSG_NOSIGNAL);
if (len != left) {
if (len > 0) {
p->sent += len;
}
break;
}
TCP_Priority_List *pp = p;
p = p->next;
free(pp);
}
con->priority_queue_start = p;
if (!p) {
con->priority_queue_end = NULL;
return 0;
}
return -1;
}
/* return 0 on failure (only if malloc fails)
* return 1 on success
*/
static _Bool add_priority(TCP_Client_Connection *con, const uint8_t *packet, uint16_t size, uint16_t sent)
{
TCP_Priority_List *p = con->priority_queue_end, *new;
new = malloc(sizeof(TCP_Priority_List) + size);
if (!new) {
return 0;
}
new->next = NULL;
new->size = size;
new->sent = sent;
memcpy(new->data, packet, size);
if (p) {
p->next = new;
} else {
con->priority_queue_start = new;
}
con->priority_queue_end = new;
return 1;
}
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure (connection must be killed).
*/
static int write_packet_TCP_secure_connection(TCP_Client_Connection *con, const uint8_t *data, uint16_t length,
_Bool priority)
{
if (length + crypto_box_MACBYTES > MAX_PACKET_SIZE)
return -1;
_Bool sendpriority = 1;
if (send_pending_data(con) == -1) {
if (priority) {
sendpriority = 0;
} else {
return 0;
}
}
uint8_t packet[sizeof(uint16_t) + length + crypto_box_MACBYTES];
uint16_t c_length = htons(length + crypto_box_MACBYTES);
memcpy(packet, &c_length, sizeof(uint16_t));
int len = encrypt_data_symmetric(con->shared_key, con->sent_nonce, data, length, packet + sizeof(uint16_t));
if ((unsigned int)len != (sizeof(packet) - sizeof(uint16_t)))
return -1;
if (priority) {
len = sendpriority ? send(con->sock, packet, sizeof(packet), MSG_NOSIGNAL) : 0;
if (len <= 0) {
len = 0;
}
increment_nonce(con->sent_nonce);
if ((unsigned int)len == sizeof(packet)) {
return 1;
}
return add_priority(con, packet, sizeof(packet), len);
}
len = send(con->sock, packet, sizeof(packet), MSG_NOSIGNAL);
if (len <= 0)
return 0;
increment_nonce(con->sent_nonce);
if ((unsigned int)len == sizeof(packet))
return 1;
memcpy(con->last_packet, packet, sizeof(packet));
con->last_packet_length = sizeof(packet);
con->last_packet_sent = len;
return 1;
}
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure (connection must be killed).
*/
int send_routing_request(TCP_Client_Connection *con, uint8_t *public_key)
{
uint8_t packet[1 + crypto_box_PUBLICKEYBYTES];
packet[0] = TCP_PACKET_ROUTING_REQUEST;
memcpy(packet + 1, public_key, crypto_box_PUBLICKEYBYTES);
return write_packet_TCP_secure_connection(con, packet, sizeof(packet), 1);
}
void routing_response_handler(TCP_Client_Connection *con, int (*response_callback)(void *object, uint8_t connection_id,
const uint8_t *public_key), void *object)
{
con->response_callback = response_callback;
con->response_callback_object = object;
}
void routing_status_handler(TCP_Client_Connection *con, int (*status_callback)(void *object, uint32_t number,
uint8_t connection_id, uint8_t status), void *object)
{
con->status_callback = status_callback;
con->status_callback_object = object;
}
static int send_ping_response(TCP_Client_Connection *con);
static int send_ping_request(TCP_Client_Connection *con);
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure.
*/
int send_data(TCP_Client_Connection *con, uint8_t con_id, const uint8_t *data, uint16_t length)
{
if (con_id >= NUM_CLIENT_CONNECTIONS)
return -1;
if (con->connections[con_id].status != 2)
return -1;
if (send_ping_response(con) == 0 || send_ping_request(con) == 0)
return 0;
uint8_t packet[1 + length];
packet[0] = con_id + NUM_RESERVED_PORTS;
memcpy(packet + 1, data, length);
return write_packet_TCP_secure_connection(con, packet, sizeof(packet), 0);
}
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure.
*/
int send_oob_packet(TCP_Client_Connection *con, const uint8_t *public_key, const uint8_t *data, uint16_t length)
{
if (length == 0 || length > TCP_MAX_OOB_DATA_LENGTH)
return -1;
uint8_t packet[1 + crypto_box_PUBLICKEYBYTES + length];
packet[0] = TCP_PACKET_OOB_SEND;
memcpy(packet + 1, public_key, crypto_box_PUBLICKEYBYTES);
memcpy(packet + 1 + crypto_box_PUBLICKEYBYTES, data, length);
return write_packet_TCP_secure_connection(con, packet, sizeof(packet), 0);
}
/* Set the number that will be used as an argument in the callbacks related to con_id.
*
* When not set by this function, the number is ~0.
*
* return 0 on success.
* return -1 on failure.
*/
int set_tcp_connection_number(TCP_Client_Connection *con, uint8_t con_id, uint32_t number)
{
if (con_id >= NUM_CLIENT_CONNECTIONS)
return -1;
if (con->connections[con_id].status == 0)
return -1;
con->connections[con_id].number = number;
return 0;
}
void routing_data_handler(TCP_Client_Connection *con, int (*data_callback)(void *object, uint32_t number,
uint8_t connection_id, const uint8_t *data, uint16_t length), void *object)
{
con->data_callback = data_callback;
con->data_callback_object = object;
}
void oob_data_handler(TCP_Client_Connection *con, int (*oob_data_callback)(void *object, const uint8_t *public_key,
const uint8_t *data, uint16_t length), void *object)
{
con->oob_data_callback = oob_data_callback;
con->oob_data_callback_object = object;
}
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure (connection must be killed).
*/
static int send_disconnect_notification(TCP_Client_Connection *con, uint8_t id)
{
uint8_t packet[1 + 1];
packet[0] = TCP_PACKET_DISCONNECT_NOTIFICATION;
packet[1] = id;
return write_packet_TCP_secure_connection(con, packet, sizeof(packet), 1);
}
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure (connection must be killed).
*/
static int send_ping_request(TCP_Client_Connection *con)
{
if (!con->ping_request_id)
return 1;
uint8_t packet[1 + sizeof(uint64_t)];
packet[0] = TCP_PACKET_PING;
memcpy(packet + 1, &con->ping_request_id, sizeof(uint64_t));
int ret;
if ((ret = write_packet_TCP_secure_connection(con, packet, sizeof(packet), 1)) == 1) {
con->ping_request_id = 0;
}
return ret;
}
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure (connection must be killed).
*/
static int send_ping_response(TCP_Client_Connection *con)
{
if (!con->ping_response_id)
return 1;
uint8_t packet[1 + sizeof(uint64_t)];
packet[0] = TCP_PACKET_PONG;
memcpy(packet + 1, &con->ping_response_id, sizeof(uint64_t));
int ret;
if ((ret = write_packet_TCP_secure_connection(con, packet, sizeof(packet), 1)) == 1) {
con->ping_response_id = 0;
}
return ret;
}
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure (connection must be killed).
*/
int send_disconnect_request(TCP_Client_Connection *con, uint8_t con_id)
{
if (con_id >= NUM_CLIENT_CONNECTIONS)
return -1;
con->connections[con_id].status = 0;
con->connections[con_id].number = 0;
return send_disconnect_notification(con, con_id + NUM_RESERVED_PORTS);
}
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure (connection must be killed).
*/
int send_onion_request(TCP_Client_Connection *con, const uint8_t *data, uint16_t length)
{
uint8_t packet[1 + length];
packet[0] = TCP_PACKET_ONION_REQUEST;
memcpy(packet + 1, data, length);
return write_packet_TCP_secure_connection(con, packet, sizeof(packet), 0);
}
void onion_response_handler(TCP_Client_Connection *con, int (*onion_callback)(void *object, const uint8_t *data,
uint16_t length), void *object)
{
con->onion_callback = onion_callback;
con->onion_callback_object = object;
}
/* Create new TCP connection to ip_port/public_key
*/
TCP_Client_Connection *new_TCP_connection(IP_Port ip_port, const uint8_t *public_key, const uint8_t *self_public_key,
const uint8_t *self_secret_key, TCP_Proxy_Info *proxy_info)
{
if (networking_at_startup() != 0) {
return NULL;
}
if (ip_port.ip.family != AF_INET && ip_port.ip.family != AF_INET6)
return NULL;
uint8_t family = ip_port.ip.family;
TCP_Proxy_Info default_proxyinfo;
if (proxy_info == NULL) {
default_proxyinfo.proxy_type = TCP_PROXY_NONE;
proxy_info = &default_proxyinfo;
}
if (proxy_info->proxy_type != TCP_PROXY_NONE) {
family = proxy_info->ip_port.ip.family;
}
sock_t sock = socket(family, SOCK_STREAM, IPPROTO_TCP);
if (!sock_valid(sock)) {
return NULL;
}
if (!set_socket_nosigpipe(sock)) {
kill_sock(sock);
return 0;
}
if (!(set_socket_nonblock(sock) && connect_sock_to(sock, ip_port, proxy_info))) {
kill_sock(sock);
return NULL;
}
TCP_Client_Connection *temp = calloc(sizeof(TCP_Client_Connection), 1);
if (temp == NULL) {
kill_sock(sock);
return NULL;
}
temp->sock = sock;
memcpy(temp->public_key, public_key, crypto_box_PUBLICKEYBYTES);
memcpy(temp->self_public_key, self_public_key, crypto_box_PUBLICKEYBYTES);
encrypt_precompute(temp->public_key, self_secret_key, temp->shared_key);
temp->ip_port = ip_port;
temp->proxy_info = *proxy_info;
switch (proxy_info->proxy_type) {
case TCP_PROXY_HTTP:
temp->status = TCP_CLIENT_PROXY_HTTP_CONNECTING;
proxy_http_generate_connection_request(temp);
break;
case TCP_PROXY_SOCKS5:
temp->status = TCP_CLIENT_PROXY_SOCKS5_CONNECTING;
proxy_socks5_generate_handshake(temp);
break;
case TCP_PROXY_NONE:
temp->status = TCP_CLIENT_CONNECTING;
if (generate_handshake(temp) == -1) {
kill_sock(sock);
free(temp);
return NULL;
}
break;
}
temp->kill_at = unix_time() + TCP_CONNECTION_TIMEOUT;
return temp;
}
/* return 0 on success
* return -1 on failure
*/
static int handle_TCP_packet(TCP_Client_Connection *conn, const uint8_t *data, uint16_t length)
{
if (length <= 1)
return -1;
switch (data[0]) {
case TCP_PACKET_ROUTING_RESPONSE: {
if (length != 1 + 1 + crypto_box_PUBLICKEYBYTES)
return -1;
if (data[1] < NUM_RESERVED_PORTS)
return 0;
uint8_t con_id = data[1] - NUM_RESERVED_PORTS;
if (conn->connections[con_id].status != 0)
return 0;
conn->connections[con_id].status = 1;
conn->connections[con_id].number = ~0;
memcpy(conn->connections[con_id].public_key, data + 2, crypto_box_PUBLICKEYBYTES);
if (conn->response_callback)
conn->response_callback(conn->response_callback_object, con_id, conn->connections[con_id].public_key);
return 0;
}
case TCP_PACKET_CONNECTION_NOTIFICATION: {
if (length != 1 + 1)
return -1;
if (data[1] < NUM_RESERVED_PORTS)
return -1;
uint8_t con_id = data[1] - NUM_RESERVED_PORTS;
if (conn->connections[con_id].status != 1)
return 0;
conn->connections[con_id].status = 2;
if (conn->status_callback)
conn->status_callback(conn->status_callback_object, conn->connections[con_id].number, con_id,
conn->connections[con_id].status);
return 0;
}
case TCP_PACKET_DISCONNECT_NOTIFICATION: {
if (length != 1 + 1)
return -1;
if (data[1] < NUM_RESERVED_PORTS)
return -1;
uint8_t con_id = data[1] - NUM_RESERVED_PORTS;
if (conn->connections[con_id].status == 0)
return 0;
if (conn->connections[con_id].status != 2)
return 0;
conn->connections[con_id].status = 1;
if (conn->status_callback)
conn->status_callback(conn->status_callback_object, conn->connections[con_id].number, con_id,
conn->connections[con_id].status);
return 0;
}
case TCP_PACKET_PING: {
if (length != 1 + sizeof(uint64_t))
return -1;
uint64_t ping_id;
memcpy(&ping_id, data + 1, sizeof(uint64_t));
conn->ping_response_id = ping_id;
send_ping_response(conn);
return 0;
}
case TCP_PACKET_PONG: {
if (length != 1 + sizeof(uint64_t))
return -1;
uint64_t ping_id;
memcpy(&ping_id, data + 1, sizeof(uint64_t));
if (ping_id) {
if (ping_id == conn->ping_id) {
conn->ping_id = 0;
}
return 0;
} else {
return -1;
}
}
case TCP_PACKET_OOB_RECV: {
if (length <= 1 + crypto_box_PUBLICKEYBYTES)
return -1;
if (conn->oob_data_callback)
conn->oob_data_callback(conn->oob_data_callback_object, data + 1, data + 1 + crypto_box_PUBLICKEYBYTES,
length - (1 + crypto_box_PUBLICKEYBYTES));
return 0;
}
case TCP_PACKET_ONION_RESPONSE: {
conn->onion_callback(conn->onion_callback_object, data + 1, length - 1);
return 0;
}
default: {
if (data[0] < NUM_RESERVED_PORTS)
return -1;
uint8_t con_id = data[0] - NUM_RESERVED_PORTS;
if (conn->data_callback)
conn->data_callback(conn->data_callback_object, conn->connections[con_id].number, con_id, data + 1, length - 1);
}
}
return 0;
}
static int do_confirmed_TCP(TCP_Client_Connection *conn)
{
send_pending_data(conn);
send_ping_response(conn);
send_ping_request(conn);
uint8_t packet[MAX_PACKET_SIZE];
int len;
if (is_timeout(conn->last_pinged, TCP_PING_FREQUENCY)) {
uint64_t ping_id = random_64b();
if (!ping_id)
++ping_id;
conn->ping_request_id = conn->ping_id = ping_id;
send_ping_request(conn);
conn->last_pinged = unix_time();
}
if (conn->ping_id && is_timeout(conn->last_pinged, TCP_PING_TIMEOUT)) {
conn->status = TCP_CLIENT_DISCONNECTED;
return 0;
}
while ((len = read_packet_TCP_secure_connection(conn->sock, &conn->next_packet_length, conn->shared_key,
conn->recv_nonce, packet, sizeof(packet)))) {
if (len == -1) {
conn->status = TCP_CLIENT_DISCONNECTED;
break;
}
if (handle_TCP_packet(conn, packet, len) == -1) {
conn->status = TCP_CLIENT_DISCONNECTED;
break;
}
}
return 0;
}
/* Run the TCP connection
*/
void do_TCP_connection(TCP_Client_Connection *TCP_connection)
{
unix_time_update();
if (TCP_connection->status == TCP_CLIENT_DISCONNECTED) {
return;
}
if (TCP_connection->status == TCP_CLIENT_PROXY_HTTP_CONNECTING) {
if (send_pending_data(TCP_connection) == 0) {
int ret = proxy_http_read_connection_response(TCP_connection);
if (ret == -1) {
TCP_connection->kill_at = 0;
TCP_connection->status = TCP_CLIENT_DISCONNECTED;
}
if (ret == 1) {
generate_handshake(TCP_connection);
TCP_connection->status = TCP_CLIENT_CONNECTING;
}
}
}
if (TCP_connection->status == TCP_CLIENT_PROXY_SOCKS5_CONNECTING) {
if (send_pending_data(TCP_connection) == 0) {
int ret = socks5_read_handshake_response(TCP_connection);
if (ret == -1) {
TCP_connection->kill_at = 0;
TCP_connection->status = TCP_CLIENT_DISCONNECTED;
}
if (ret == 1) {
proxy_socks5_generate_connection_request(TCP_connection);
TCP_connection->status = TCP_CLIENT_PROXY_SOCKS5_UNCONFIRMED;
}
}
}
if (TCP_connection->status == TCP_CLIENT_PROXY_SOCKS5_UNCONFIRMED) {
if (send_pending_data(TCP_connection) == 0) {
int ret = proxy_socks5_read_connection_response(TCP_connection);
if (ret == -1) {
TCP_connection->kill_at = 0;
TCP_connection->status = TCP_CLIENT_DISCONNECTED;
}
if (ret == 1) {
generate_handshake(TCP_connection);
TCP_connection->status = TCP_CLIENT_CONNECTING;
}
}
}
if (TCP_connection->status == TCP_CLIENT_CONNECTING) {
if (send_pending_data(TCP_connection) == 0) {
TCP_connection->status = TCP_CLIENT_UNCONFIRMED;
}
}
if (TCP_connection->status == TCP_CLIENT_UNCONFIRMED) {
uint8_t data[TCP_SERVER_HANDSHAKE_SIZE];
int len = read_TCP_packet(TCP_connection->sock, data, sizeof(data));
if (sizeof(data) == len) {
if (handle_handshake(TCP_connection, data) == 0) {
TCP_connection->kill_at = ~0;
TCP_connection->status = TCP_CLIENT_CONFIRMED;
} else {
TCP_connection->kill_at = 0;
TCP_connection->status = TCP_CLIENT_DISCONNECTED;
}
}
}
if (TCP_connection->status == TCP_CLIENT_CONFIRMED) {
do_confirmed_TCP(TCP_connection);
}
if (TCP_connection->kill_at <= unix_time()) {
TCP_connection->status = TCP_CLIENT_DISCONNECTED;
}
}
/* Kill the TCP connection
*/
void kill_TCP_connection(TCP_Client_Connection *TCP_connection)
{
if (TCP_connection == NULL)
return;
wipe_priority_list(TCP_connection->priority_queue_start);
kill_sock(TCP_connection->sock);
sodium_memzero(TCP_connection, sizeof(TCP_Client_Connection));
free(TCP_connection);
}
================================================
FILE: toxcore/TCP_client.h
================================================
/*
* TCP_client.h -- Implementation of the TCP relay client part of Tox.
*
* Copyright (C) 2014 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef TCP_CLIENT_H
#define TCP_CLIENT_H
#include "crypto_core.h"
#include "TCP_server.h"
#define TCP_CONNECTION_TIMEOUT 10
typedef enum {
TCP_PROXY_NONE,
TCP_PROXY_HTTP,
TCP_PROXY_SOCKS5
} TCP_PROXY_TYPE;
typedef struct {
IP_Port ip_port;
uint8_t proxy_type; // a value from TCP_PROXY_TYPE
} TCP_Proxy_Info;
enum {
TCP_CLIENT_NO_STATUS,
TCP_CLIENT_PROXY_HTTP_CONNECTING,
TCP_CLIENT_PROXY_SOCKS5_CONNECTING,
TCP_CLIENT_PROXY_SOCKS5_UNCONFIRMED,
TCP_CLIENT_CONNECTING,
TCP_CLIENT_UNCONFIRMED,
TCP_CLIENT_CONFIRMED,
TCP_CLIENT_DISCONNECTED,
};
typedef struct {
uint8_t status;
sock_t sock;
uint8_t self_public_key[crypto_box_PUBLICKEYBYTES]; /* our public key */
uint8_t public_key[crypto_box_PUBLICKEYBYTES]; /* public key of the server */
IP_Port ip_port; /* The ip and port of the server */
TCP_Proxy_Info proxy_info;
uint8_t recv_nonce[crypto_box_NONCEBYTES]; /* Nonce of received packets. */
uint8_t sent_nonce[crypto_box_NONCEBYTES]; /* Nonce of sent packets. */
uint8_t shared_key[crypto_box_BEFORENMBYTES];
uint16_t next_packet_length;
uint8_t temp_secret_key[crypto_box_SECRETKEYBYTES];
uint8_t last_packet[2 + MAX_PACKET_SIZE];
uint16_t last_packet_length;
uint16_t last_packet_sent;
TCP_Priority_List *priority_queue_start, *priority_queue_end;
uint64_t kill_at;
uint64_t last_pinged;
uint64_t ping_id;
uint64_t ping_response_id;
uint64_t ping_request_id;
struct {
uint8_t status; /* 0 if not used, 1 if other is offline, 2 if other is online. */
uint8_t public_key[crypto_box_PUBLICKEYBYTES];
uint32_t number;
} connections[NUM_CLIENT_CONNECTIONS];
int (*response_callback)(void *object, uint8_t connection_id, const uint8_t *public_key);
void *response_callback_object;
int (*status_callback)(void *object, uint32_t number, uint8_t connection_id, uint8_t status);
void *status_callback_object;
int (*data_callback)(void *object, uint32_t number, uint8_t connection_id, const uint8_t *data, uint16_t length);
void *data_callback_object;
int (*oob_data_callback)(void *object, const uint8_t *public_key, const uint8_t *data, uint16_t length);
void *oob_data_callback_object;
int (*onion_callback)(void *object, const uint8_t *data, uint16_t length);
void *onion_callback_object;
/* Can be used by user. */
void *custom_object;
uint32_t custom_uint;
} TCP_Client_Connection;
/* Create new TCP connection to ip_port/public_key
*/
TCP_Client_Connection *new_TCP_connection(IP_Port ip_port, const uint8_t *public_key, const uint8_t *self_public_key,
const uint8_t *self_secret_key, TCP_Proxy_Info *proxy_info);
/* Run the TCP connection
*/
void do_TCP_connection(TCP_Client_Connection *TCP_connection);
/* Kill the TCP connection
*/
void kill_TCP_connection(TCP_Client_Connection *TCP_connection);
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure (connection must be killed).
*/
int send_onion_request(TCP_Client_Connection *con, const uint8_t *data, uint16_t length);
void onion_response_handler(TCP_Client_Connection *con, int (*onion_callback)(void *object, const uint8_t *data,
uint16_t length), void *object);
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure (connection must be killed).
*/
int send_routing_request(TCP_Client_Connection *con, uint8_t *public_key);
void routing_response_handler(TCP_Client_Connection *con, int (*response_callback)(void *object, uint8_t connection_id,
const uint8_t *public_key), void *object);
void routing_status_handler(TCP_Client_Connection *con, int (*status_callback)(void *object, uint32_t number,
uint8_t connection_id, uint8_t status), void *object);
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure (connection must be killed).
*/
int send_disconnect_request(TCP_Client_Connection *con, uint8_t con_id);
/* Set the number that will be used as an argument in the callbacks related to con_id.
*
* When not set by this function, the number is ~0.
*
* return 0 on success.
* return -1 on failure.
*/
int set_tcp_connection_number(TCP_Client_Connection *con, uint8_t con_id, uint32_t number);
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure.
*/
int send_data(TCP_Client_Connection *con, uint8_t con_id, const uint8_t *data, uint16_t length);
void routing_data_handler(TCP_Client_Connection *con, int (*data_callback)(void *object, uint32_t number,
uint8_t connection_id, const uint8_t *data, uint16_t length), void *object);
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure.
*/
int send_oob_packet(TCP_Client_Connection *con, const uint8_t *public_key, const uint8_t *data, uint16_t length);
void oob_data_handler(TCP_Client_Connection *con, int (*oob_data_callback)(void *object, const uint8_t *public_key,
const uint8_t *data, uint16_t length), void *object);
#endif
================================================
FILE: toxcore/TCP_connection.c
================================================
/* TCP_connection.c
*
* Handles TCP relay connections between two Tox clients.
*
* Copyright (C) 2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "TCP_connection.h"
#include "util.h"
/* Set the size of the array to num.
*
* return -1 if realloc fails.
* return 0 if it succeeds.
*/
#define realloc_tox_array(array, element_size, num, temp_pointer) (num ? (temp_pointer = realloc(array, ((num) * (element_size))), temp_pointer ? (array = temp_pointer, 0) : (-1) ) : (free(array), array = NULL, 0))
/* return 1 if the connections_number is not valid.
* return 0 if the connections_number is valid.
*/
static _Bool connections_number_not_valid(const TCP_Connections *tcp_c, int connections_number)
{
if ((unsigned int)connections_number >= tcp_c->connections_length)
return 1;
if (tcp_c->connections == NULL)
return 1;
if (tcp_c->connections[connections_number].status == TCP_CONN_NONE)
return 1;
return 0;
}
/* return 1 if the tcp_connections_number is not valid.
* return 0 if the tcp_connections_number is valid.
*/
static _Bool tcp_connections_number_not_valid(const TCP_Connections *tcp_c, int tcp_connections_number)
{
if ((unsigned int)tcp_connections_number >= tcp_c->tcp_connections_length)
return 1;
if (tcp_c->tcp_connections == NULL)
return 1;
if (tcp_c->tcp_connections[tcp_connections_number].status == TCP_CONN_NONE)
return 1;
return 0;
}
/* Create a new empty connection.
*
* return -1 on failure.
* return connections_number on success.
*/
static int create_connection(TCP_Connections *tcp_c)
{
uint32_t i;
for (i = 0; i < tcp_c->connections_length; ++i) {
if (tcp_c->connections[i].status == TCP_CONN_NONE)
return i;
}
int id = -1;
TCP_Connection_to *temp_pointer;
if (realloc_tox_array(tcp_c->connections, sizeof(TCP_Connection_to), tcp_c->connections_length + 1,
temp_pointer) == 0) {
id = tcp_c->connections_length;
++tcp_c->connections_length;
memset(&(tcp_c->connections[id]), 0, sizeof(TCP_Connection_to));
}
return id;
}
/* Create a new empty tcp connection.
*
* return -1 on failure.
* return tcp_connections_number on success.
*/
static int create_tcp_connection(TCP_Connections *tcp_c)
{
uint32_t i;
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
if (tcp_c->tcp_connections[i].status == TCP_CONN_NONE)
return i;
}
int id = -1;
TCP_con *temp_pointer;
if (realloc_tox_array(tcp_c->tcp_connections, sizeof(TCP_con), tcp_c->tcp_connections_length + 1, temp_pointer) == 0) {
id = tcp_c->tcp_connections_length;
++tcp_c->tcp_connections_length;
memset(&(tcp_c->tcp_connections[id]), 0, sizeof(TCP_con));
}
return id;
}
/* Wipe a connection.
*
* return -1 on failure.
* return 0 on success.
*/
static int wipe_connection(TCP_Connections *tcp_c, int connections_number)
{
if (connections_number_not_valid(tcp_c, connections_number))
return -1;
uint32_t i;
memset(&(tcp_c->connections[connections_number]), 0 , sizeof(TCP_Connection_to));
for (i = tcp_c->connections_length; i != 0; --i) {
if (tcp_c->connections[i - 1].status != TCP_CONN_NONE)
break;
}
if (tcp_c->connections_length != i) {
tcp_c->connections_length = i;
TCP_Connection_to *temp_pointer;
realloc_tox_array(tcp_c->connections, sizeof(TCP_Connection_to), tcp_c->connections_length, temp_pointer);
}
return 0;
}
/* Wipe a connection.
*
* return -1 on failure.
* return 0 on success.
*/
static int wipe_tcp_connection(TCP_Connections *tcp_c, int tcp_connections_number)
{
if (tcp_connections_number_not_valid(tcp_c, tcp_connections_number))
return -1;
uint32_t i;
memset(&(tcp_c->tcp_connections[tcp_connections_number]), 0 , sizeof(TCP_con));
for (i = tcp_c->tcp_connections_length; i != 0; --i) {
if (tcp_c->tcp_connections[i - 1].status != TCP_CONN_NONE)
break;
}
if (tcp_c->tcp_connections_length != i) {
tcp_c->tcp_connections_length = i;
TCP_con *temp_pointer;
realloc_tox_array(tcp_c->tcp_connections, sizeof(TCP_con), tcp_c->tcp_connections_length, temp_pointer);
}
return 0;
}
static TCP_Connection_to *get_connection(const TCP_Connections *tcp_c, int connections_number)
{
if (connections_number_not_valid(tcp_c, connections_number))
return 0;
return &tcp_c->connections[connections_number];
}
static TCP_con *get_tcp_connection(const TCP_Connections *tcp_c, int tcp_connections_number)
{
if (tcp_connections_number_not_valid(tcp_c, tcp_connections_number))
return 0;
return &tcp_c->tcp_connections[tcp_connections_number];
}
/* Send a packet to the TCP connection.
*
* return -1 on failure.
* return 0 on success.
*/
int send_packet_tcp_connection(TCP_Connections *tcp_c, int connections_number, const uint8_t *packet, uint16_t length)
{
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (!con_to) {
return -1;
}
//TODO: detect and kill bad relays.
//TODO: thread safety?
unsigned int i;
int ret = -1;
_Bool limit_reached = 0;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
uint32_t tcp_con_num = con_to->connections[i].tcp_connection;
uint8_t status = con_to->connections[i].status;
uint8_t connection_id = con_to->connections[i].connection_id;
if (tcp_con_num && status == TCP_CONNECTIONS_STATUS_ONLINE) {
tcp_con_num -= 1;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_con_num);
if (!tcp_con) {
continue;
}
ret = send_data(tcp_con->connection, connection_id, packet, length);
if (ret == 0) {
limit_reached = 1;
}
if (ret == 1) {
break;
}
}
}
if (ret == 1) {
return 0;
} else if (!limit_reached) {
ret = 0;
/* Send oob packets to all relays tied to the connection. */
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
uint32_t tcp_con_num = con_to->connections[i].tcp_connection;
uint8_t status = con_to->connections[i].status;
if (tcp_con_num && status == TCP_CONNECTIONS_STATUS_REGISTERED) {
tcp_con_num -= 1;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_con_num);
if (!tcp_con) {
continue;
}
if (send_oob_packet(tcp_con->connection, con_to->public_key, packet, length) == 1) {
ret += 1;
}
}
}
if (ret >= 1) {
return 0;
} else {
return -1;
}
} else {
return -1;
}
}
/* Return a random TCP connection number for use in send_tcp_onion_request.
*
* TODO: This number is just the index of an array that the elements can
* change without warning.
*
* return TCP connection number on success.
* return -1 on failure.
*/
int get_random_tcp_onion_conn_number(TCP_Connections *tcp_c)
{
unsigned int i, r = rand();
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
unsigned int index = ((i + r) % tcp_c->tcp_connections_length);
if (tcp_c->tcp_connections[index].onion && tcp_c->tcp_connections[index].status == TCP_CONN_CONNECTED) {
return index;
}
}
return -1;
}
/* Send an onion packet via the TCP relay corresponding to tcp_connections_number.
*
* return 0 on success.
* return -1 on failure.
*/
int tcp_send_onion_request(TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *data,
uint16_t length)
{
if (tcp_connections_number >= tcp_c->tcp_connections_length) {
return -1;
}
if (tcp_c->tcp_connections[tcp_connections_number].status == TCP_CONN_CONNECTED) {
int ret = send_onion_request(tcp_c->tcp_connections[tcp_connections_number].connection, data, length);
if (ret == 1)
return 0;
}
return -1;
}
/* Send an oob packet via the TCP relay corresponding to tcp_connections_number.
*
* return 0 on success.
* return -1 on failure.
*/
int tcp_send_oob_packet(TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *public_key,
const uint8_t *packet, uint16_t length)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
return -1;
if (tcp_con->status != TCP_CONN_CONNECTED)
return -1;
int ret = send_oob_packet(tcp_con->connection, public_key, packet, length);
if (ret == 1)
return 0;
return -1;
}
/* Set the callback for TCP data packets.
*/
void set_packet_tcp_connection_callback(TCP_Connections *tcp_c, int (*tcp_data_callback)(void *object, int id,
const uint8_t *data, uint16_t length), void *object)
{
tcp_c->tcp_data_callback = tcp_data_callback;
tcp_c->tcp_data_callback_object = object;
}
/* Set the callback for TCP onion packets.
*/
void set_oob_packet_tcp_connection_callback(TCP_Connections *tcp_c, int (*tcp_oob_callback)(void *object,
const uint8_t *public_key, unsigned int tcp_connections_number, const uint8_t *data, uint16_t length), void *object)
{
tcp_c->tcp_oob_callback = tcp_oob_callback;
tcp_c->tcp_oob_callback_object = object;
}
/* Set the callback for TCP oob data packets.
*/
void set_onion_packet_tcp_connection_callback(TCP_Connections *tcp_c, int (*tcp_onion_callback)(void *object,
const uint8_t *data, uint16_t length), void *object)
{
tcp_c->tcp_onion_callback = tcp_onion_callback;
tcp_c->tcp_onion_callback_object = object;
}
/* Find the TCP connection with public_key.
*
* return connections_number on success.
* return -1 on failure.
*/
static int find_tcp_connection_to(TCP_Connections *tcp_c, const uint8_t *public_key)
{
unsigned int i;
for (i = 0; i < tcp_c->connections_length; ++i) {
TCP_Connection_to *con_to = get_connection(tcp_c, i);
if (con_to) {
if (public_key_cmp(con_to->public_key, public_key) == 0) {
return i;
}
}
}
return -1;
}
/* Find the TCP connection to a relay with relay_pk.
*
* return connections_number on success.
* return -1 on failure.
*/
static int find_tcp_connection_relay(TCP_Connections *tcp_c, const uint8_t *relay_pk)
{
unsigned int i;
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
TCP_con *tcp_con = get_tcp_connection(tcp_c, i);
if (tcp_con) {
if (tcp_con->status == TCP_CONN_SLEEPING) {
if (public_key_cmp(tcp_con->relay_pk, relay_pk) == 0) {
return i;
}
} else {
if (public_key_cmp(tcp_con->connection->public_key, relay_pk) == 0) {
return i;
}
}
}
}
return -1;
}
/* Create a new TCP connection to public_key.
*
* public_key must be the counterpart to the secret key that the other peer used with new_tcp_connections().
*
* id is the id in the callbacks for that connection.
*
* return connections_number on success.
* return -1 on failure.
*/
int new_tcp_connection_to(TCP_Connections *tcp_c, const uint8_t *public_key, int id)
{
if (find_tcp_connection_to(tcp_c, public_key) != -1)
return -1;
int connections_number = create_connection(tcp_c);
if (connections_number == -1)
return -1;
TCP_Connection_to *con_to = &tcp_c->connections[connections_number];
con_to->status = TCP_CONN_VALID;
memcpy(con_to->public_key, public_key, crypto_box_PUBLICKEYBYTES);
con_to->id = id;
return connections_number;
}
/* return 0 on success.
* return -1 on failure.
*/
int kill_tcp_connection_to(TCP_Connections *tcp_c, int connections_number)
{
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (!con_to)
return -1;
unsigned int i;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection) {
unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
continue;
if (tcp_con->status == TCP_CONN_CONNECTED) {
send_disconnect_request(tcp_con->connection, con_to->connections[i].connection_id);
}
if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) {
--tcp_con->lock_count;
if (con_to->status == TCP_CONN_SLEEPING) {
--tcp_con->sleep_count;
}
}
}
}
return wipe_connection(tcp_c, connections_number);
}
/* Set connection status.
*
* status of 1 means we are using the connection.
* status of 0 means we are not using it.
*
* Unused tcp connections will be disconnected from but kept in case they are needed.
*
* return 0 on success.
* return -1 on failure.
*/
int set_tcp_connection_to_status(TCP_Connections *tcp_c, int connections_number, _Bool status)
{
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (!con_to)
return -1;
if (status) {
/* Conection is unsleeping. */
if (con_to->status != TCP_CONN_SLEEPING)
return -1;
unsigned int i;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection) {
unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
continue;
if (tcp_con->status == TCP_CONN_SLEEPING) {
tcp_con->unsleep = 1;
}
}
}
con_to->status = TCP_CONN_VALID;
return 0;
} else {
/* Conection is going to sleep. */
if (con_to->status != TCP_CONN_VALID)
return -1;
unsigned int i;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection) {
unsigned int tcp_connections_number = con_to->connections[i].tcp_connection - 1;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
continue;
if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) {
++tcp_con->sleep_count;
}
}
}
con_to->status = TCP_CONN_SLEEPING;
return 0;
}
}
static _Bool tcp_connection_in_conn(TCP_Connection_to *con_to, unsigned int tcp_connections_number)
{
unsigned int i;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) {
return 1;
}
}
return 0;
}
/* return index on success.
* return -1 on failure.
*/
static int add_tcp_connection_to_conn(TCP_Connection_to *con_to, unsigned int tcp_connections_number)
{
unsigned int i;
if (tcp_connection_in_conn(con_to, tcp_connections_number))
return -1;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection == 0) {
con_to->connections[i].tcp_connection = tcp_connections_number + 1;
con_to->connections[i].status = TCP_CONNECTIONS_STATUS_NONE;
con_to->connections[i].connection_id = 0;
return i;
}
}
return -1;
}
/* return index on success.
* return -1 on failure.
*/
static int rm_tcp_connection_from_conn(TCP_Connection_to *con_to, unsigned int tcp_connections_number)
{
unsigned int i;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) {
con_to->connections[i].tcp_connection = 0;
con_to->connections[i].status = TCP_CONNECTIONS_STATUS_NONE;
con_to->connections[i].connection_id = 0;
return i;
}
}
return -1;
}
/* return number of online connections on success.
* return -1 on failure.
*/
static unsigned int online_tcp_connection_from_conn(TCP_Connection_to *con_to)
{
unsigned int i, count = 0;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection) {
if (con_to->connections[i].status == TCP_CONNECTIONS_STATUS_ONLINE) {
++count;
}
}
}
return count;
}
/* return index on success.
* return -1 on failure.
*/
static int set_tcp_connection_status(TCP_Connection_to *con_to, unsigned int tcp_connections_number,
unsigned int status, uint8_t connection_id)
{
unsigned int i;
for (i = 0; i < MAX_FRIEND_TCP_CONNECTIONS; ++i) {
if (con_to->connections[i].tcp_connection == (tcp_connections_number + 1)) {
if (con_to->connections[i].status == status) {
return -1;
}
con_to->connections[i].status = status;
con_to->connections[i].connection_id = connection_id;
return i;
}
}
return -1;
}
/* Kill a TCP relay connection.
*
* return 0 on success.
* return -1 on failure.
*/
static int kill_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
return -1;
unsigned int i;
for (i = 0; i < tcp_c->connections_length; ++i) {
TCP_Connection_to *con_to = get_connection(tcp_c, i);
if (con_to) {
rm_tcp_connection_from_conn(con_to, tcp_connections_number);
}
}
if (tcp_con->onion) {
--tcp_c->onion_num_conns;
}
kill_TCP_connection(tcp_con->connection);
return wipe_tcp_connection(tcp_c, tcp_connections_number);
}
static int reconnect_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
return -1;
if (tcp_con->status == TCP_CONN_SLEEPING)
return -1;
IP_Port ip_port = tcp_con->connection->ip_port;
uint8_t relay_pk[crypto_box_PUBLICKEYBYTES];
memcpy(relay_pk, tcp_con->connection->public_key, crypto_box_PUBLICKEYBYTES);
kill_TCP_connection(tcp_con->connection);
tcp_con->connection = new_TCP_connection(ip_port, relay_pk, tcp_c->self_public_key, tcp_c->self_secret_key,
&tcp_c->proxy_info);
if (!tcp_con->connection) {
kill_tcp_relay_connection(tcp_c, tcp_connections_number);
return -1;
}
unsigned int i;
for (i = 0; i < tcp_c->connections_length; ++i) {
TCP_Connection_to *con_to = get_connection(tcp_c, i);
if (con_to) {
set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_NONE, 0);
}
}
if (tcp_con->onion) {
--tcp_c->onion_num_conns;
tcp_con->onion = 0;
}
tcp_con->lock_count = 0;
tcp_con->sleep_count = 0;
tcp_con->connected_time = 0;
tcp_con->status = TCP_CONN_VALID;
tcp_con->unsleep = 0;
return 0;
}
static int sleep_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
return -1;
if (tcp_con->status != TCP_CONN_CONNECTED)
return -1;
if (tcp_con->lock_count != tcp_con->sleep_count)
return -1;
tcp_con->ip_port = tcp_con->connection->ip_port;
memcpy(tcp_con->relay_pk, tcp_con->connection->public_key, crypto_box_PUBLICKEYBYTES);
kill_TCP_connection(tcp_con->connection);
tcp_con->connection = NULL;
unsigned int i;
for (i = 0; i < tcp_c->connections_length; ++i) {
TCP_Connection_to *con_to = get_connection(tcp_c, i);
if (con_to) {
set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_NONE, 0);
}
}
if (tcp_con->onion) {
--tcp_c->onion_num_conns;
tcp_con->onion = 0;
}
tcp_con->lock_count = 0;
tcp_con->sleep_count = 0;
tcp_con->connected_time = 0;
tcp_con->status = TCP_CONN_SLEEPING;
tcp_con->unsleep = 0;
return 0;
}
static int unsleep_tcp_relay_connection(TCP_Connections *tcp_c, int tcp_connections_number)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
return -1;
if (tcp_con->status != TCP_CONN_SLEEPING)
return -1;
tcp_con->connection = new_TCP_connection(tcp_con->ip_port, tcp_con->relay_pk, tcp_c->self_public_key,
tcp_c->self_secret_key, &tcp_c->proxy_info);
if (!tcp_con->connection) {
kill_tcp_relay_connection(tcp_c, tcp_connections_number);
return -1;
}
tcp_con->lock_count = 0;
tcp_con->sleep_count = 0;
tcp_con->connected_time = 0;
tcp_con->status = TCP_CONN_VALID;
tcp_con->unsleep = 0;
return 0;
}
/* Send a TCP routing request.
*
* return 0 on success.
* return -1 on failure.
*/
static int send_tcp_relay_routing_request(TCP_Connections *tcp_c, int tcp_connections_number, uint8_t *public_key)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
return -1;
if (tcp_con->status == TCP_CONN_SLEEPING)
return -1;
if (send_routing_request(tcp_con->connection, public_key) != 1)
return -1;
return 0;
}
static int tcp_response_callback(void *object, uint8_t connection_id, const uint8_t *public_key)
{
TCP_Client_Connection *TCP_client_con = object;
TCP_Connections *tcp_c = TCP_client_con->custom_object;
unsigned int tcp_connections_number = TCP_client_con->custom_uint;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
return -1;
int connections_number = find_tcp_connection_to(tcp_c, public_key);
if (connections_number == -1)
return -1;
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (con_to == NULL)
return -1;
if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_REGISTERED, connection_id) == -1)
return -1;
set_tcp_connection_number(tcp_con->connection, connection_id, connections_number);
return 0;
}
static int tcp_status_callback(void *object, uint32_t number, uint8_t connection_id, uint8_t status)
{
TCP_Client_Connection *TCP_client_con = object;
TCP_Connections *tcp_c = TCP_client_con->custom_object;
unsigned int tcp_connections_number = TCP_client_con->custom_uint;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
TCP_Connection_to *con_to = get_connection(tcp_c, number);
if (!con_to || !tcp_con)
return -1;
if (status == 1) {
if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_REGISTERED, connection_id) == -1)
return -1;
--tcp_con->lock_count;
if (con_to->status == TCP_CONN_SLEEPING) {
--tcp_con->sleep_count;
}
} else if (status == 2) {
if (set_tcp_connection_status(con_to, tcp_connections_number, TCP_CONNECTIONS_STATUS_ONLINE, connection_id) == -1)
return -1;
++tcp_con->lock_count;
if (con_to->status == TCP_CONN_SLEEPING) {
++tcp_con->sleep_count;
}
}
return 0;
}
static int tcp_data_callback(void *object, uint32_t number, uint8_t connection_id, const uint8_t *data, uint16_t length)
{
if (length == 0)
return -1;
TCP_Client_Connection *TCP_client_con = object;
TCP_Connections *tcp_c = TCP_client_con->custom_object;
unsigned int tcp_connections_number = TCP_client_con->custom_uint;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
return -1;
TCP_Connection_to *con_to = get_connection(tcp_c, number);
if (!con_to)
return -1;
if (tcp_c->tcp_data_callback)
tcp_c->tcp_data_callback(tcp_c->tcp_data_callback_object, con_to->id, data, length);
return 0;
}
static int tcp_oob_callback(void *object, const uint8_t *public_key, const uint8_t *data, uint16_t length)
{
if (length == 0)
return -1;
TCP_Client_Connection *TCP_client_con = object;
TCP_Connections *tcp_c = TCP_client_con->custom_object;
unsigned int tcp_connections_number = TCP_client_con->custom_uint;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
return -1;
/* TODO: optimize */
int connections_number = find_tcp_connection_to(tcp_c, public_key);
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (con_to && tcp_connection_in_conn(con_to, tcp_connections_number)) {
return tcp_data_callback(object, connections_number, 0, data, length);
} else {
if (tcp_c->tcp_oob_callback)
tcp_c->tcp_oob_callback(tcp_c->tcp_oob_callback_object, public_key, tcp_connections_number, data, length);
}
return 0;
}
static int tcp_onion_callback(void *object, const uint8_t *data, uint16_t length)
{
TCP_Connections *tcp_c = object;
if (tcp_c->tcp_onion_callback)
tcp_c->tcp_onion_callback(tcp_c->tcp_onion_callback_object, data, length);
return 0;
}
/* Set callbacks for the TCP relay connection.
*
* return 0 on success.
* return -1 on failure.
*/
static int tcp_relay_set_callbacks(TCP_Connections *tcp_c, int tcp_connections_number)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
return -1;
TCP_Client_Connection *con = tcp_con->connection;
con->custom_object = tcp_c;
con->custom_uint = tcp_connections_number;
onion_response_handler(con, &tcp_onion_callback, tcp_c);
routing_response_handler(con, &tcp_response_callback, con);
routing_status_handler(con, &tcp_status_callback, con);
routing_data_handler(con, &tcp_data_callback, con);
oob_data_handler(con, &tcp_oob_callback, con);
return 0;
}
static int tcp_relay_on_online(TCP_Connections *tcp_c, int tcp_connections_number)
{
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
return -1;
unsigned int i, sent = 0;
for (i = 0; i < tcp_c->connections_length; ++i) {
TCP_Connection_to *con_to = get_connection(tcp_c, i);
if (con_to) {
if (tcp_connection_in_conn(con_to, tcp_connections_number)) {
if (send_tcp_relay_routing_request(tcp_c, tcp_connections_number, con_to->public_key) == 0) {
++sent;
}
}
}
}
tcp_relay_set_callbacks(tcp_c, tcp_connections_number);
tcp_con->status = TCP_CONN_CONNECTED;
/* If this connection isn't used by any connection, we don't need to wait for them to come online. */
if (sent) {
tcp_con->connected_time = unix_time();
} else {
tcp_con->connected_time = 0;
}
if (tcp_c->onion_status && tcp_c->onion_num_conns < NUM_ONION_TCP_CONNECTIONS) {
tcp_con->onion = 1;
++tcp_c->onion_num_conns;
}
return 0;
}
static int add_tcp_relay_instance(TCP_Connections *tcp_c, IP_Port ip_port, const uint8_t *relay_pk)
{
if (ip_port.ip.family == TCP_INET) {
ip_port.ip.family = AF_INET;
} else if (ip_port.ip.family == TCP_INET6) {
ip_port.ip.family = AF_INET6;
}
if (ip_port.ip.family != AF_INET && ip_port.ip.family != AF_INET6)
return -1;
int tcp_connections_number = create_tcp_connection(tcp_c);
if (tcp_connections_number == -1)
return -1;
TCP_con *tcp_con = &tcp_c->tcp_connections[tcp_connections_number];
tcp_con->connection = new_TCP_connection(ip_port, relay_pk, tcp_c->self_public_key, tcp_c->self_secret_key,
&tcp_c->proxy_info);
if (!tcp_con->connection)
return -1;
tcp_con->status = TCP_CONN_VALID;
return tcp_connections_number;
}
/* Add a TCP relay to the TCP_Connections instance.
*
* return 0 on success.
* return -1 on failure.
*/
int add_tcp_relay_global(TCP_Connections *tcp_c, IP_Port ip_port, const uint8_t *relay_pk)
{
int tcp_connections_number = find_tcp_connection_relay(tcp_c, relay_pk);
if (tcp_connections_number != -1)
return -1;
if (add_tcp_relay_instance(tcp_c, ip_port, relay_pk) == -1)
return -1;
return 0;
}
/* Add a TCP relay tied to a connection.
*
* return 0 on success.
* return -1 on failure.
*/
int add_tcp_number_relay_connection(TCP_Connections *tcp_c, int connections_number, unsigned int tcp_connections_number)
{
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (!con_to)
return -1;
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
return -1;
if (con_to->status != TCP_CONN_SLEEPING && tcp_con->status == TCP_CONN_SLEEPING) {
tcp_con->unsleep = 1;
}
if (add_tcp_connection_to_conn(con_to, tcp_connections_number) == -1)
return -1;
if (tcp_con->status == TCP_CONN_CONNECTED) {
if (send_tcp_relay_routing_request(tcp_c, tcp_connections_number, con_to->public_key) == 0) {
tcp_con->connected_time = unix_time();
}
}
return 0;
}
/* Add a TCP relay tied to a connection.
*
* This should be called with the same relay by two peers who want to create a TCP connection with each other.
*
* return 0 on success.
* return -1 on failure.
*/
int add_tcp_relay_connection(TCP_Connections *tcp_c, int connections_number, IP_Port ip_port, const uint8_t *relay_pk)
{
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (!con_to)
return -1;
int tcp_connections_number = find_tcp_connection_relay(tcp_c, relay_pk);
if (tcp_connections_number != -1) {
return add_tcp_number_relay_connection(tcp_c, connections_number, tcp_connections_number);
} else {
if (online_tcp_connection_from_conn(con_to) >= RECOMMENDED_FRIEND_TCP_CONNECTIONS) {
return -1;
}
int tcp_connections_number = add_tcp_relay_instance(tcp_c, ip_port, relay_pk);
TCP_con *tcp_con = get_tcp_connection(tcp_c, tcp_connections_number);
if (!tcp_con)
return -1;
if (add_tcp_connection_to_conn(con_to, tcp_connections_number) == -1) {
return -1;
}
return 0;
}
}
/* return number of online tcp relays tied to the connection on success.
* return 0 on failure.
*/
unsigned int tcp_connection_to_online_tcp_relays(TCP_Connections *tcp_c, int connections_number)
{
TCP_Connection_to *con_to = get_connection(tcp_c, connections_number);
if (!con_to)
return 0;
return online_tcp_connection_from_conn(con_to);
}
/* Copy a maximum of max_num TCP relays we are connected to to tcp_relays.
* NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6.
*
* return number of relays copied to tcp_relays on success.
* return 0 on failure.
*/
unsigned int tcp_copy_connected_relays(TCP_Connections *tcp_c, Node_format *tcp_relays, uint16_t max_num)
{
unsigned int i, copied = 0, r = rand();
for (i = 0; (i < tcp_c->tcp_connections_length) && (copied < max_num); ++i) {
TCP_con *tcp_con = get_tcp_connection(tcp_c, (i + r) % tcp_c->tcp_connections_length);
if (!tcp_con) {
continue;
}
if (tcp_con->status == TCP_CONN_CONNECTED) {
memcpy(tcp_relays[copied].public_key, tcp_con->connection->public_key, crypto_box_PUBLICKEYBYTES);
tcp_relays[copied].ip_port = tcp_con->connection->ip_port;
if (tcp_relays[copied].ip_port.ip.family == AF_INET) {
tcp_relays[copied].ip_port.ip.family = TCP_INET;
} else if (tcp_relays[copied].ip_port.ip.family == AF_INET6) {
tcp_relays[copied].ip_port.ip.family = TCP_INET6;
}
++copied;
}
}
return copied;
}
/* Set if we want TCP_connection to allocate some connection for onion use.
*
* If status is 1, allocate some connections. if status is 0, don't.
*
* return 0 on success.
* return -1 on failure.
*/
int set_tcp_onion_status(TCP_Connections *tcp_c, _Bool status)
{
if (tcp_c->onion_status == status)
return -1;
if (status) {
unsigned int i;
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
TCP_con *tcp_con = get_tcp_connection(tcp_c, i);
if (tcp_con) {
if (tcp_con->status == TCP_CONN_CONNECTED && !tcp_con->onion) {
++tcp_c->onion_num_conns;
tcp_con->onion = 1;
}
}
if (tcp_c->onion_num_conns >= NUM_ONION_TCP_CONNECTIONS)
break;
}
if (tcp_c->onion_num_conns < NUM_ONION_TCP_CONNECTIONS) {
unsigned int wakeup = NUM_ONION_TCP_CONNECTIONS - tcp_c->onion_num_conns;
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
TCP_con *tcp_con = get_tcp_connection(tcp_c, i);
if (tcp_con) {
if (tcp_con->status == TCP_CONN_SLEEPING) {
tcp_con->unsleep = 1;
}
}
if (!wakeup)
break;
}
}
tcp_c->onion_status = 1;
} else {
unsigned int i;
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
TCP_con *tcp_con = get_tcp_connection(tcp_c, i);
if (tcp_con) {
if (tcp_con->onion) {
--tcp_c->onion_num_conns;
tcp_con->onion = 0;
}
}
}
tcp_c->onion_status = 0;
}
return 0;
}
/* Returns a new TCP_Connections object associated with the secret_key.
*
* In order for others to connect to this instance new_tcp_connection_to() must be called with the
* public_key associated with secret_key.
*
* Returns NULL on failure.
*/
TCP_Connections *new_tcp_connections(const uint8_t *secret_key, TCP_Proxy_Info *proxy_info)
{
if (secret_key == NULL)
return NULL;
TCP_Connections *temp = calloc(1, sizeof(TCP_Connections));
if (temp == NULL)
return NULL;
memcpy(temp->self_secret_key, secret_key, crypto_box_SECRETKEYBYTES);
crypto_scalarmult_curve25519_base(temp->self_public_key, temp->self_secret_key);
temp->proxy_info = *proxy_info;
return temp;
}
static void do_tcp_conns(TCP_Connections *tcp_c)
{
unsigned int i;
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
TCP_con *tcp_con = get_tcp_connection(tcp_c, i);
if (tcp_con) {
if (tcp_con->status != TCP_CONN_SLEEPING) {
do_TCP_connection(tcp_con->connection);
/* callbacks can change TCP connection address. */
tcp_con = get_tcp_connection(tcp_c, i);
if (tcp_con->connection->status == TCP_CLIENT_DISCONNECTED) {
if (tcp_con->status == TCP_CONN_CONNECTED) {
reconnect_tcp_relay_connection(tcp_c, i);
} else {
kill_tcp_relay_connection(tcp_c, i);
}
continue;
}
if (tcp_con->status == TCP_CONN_VALID && tcp_con->connection->status == TCP_CLIENT_CONFIRMED) {
tcp_relay_on_online(tcp_c, i);
}
if (tcp_con->status == TCP_CONN_CONNECTED && !tcp_con->onion && tcp_con->lock_count
&& tcp_con->lock_count == tcp_con->sleep_count
&& is_timeout(tcp_con->connected_time, TCP_CONNECTION_ANNOUNCE_TIMEOUT)) {
sleep_tcp_relay_connection(tcp_c, i);
}
}
if (tcp_con->status == TCP_CONN_SLEEPING && tcp_con->unsleep) {
unsleep_tcp_relay_connection(tcp_c, i);
}
}
}
}
static void kill_nonused_tcp(TCP_Connections *tcp_c)
{
if (tcp_c->tcp_connections_length == 0)
return;
unsigned int i, num_online = 0, num_kill = 0, to_kill[tcp_c->tcp_connections_length];
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
TCP_con *tcp_con = get_tcp_connection(tcp_c, i);
if (tcp_con) {
if (tcp_con->status == TCP_CONN_CONNECTED) {
if (!tcp_con->onion && !tcp_con->lock_count && is_timeout(tcp_con->connected_time, TCP_CONNECTION_ANNOUNCE_TIMEOUT)) {
to_kill[num_kill] = i;
++num_kill;
}
++num_online;
}
}
}
if (num_online <= RECOMMENDED_FRIEND_TCP_CONNECTIONS) {
return;
} else {
unsigned int n = num_online - RECOMMENDED_FRIEND_TCP_CONNECTIONS;
if (n < num_kill)
num_kill = n;
}
for (i = 0; i < num_kill; ++i) {
kill_tcp_relay_connection(tcp_c, to_kill[i]);
}
}
void do_tcp_connections(TCP_Connections *tcp_c)
{
do_tcp_conns(tcp_c);
kill_nonused_tcp(tcp_c);
}
void kill_tcp_connections(TCP_Connections *tcp_c)
{
unsigned int i;
for (i = 0; i < tcp_c->tcp_connections_length; ++i) {
kill_TCP_connection(tcp_c->tcp_connections[i].connection);
}
free(tcp_c->tcp_connections);
free(tcp_c->connections);
free(tcp_c);
}
================================================
FILE: toxcore/TCP_connection.h
================================================
/* TCP_connection.h
*
* Handles TCP relay connections between two Tox clients.
*
* Copyright (C) 2015 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifndef TCP_CONNECTION_H
#define TCP_CONNECTION_H
#include "TCP_client.h"
#define TCP_CONN_NONE 0
#define TCP_CONN_VALID 1
/* NOTE: only used by TCP_con */
#define TCP_CONN_CONNECTED 2
/* Connection is not connected but can be quickly reconnected in case it is needed. */
#define TCP_CONN_SLEEPING 3
#define TCP_CONNECTIONS_STATUS_NONE 0
#define TCP_CONNECTIONS_STATUS_REGISTERED 1
#define TCP_CONNECTIONS_STATUS_ONLINE 2
#define MAX_FRIEND_TCP_CONNECTIONS 6
/* Time until connection to friend gets killed (if it doesn't get locked withing that time) */
#define TCP_CONNECTION_ANNOUNCE_TIMEOUT (TCP_CONNECTION_TIMEOUT)
/* The amount of recommended connections for each friend
NOTE: Must be at most (MAX_FRIEND_TCP_CONNECTIONS / 2) */
#define RECOMMENDED_FRIEND_TCP_CONNECTIONS (MAX_FRIEND_TCP_CONNECTIONS / 2)
/* Number of TCP connections used for onion purposes. */
#define NUM_ONION_TCP_CONNECTIONS RECOMMENDED_FRIEND_TCP_CONNECTIONS
typedef struct {
uint8_t status;
uint8_t public_key[crypto_box_PUBLICKEYBYTES]; /* The dht public key of the peer */
struct {
uint32_t tcp_connection;
unsigned int status;
unsigned int connection_id;
} connections[MAX_FRIEND_TCP_CONNECTIONS];
int id; /* id used in callbacks. */
} TCP_Connection_to;
typedef struct {
uint8_t status;
TCP_Client_Connection *connection;
uint64_t connected_time;
uint32_t lock_count;
uint32_t sleep_count;
_Bool onion;
/* Only used when connection is sleeping. */
IP_Port ip_port;
uint8_t relay_pk[crypto_box_PUBLICKEYBYTES];
_Bool unsleep; /* set to 1 to unsleep connection. */
} TCP_con;
typedef struct {
DHT *dht;
uint8_t self_public_key[crypto_box_PUBLICKEYBYTES];
uint8_t self_secret_key[crypto_box_SECRETKEYBYTES];
TCP_Connection_to *connections;
uint32_t connections_length; /* Length of connections array. */
TCP_con *tcp_connections;
uint32_t tcp_connections_length; /* Length of tcp_connections array. */
int (*tcp_data_callback)(void *object, int id, const uint8_t *data, uint16_t length);
void *tcp_data_callback_object;
int (*tcp_oob_callback)(void *object, const uint8_t *public_key, unsigned int tcp_connections_number,
const uint8_t *data, uint16_t length);
void *tcp_oob_callback_object;
int (*tcp_onion_callback)(void *object, const uint8_t *data, uint16_t length);
void *tcp_onion_callback_object;
TCP_Proxy_Info proxy_info;
_Bool onion_status;
uint16_t onion_num_conns;
} TCP_Connections;
/* Send a packet to the TCP connection.
*
* return -1 on failure.
* return 0 on success.
*/
int send_packet_tcp_connection(TCP_Connections *tcp_c, int connections_number, const uint8_t *packet, uint16_t length);
/* Return a random TCP connection number for use in send_tcp_onion_request.
*
* TODO: This number is just the index of an array that the elements can
* change without warning.
*
* return TCP connection number on success.
* return -1 on failure.
*/
int get_random_tcp_onion_conn_number(TCP_Connections *tcp_c);
/* Send an onion packet via the TCP relay corresponding to tcp_connections_number.
*
* return 0 on success.
* return -1 on failure.
*/
int tcp_send_onion_request(TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *data,
uint16_t length);
/* Set if we want TCP_connection to allocate some connection for onion use.
*
* If status is 1, allocate some connections. if status is 0, don't.
*
* return 0 on success.
* return -1 on failure.
*/
int set_tcp_onion_status(TCP_Connections *tcp_c, _Bool status);
/* Send an oob packet via the TCP relay corresponding to tcp_connections_number.
*
* return 0 on success.
* return -1 on failure.
*/
int tcp_send_oob_packet(TCP_Connections *tcp_c, unsigned int tcp_connections_number, const uint8_t *public_key,
const uint8_t *packet, uint16_t length);
/* Set the callback for TCP data packets.
*/
void set_packet_tcp_connection_callback(TCP_Connections *tcp_c, int (*tcp_data_callback)(void *object, int id,
const uint8_t *data, uint16_t length), void *object);
/* Set the callback for TCP onion packets.
*/
void set_onion_packet_tcp_connection_callback(TCP_Connections *tcp_c, int (*tcp_onion_callback)(void *object,
const uint8_t *data, uint16_t length), void *object);
/* Set the callback for TCP oob data packets.
*/
void set_oob_packet_tcp_connection_callback(TCP_Connections *tcp_c, int (*tcp_oob_callback)(void *object,
const uint8_t *public_key, unsigned int tcp_connections_number, const uint8_t *data, uint16_t length), void *object);
/* Create a new TCP connection to public_key.
*
* public_key must be the counterpart to the secret key that the other peer used with new_tcp_connections().
*
* id is the id in the callbacks for that connection.
*
* return connections_number on success.
* return -1 on failure.
*/
int new_tcp_connection_to(TCP_Connections *tcp_c, const uint8_t *public_key, int id);
/* return 0 on success.
* return -1 on failure.
*/
int kill_tcp_connection_to(TCP_Connections *tcp_c, int connections_number);
/* Set connection status.
*
* status of 1 means we are using the connection.
* status of 0 means we are not using it.
*
* Unused tcp connections will be disconnected from but kept in case they are needed.
*
* return 0 on success.
* return -1 on failure.
*/
int set_tcp_connection_to_status(TCP_Connections *tcp_c, int connections_number, _Bool status);
/* return number of online tcp relays tied to the connection on success.
* return 0 on failure.
*/
unsigned int tcp_connection_to_online_tcp_relays(TCP_Connections *tcp_c, int connections_number);
/* Add a TCP relay tied to a connection.
*
* NOTE: This can only be used during the tcp_oob_callback.
*
* return 0 on success.
* return -1 on failure.
*/
int add_tcp_number_relay_connection(TCP_Connections *tcp_c, int connections_number,
unsigned int tcp_connections_number);
/* Add a TCP relay tied to a connection.
*
* This should be called with the same relay by two peers who want to create a TCP connection with each other.
*
* return 0 on success.
* return -1 on failure.
*/
int add_tcp_relay_connection(TCP_Connections *tcp_c, int connections_number, IP_Port ip_port, const uint8_t *relay_pk);
/* Add a TCP relay to the instance.
*
* return 0 on success.
* return -1 on failure.
*/
int add_tcp_relay_global(TCP_Connections *tcp_c, IP_Port ip_port, const uint8_t *relay_pk);
/* Copy a maximum of max_num TCP relays we are connected to to tcp_relays.
* NOTE that the family of the copied ip ports will be set to TCP_INET or TCP_INET6.
*
* return number of relays copied to tcp_relays on success.
* return 0 on failure.
*/
unsigned int tcp_copy_connected_relays(TCP_Connections *tcp_c, Node_format *tcp_relays, uint16_t max_num);
/* Returns a new TCP_Connections object associated with the secret_key.
*
* In order for others to connect to this instance new_tcp_connection_to() must be called with the
* public_key associated with secret_key.
*
* Returns NULL on failure.
*/
TCP_Connections *new_tcp_connections(const uint8_t *secret_key, TCP_Proxy_Info *proxy_info);
void do_tcp_connections(TCP_Connections *tcp_c);
void kill_tcp_connections(TCP_Connections *tcp_c);
#endif
================================================
FILE: toxcore/TCP_server.c
================================================
/*
* TCP_server.c -- Implementation of the TCP relay server part of Tox.
*
* Copyright (C) 2014 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see .
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "TCP_server.h"
#if !defined(_WIN32) && !defined(__WIN32__) && !defined (WIN32)
#include
#endif
#include "util.h"
/* return 1 on success
* return 0 on failure
*/
static int bind_to_port(sock_t sock, int family, uint16_t port)
{
struct sockaddr_storage addr = {0};
size_t addrsize;
if (family == AF_INET) {
struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
addrsize = sizeof(struct sockaddr_in);
addr4->sin_family = AF_INET;
addr4->sin_port = htons(port);
} else if (family == AF_INET6) {
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr;
addrsize = sizeof(struct sockaddr_in6);
addr6->sin6_family = AF_INET6;
addr6->sin6_port = htons(port);
} else {
return 0;
}
return (bind(sock, (struct sockaddr *)&addr, addrsize) == 0);
}
/* Set the size of the connection list to numfriends.
*
* return -1 if realloc fails.
* return 0 if it succeeds.
*/
static int realloc_connection(TCP_Server *TCP_server, uint32_t num)
{
if (num == 0) {
free(TCP_server->accepted_connection_array);
TCP_server->accepted_connection_array = NULL;
TCP_server->size_accepted_connections = 0;
return 0;
}
if (num == TCP_server->size_accepted_connections) {
return 0;
}
TCP_Secure_Connection *new_connections = realloc(TCP_server->accepted_connection_array,
num * sizeof(TCP_Secure_Connection));
if (new_connections == NULL)
return -1;
if (num > TCP_server->size_accepted_connections) {
uint32_t old_size = TCP_server->size_accepted_connections;
uint32_t size_new_entries = (num - old_size) * sizeof(TCP_Secure_Connection);
memset(new_connections + old_size, 0, size_new_entries);
}
TCP_server->accepted_connection_array = new_connections;
TCP_server->size_accepted_connections = num;
return 0;
}
/* return index corresponding to connection with peer on success
* return -1 on failure.
*/
static int get_TCP_connection_index(const TCP_Server *TCP_server, const uint8_t *public_key)
{
return bs_list_find(&TCP_server->accepted_key_list, public_key);
}
static int kill_accepted(TCP_Server *TCP_server, int index);
/* Add accepted TCP connection to the list.
*
* return index on success
* return -1 on failure
*/
static int add_accepted(TCP_Server *TCP_server, const TCP_Secure_Connection *con)
{
int index = get_TCP_connection_index(TCP_server, con->public_key);
if (index != -1) { /* If an old connection to the same public key exists, kill it. */
kill_accepted(TCP_server, index);
index = -1;
}
if (TCP_server->size_accepted_connections == TCP_server->num_accepted_connections) {
if (realloc_connection(TCP_server, TCP_server->size_accepted_connections + 4) == -1)
return -1;
index = TCP_server->num_accepted_connections;
} else {
uint32_t i;
for (i = TCP_server->size_accepted_connections; i != 0; --i) {
if (TCP_server->accepted_connection_array[i - 1].status == TCP_STATUS_NO_STATUS) {
index = i - 1;
break;
}
}
}
if (index == -1) {
fprintf(stderr, "FAIL index is -1\n");
return -1;
}
if (!bs_list_add(&TCP_server->accepted_key_list, con->public_key, index))
return -1;
memcpy(&TCP_server->accepted_connection_array[index], con, sizeof(TCP_Secure_Connection));
TCP_server->accepted_connection_array[index].status = TCP_STATUS_CONFIRMED;
++TCP_server->num_accepted_connections;
TCP_server->accepted_connection_array[index].identifier = ++TCP_server->counter;
TCP_server->accepted_connection_array[index].last_pinged = unix_time();
TCP_server->accepted_connection_array[index].ping_id = 0;
return index;
}
/* Delete accepted connection from list.
*
* return 0 on success
* return -1 on failure
*/
static int del_accepted(TCP_Server *TCP_server, int index)
{
if ((uint32_t)index >= TCP_server->size_accepted_connections)
return -1;
if (TCP_server->accepted_connection_array[index].status == TCP_STATUS_NO_STATUS)
return -1;
if (!bs_list_remove(&TCP_server->accepted_key_list, TCP_server->accepted_connection_array[index].public_key, index))
return -1;
wipe_priority_list(TCP_server->accepted_connection_array[index].priority_queue_start);
sodium_memzero(&TCP_server->accepted_connection_array[index], sizeof(TCP_Secure_Connection));
--TCP_server->num_accepted_connections;
if (TCP_server->num_accepted_connections == 0)
realloc_connection(TCP_server, 0);
return 0;
}
/* return the amount of data in the tcp recv buffer.
* return 0 on failure.
*/
unsigned int TCP_socket_data_recv_buffer(sock_t sock)
{
#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32)
unsigned long count = 0;
ioctlsocket(sock, FIONREAD, &count);
#else
int count = 0;
ioctl(sock, FIONREAD, &count);
#endif
return count;
}
/* Read the next two bytes in TCP stream then convert them to
* length (host byte order).
*
* return length on success
* return 0 if nothing has been read from socket.
* return ~0 on failure.
*/
uint16_t read_TCP_length(sock_t sock)
{
unsigned int count = TCP_socket_data_recv_buffer(sock);
if (count >= sizeof(uint16_t)) {
uint16_t length;
int len = recv(sock, (uint8_t *)&length, sizeof(uint16_t), MSG_NOSIGNAL);
if (len != sizeof(uint16_t)) {
fprintf(stderr, "FAIL recv packet\n");
return 0;
}
length = ntohs(length);
if (length > MAX_PACKET_SIZE) {
return ~0;
}
return length;
}
return 0;
}
/* Read length bytes from socket.
*
* return length on success
* return -1 on failure/no data in buffer.
*/
int read_TCP_packet(sock_t sock, uint8_t *data, uint16_t length)
{
unsigned int count = TCP_socket_data_recv_buffer(sock);
if (count >= length) {
int len = recv(sock, data, length, MSG_NOSIGNAL);
if (len != length) {
fprintf(stderr, "FAIL recv packet\n");
return -1;
}
return len;
}
return -1;
}
/* return length of received packet on success.
* return 0 if could not read any packet.
* return -1 on failure (connection must be killed).
*/
int read_packet_TCP_secure_connection(sock_t sock, uint16_t *next_packet_length, const uint8_t *shared_key,
uint8_t *recv_nonce, uint8_t *data, uint16_t max_len)
{
if (*next_packet_length == 0) {
uint16_t len = read_TCP_length(sock);
if (len == (uint16_t)~0)
return -1;
if (len == 0)
return 0;
*next_packet_length = len;
}
if (max_len + crypto_box_MACBYTES < *next_packet_length)
return -1;
uint8_t data_encrypted[*next_packet_length];
int len_packet = read_TCP_packet(sock, data_encrypted, *next_packet_length);
if (len_packet != *next_packet_length)
return 0;
*next_packet_length = 0;
int len = decrypt_data_symmetric(shared_key, recv_nonce, data_encrypted, len_packet, data);
if (len + crypto_box_MACBYTES != len_packet)
return -1;
increment_nonce(recv_nonce);
return len;
}
/* return 0 if pending data was sent completely
* return -1 if it wasn't
*/
static int send_pending_data_nonpriority(TCP_Secure_Connection *con)
{
if (con->last_packet_length == 0) {
return 0;
}
uint16_t left = con->last_packet_length - con->last_packet_sent;
int len = send(con->sock, con->last_packet + con->last_packet_sent, left, MSG_NOSIGNAL);
if (len <= 0)
return -1;
if (len == left) {
con->last_packet_length = 0;
con->last_packet_sent = 0;
return 0;
}
con->last_packet_sent += len;
return -1;
}
void wipe_priority_list(TCP_Priority_List *p)
{
while (p) {
TCP_Priority_List *pp = p;
p = p->next;
free(pp);
}
}
/* return 0 if pending data was sent completely
* return -1 if it wasn't
*/
static int send_pending_data(TCP_Secure_Connection *con)
{
/* finish sending current non-priority packet */
if (send_pending_data_nonpriority(con) == -1) {
return -1;
}
TCP_Priority_List *p = con->priority_queue_start;
while (p) {
uint16_t left = p->size - p->sent;
int len = send(con->sock, p->data + p->sent, left, MSG_NOSIGNAL);
if (len != left) {
if (len > 0) {
p->sent += len;
}
break;
}
TCP_Priority_List *pp = p;
p = p->next;
free(pp);
}
con->priority_queue_start = p;
if (!p) {
con->priority_queue_end = NULL;
return 0;
}
return -1;
}
/* return 0 on failure (only if malloc fails)
* return 1 on success
*/
static _Bool add_priority(TCP_Secure_Connection *con, const uint8_t *packet, uint16_t size, uint16_t sent)
{
TCP_Priority_List *p = con->priority_queue_end, *new;
new = malloc(sizeof(TCP_Priority_List) + size);
if (!new) {
return 0;
}
new->next = NULL;
new->size = size;
new->sent = sent;
memcpy(new->data, packet, size);
if (p) {
p->next = new;
} else {
con->priority_queue_start = new;
}
con->priority_queue_end = new;
return 1;
}
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure (connection must be killed).
*/
static int write_packet_TCP_secure_connection(TCP_Secure_Connection *con, const uint8_t *data, uint16_t length,
_Bool priority)
{
if (length + crypto_box_MACBYTES > MAX_PACKET_SIZE)
return -1;
_Bool sendpriority = 1;
if (send_pending_data(con) == -1) {
if (priority) {
sendpriority = 0;
} else {
return 0;
}
}
uint8_t packet[sizeof(uint16_t) + length + crypto_box_MACBYTES];
uint16_t c_length = htons(length + crypto_box_MACBYTES);
memcpy(packet, &c_length, sizeof(uint16_t));
int len = encrypt_data_symmetric(con->shared_key, con->sent_nonce, data, length, packet + sizeof(uint16_t));
if ((unsigned int)len != (sizeof(packet) - sizeof(uint16_t)))
return -1;
if (priority) {
len = sendpriority ? send(con->sock, packet, sizeof(packet), MSG_NOSIGNAL) : 0;
if (len <= 0) {
len = 0;
}
increment_nonce(con->sent_nonce);
if ((unsigned int)len == sizeof(packet)) {
return 1;
}
return add_priority(con, packet, sizeof(packet), len);
}
len = send(con->sock, packet, sizeof(packet), MSG_NOSIGNAL);
if (len <= 0)
return 0;
increment_nonce(con->sent_nonce);
if ((unsigned int)len == sizeof(packet))
return 1;
memcpy(con->last_packet, packet, sizeof(packet));
con->last_packet_length = sizeof(packet);
con->last_packet_sent = len;
return 1;
}
/* Kill a TCP_Secure_Connection
*/
static void kill_TCP_connection(TCP_Secure_Connection *con)
{
wipe_priority_list(con->priority_queue_start);
kill_sock(con->sock);
sodium_memzero(con, sizeof(TCP_Secure_Connection));
}
static int rm_connection_index(TCP_Server *TCP_server, TCP_Secure_Connection *con, uint8_t con_number);
/* Kill an accepted TCP_Secure_Connection
*
* return -1 on failure.
* return 0 on success.
*/
static int kill_accepted(TCP_Server *TCP_server, int index)
{
if ((uint32_t)index >= TCP_server->size_accepted_connections)
return -1;
uint32_t i;
for (i = 0; i < NUM_CLIENT_CONNECTIONS; ++i) {
rm_connection_index(TCP_server, &TCP_server->accepted_connection_array[index], i);
}
sock_t sock = TCP_server->accepted_connection_array[index].sock;
if (del_accepted(TCP_server, index) != 0)
return -1;
kill_sock(sock);
return 0;
}
/* return 1 if everything went well.
* return -1 if the connection must be killed.
*/
static int handle_TCP_handshake(TCP_Secure_Connection *con, const uint8_t *data, uint16_t length,
const uint8_t *self_secret_key)
{
if (length != TCP_CLIENT_HANDSHAKE_SIZE)
return -1;
if (con->status != TCP_STATUS_CONNECTED)
return -1;
uint8_t shared_key[crypto_box_BEFORENMBYTES];
encrypt_precompute(data, self_secret_key, shared_key);
uint8_t plain[TCP_HANDSHAKE_PLAIN_SIZE];
int len = decrypt_data_symmetric(shared_key, data + crypto_box_PUBLICKEYBYTES,
data + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES, TCP_HANDSHAKE_PLAIN_SIZE + crypto_box_MACBYTES, plain);
if (len != TCP_HANDSHAKE_PLAIN_SIZE)
return -1;
memcpy(con->public_key, data, crypto_box_PUBLICKEYBYTES);
uint8_t temp_secret_key[crypto_box_SECRETKEYBYTES];
uint8_t resp_plain[TCP_HANDSHAKE_PLAIN_SIZE];
crypto_box_keypair(resp_plain, temp_secret_key);
random_nonce(con->sent_nonce);
memcpy(resp_plain + crypto_box_PUBLICKEYBYTES, con->sent_nonce, crypto_box_NONCEBYTES);
memcpy(con->recv_nonce, plain + crypto_box_PUBLICKEYBYTES, crypto_box_NONCEBYTES);
uint8_t response[TCP_SERVER_HANDSHAKE_SIZE];
new_nonce(response);
len = encrypt_data_symmetric(shared_key, response, resp_plain, TCP_HANDSHAKE_PLAIN_SIZE,
response + crypto_box_NONCEBYTES);
if (len != TCP_HANDSHAKE_PLAIN_SIZE + crypto_box_MACBYTES)
return -1;
if (TCP_SERVER_HANDSHAKE_SIZE != send(con->sock, response, TCP_SERVER_HANDSHAKE_SIZE, MSG_NOSIGNAL))
return -1;
encrypt_precompute(plain, temp_secret_key, con->shared_key);
con->status = TCP_STATUS_UNCONFIRMED;
return 1;
}
/* return 1 if connection handshake was handled correctly.
* return 0 if we didn't get it yet.
* return -1 if the connection must be killed.
*/
static int read_connection_handshake(TCP_Secure_Connection *con, const uint8_t *self_secret_key)
{
uint8_t data[TCP_CLIENT_HANDSHAKE_SIZE];
int len = 0;
if ((len = read_TCP_packet(con->sock, data, TCP_CLIENT_HANDSHAKE_SIZE)) != -1) {
return handle_TCP_handshake(con, data, len, self_secret_key);
}
return 0;
}
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure (connection must be killed).
*/
static int send_routing_response(TCP_Secure_Connection *con, uint8_t rpid, const uint8_t *public_key)
{
uint8_t data[1 + 1 + crypto_box_PUBLICKEYBYTES];
data[0] = TCP_PACKET_ROUTING_RESPONSE;
data[1] = rpid;
memcpy(data + 2, public_key, crypto_box_PUBLICKEYBYTES);
return write_packet_TCP_secure_connection(con, data, sizeof(data), 1);
}
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure (connection must be killed).
*/
static int send_connect_notification(TCP_Secure_Connection *con, uint8_t id)
{
uint8_t data[2] = {TCP_PACKET_CONNECTION_NOTIFICATION, id + NUM_RESERVED_PORTS};
return write_packet_TCP_secure_connection(con, data, sizeof(data), 1);
}
/* return 1 on success.
* return 0 if could not send packet.
* return -1 on failure (connection must be killed).
*/
static int send_disconnect_notification(TCP_Secure_Connection *con, uint8_t id)
{
uint8_t data[2] = {TCP_PACKET_DISCONNECT_NOTIFICATION, id + NUM_RESERVED_PORTS};
return write_packet_TCP_secure_connection(con, data, sizeof(data), 1);
}
/* return 0 on success.
* return -1 on failure (connection must be killed).
*/
static int handle_TCP_routing_req(TCP_Server *TCP_server, uint32_t con_id, const uint8_t *public_key)
{
uint32_t i;
uint32_t index = ~0;
TCP_Secure_Connection *con = &TCP_server->accepted_connection_array[con_id];
/* If person tries to cennect to himself we deny the request*/
if (public_key_cmp(con->public_key, public_key) == 0) {
if (send_routing_response(con, 0, public_key) == -1)
return -1;
return 0;
}
for (i = 0; i < NUM_CLIENT_CONNECTIONS; ++i) {
if (con->connections[i].status != 0) {
if (public_key_cmp(public_key, con->connections[i].public_key) == 0) {
if (send_routing_response(con, i + NUM_RESERVED_PORTS, public_key) == -1) {
return -1;
} else {
return 0;
}
}
} else if (index == (uint32_t)~0) {
index = i;
}
}
if (index == (uint32_t)~0) {
if (send_routing_response(con, 0, public_key) == -1)
return -1;
return 0;
}
int ret = send_routing_response(con, index + NUM_RESERVED_PORTS, public_key);
if (ret == 0)
return 0;
if (ret == -1)
return -1;
con->connections[index].status = 1;
memcpy(con->connections[index].public_key, public_key, crypto_box_PUBLICKEYBYTES);
int other_index = get_TCP_connection_index(TCP_server, public_key);
if (other_index != -1) {
uint32_t other_id = ~0;
TCP_Secure_Connection *other_conn = &TCP_server->accepted_connection_array[other_index];
for (i = 0; i < NUM_CLIENT_CONNECTIONS; ++i) {
if (other_conn->connections[i].status == 1
&& public_key_cmp(other_conn->connections[i].public_key, con->public_key) == 0) {
other_id = i;
break;
}
}
if (other_id != (uint32_t)~0) {
con->connections[index].status = 2;
con->connections[index].index = other_index;
con->connections[index].other_id = other_id;
other_conn->connections[other_id].status = 2;
other_conn->connections[other_id].index = con_id;
other_conn->connections[other_id].other_id = index;
//TODO: return values?
send_connect_notification(con, index);
send_connect_notification(other_conn, other_id);
}
}
return 0;
}
/* return 0 on success.
* return -1 on failure (connection must be killed).
*/
static int handle_TCP_oob_send(TCP_Server *TCP_server, uint32_t con_id, const uint8_t *public_key, const uint8_t *data,
uint16_t length)
{
if (length == 0 || length > TCP_MAX_OOB_DATA_LENGTH)
return -1;
TCP_Secure_Connection *con = &TCP_server->accepted_connection_array[con_id];
int other_index = get_TCP_connection_index(TCP_server, public_key);
if (other_index != -1) {
uint8_t resp_packet[1 + crypto_box_PUBLICKEYBYTES + length];
resp_packet[0] = TCP_PACKET_OOB_RECV;
memcpy(resp_packet + 1, con->public_key, crypto_box_PUBLICKEYBYTES);
memcpy(resp_packet + 1 + crypto_box_PUBLICKEYBYTES, data, length);
write_packet_TCP_secure_connection(&TCP_server->accepted_connection_array[other_index], resp_packet,
sizeof(resp_packet), 0);
}
return 0;
}
/* Remove connection with con_number from the connections array of con.
*
* return -1 on failure.
* return 0 on success.
*/
static int rm_connection_index(TCP_Server *TCP_server, TCP_Secure_Connection *con, uint8_t con_number)
{
if (con_number >= NUM_CLIENT_CONNECTIONS)
return -1;
if (con->connections[con_number].status) {
uint32_t index = con->connections[con_number].index;
uint8_t other_id = con->connections[con_number].other_id;
if (con->connections[con_number].status == 2) {
if (index >= TCP_server->size_accepted_connections)
return -1;
TCP_server->accepted_connection_array[index].connections[other_id].other_id = 0;
TCP_server->accepted_connection_array[index].connections[other_id].index = 0;
TCP_server->accepted_connection_array[index].connections[other_id].status = 1;
//TODO: return values?
send_disconnect_notification(&TCP_server->accepted_connection_array[index], other_id);
}
con->connections[con_number].index = 0;
con->connections[con_number].other_id = 0;
con->connections[con_number].status = 0;
return 0;
} else {
return -1;
}
}
static int handle_onion_recv_1(void *object, IP_Port dest, const uint8_t *data, uint16_t length)
{
TCP_Server *TCP_server = object;
uint32_t index = dest.ip.ip6.uint32[0];
if (index >= TCP_server->size_accepted_connections)
return 1;
TCP_Secure_Connection *con = &TCP_server->accepted_connection_array[index];
if (con->identifier != dest.ip.ip6.uint64[1])
return 1;
uint8_t packet[1 + length];
memcpy(packet + 1, data, length);
packet[0] = TCP_PACKET_ONION_RESPONSE;
if (write_packet_TCP_secure_connection(con, packet, sizeof(packet), 0) != 1)
return 1;
return 0;
}
/* return 0 on success
* return -1 on failure
*/
static int handle_TCP_packet(TCP_Server *TCP_server, uint32_t con_id, const uint8_t *data, uint16_t length)
{
if (length == 0)
return -1;
TCP_Secure_Connection *con = &TCP_server->accepted_connection_array[con_id];
switch (data[0]) {
case TCP_PACKET_ROUTING_REQUEST: {
if (length != 1 + crypto_box_PUBLICKEYBYTES)
return -1;
return handle_TCP_routing_req(TCP_server, con_id, data + 1);
}
case TCP_PACKET_CONNECTION_NOTIFICATION: {
if (length != 2)
return -1;
break;
}
case TCP_PACKET_DISCONNECT_NOTIFICATION: {
if (length != 2)
return -1;
return rm_connection_index(TCP_server, con, data[1] - NUM_RESERVED_PORTS);
}
case TCP_PACKET_PING: {
if (length != 1 + sizeof(uint64_t))
return -1;
uint8_t response[1 + sizeof(uint64_t)];
response[0] = TCP_PACKET_PONG;
memcpy(response + 1, data + 1, sizeof(uint64_t));
write_packet_TCP_secure_connection(con, response, sizeof(response), 1);
return 0;
}
case TCP_PACKET_PONG: {
if (length != 1 + sizeof(uint64_t))
return -1;
uint64_t ping_id;
memcpy(&ping_id, data + 1, sizeof(uint64_t));
if (ping_id) {
if (ping_id == con->ping_id) {
con->ping_id = 0;
}
return 0;
} else {
return -1;
}
}
case TCP_PACKET_OOB_SEND: {
if (length <= 1 + crypto_box_PUBLICKEYBYTES)
return -1;
return handle_TCP_oob_send(TCP_server, con_id, data + 1, data + 1 + crypto_box_PUBLICKEYBYTES,
length - (1 + crypto_box_PUBLICKEYBYTES));
}
case TCP_PACKET_ONION_REQUEST: {
if (TCP_server->onion) {
if (length <= 1 + crypto_box_NONCEBYTES + ONION_SEND_BASE * 2)
return -1;
IP_Port source;
source.port = 0; // dummy initialise
source.ip.family = TCP_ONION_FAMILY;
source.ip.ip6.uint32[0] = con_id;
source.ip.ip6.uint32[1] = 0;
source.ip.ip6.uint64[1] = con->identifier;
onion_send_1(TCP_server->onion, data + 1 + crypto_box_NONCEBYTES, length - (1 + crypto_box_NONCEBYTES), source,
data + 1);
}
return 0;
}
case TCP_PACKET_ONION_RESPONSE: {
return -1;
}
default: {
if (data[0] < NUM_RESERVED_PORTS)
return -1;
uint8_t c_id = data[0] - NUM_RESERVED_PORTS;
if (c_id >= NUM_CLIENT_CONNECTIONS)
return -1;
if (con->connections[c_id].status == 0)
return -1;
if (con->connections[c_id].status != 2)
return 0;
uint32_t index = con->connections[c_id].index;
uint8_t other_c_id = con->connections[c_id].other_id + NUM_RESERVED_PORTS;
uint8_t new_data[length];
memcpy(new_data, data, length);
new_data[0] = other_c_id;
int ret = write_packet_TCP_secure_connection(&TCP_server->accepted_connection_array[index], new_data, length, 0);
if (ret == -1)
return -1;
return 0;
}
}
return 0;
}
static int confirm_TCP_connection(TCP_Server *TCP_server, TCP_Secure_Connection *con, const uint8_t *data,
uint16_t length)
{
int index = add_accepted(TCP_server, con);
if (index == -1) {
kill_TCP_connection(con);
return -1;
}
sodium_memzero(con, sizeof(TCP_Secure_Connection));
if (handle_TCP_packet(TCP_server, index, data, length) == -1) {
kill_accepted(TCP_server, index);
return -1;
}
return index;
}
/* return index on success
* return -1 on failure
*/
static int accept_connection(TCP_Server *TCP_server, sock_t sock)
{
if (!sock_valid(sock))
return -1;
if (!set_socket_nonblock(sock)) {
kill_sock(sock);
return -1;
}
if (!set_socket_nosigpipe(sock)) {
kill_sock(sock);
return -1;
}
uint16_t index = TCP_server->incomming_connection_queue_index % MAX_INCOMMING_CONNECTIONS;
TCP_Secure_Connection *conn = &TCP_server->incomming_connection_queue[index];
if (conn->status != TCP_STATUS_NO_STATUS)
kill_TCP_connection(conn);
conn->status = TCP_STATUS_CONNECTED;
conn->sock = sock;
conn->next_packet_length = 0;
++TCP_server->incomming_connection_queue_index;
return index;
}
static sock_t new_listening_TCP_socket(int family, uint16_t port)
{
sock_t sock = socket(family, SOCK_STREAM, IPPROTO_TCP);
if (!sock_valid(sock)) {
return ~0;
}
int ok = set_socket_nonblock(sock);
if (ok && family == AF_INET6) {
ok = set_socket_dualstack(sock);
}
if (ok) {
ok = set_socket_reuseaddr(sock);
}
ok = ok && bind_to_port(sock, family, port) && (listen(sock, TCP_MAX_BACKLOG) == 0);
if (!ok) {
kill_sock(sock);
return ~0;
}
return sock;
}
TCP_Server *new_TCP_server(uint8_t ipv6_enabled, uint16_t num_sockets, const uint16_t *ports, const uint8_t *secret_key,
Onion *onion)
{
if (num_sockets == 0 || ports == NULL)
return NULL;
if (networking_at_startup() != 0) {
return NULL;
}
TCP_Server *temp = calloc(1, sizeof(TCP_Server));
if (temp == NULL)
return NULL;
temp->socks_listening = calloc(num_sockets, sizeof(sock_t));
if (temp->socks_listening == NULL) {
free(temp);
return NULL;
}
#ifdef TCP_SERVER_USE_EPOLL
temp->efd = epoll_create(8);
if (temp->efd == -1) {
free(temp->socks_listening);
free(temp);
return NULL;
}
#endif
uint8_t family;
if (ipv6_enabled) {
family = AF_INET6;
} else {
family = AF_INET;
}
uint32_t i;
#ifdef TCP_SERVER_USE_EPOLL
struct epoll_event ev;
#endif
for (i = 0; i < num_sockets; ++i) {
sock_t sock = new_listening_TCP_socket(family, ports[i]);
if (sock_valid(sock)) {
#ifdef TCP_SERVER_USE_EPOLL
ev.events = EPOLLIN | EPOLLET;
ev.data.u64 = sock | ((uint64_t)TCP_SOCKET_LISTENING << 32);
if (epoll_ctl(temp->efd, EPOLL_CTL_ADD, sock, &ev) == -1) {
continue;
}
#endif
temp->socks_listening[temp->num_listening_socks] = sock;
++temp->num_listening_socks;
}
}
if (temp->num_listening_socks == 0) {
free(temp->socks_listening);
free(temp);
return NULL;
}
if (onion) {
temp->onion = onion;
set_callback_handle_recv_1(onion, &handle_onion_recv_1, temp);
}
memcpy(temp->secret_key, secret_key, crypto_box_SECRETKEYBYTES);
crypto_scalarmult_curve25519_base(temp->public_key, temp->secret_key);
bs_list_init(&temp->accepted_key_list, crypto_box_PUBLICKEYBYTES, 8);
return temp;
}
static void do_TCP_accept_new(TCP_Server *TCP_server)
{
uint32_t i;
for (i = 0; i < TCP_server->num_listening_socks; ++i) {
struct sockaddr_storage addr;
unsigned int addrlen = sizeof(addr);
sock_t sock;
do {
sock = accept(TCP_server->socks_listening[i], (struct sockaddr *)&addr, &addrlen);
} while (accept_connection(TCP_server, sock) != -1);
}
}
static int do_incoming(TCP_Server *TCP_server, uint32_t i)
{
if (TCP_server->incomming_connection_queue[i].status != TCP_STATUS_CONNECTED)
return -1;
int ret = read_connection_handshake(&TCP_server->incomming_connection_queue[i], TCP_server->secret_key);
if (ret == -1) {
kill_TCP_connection(&TCP_server->incomming_connection_queue[i]);
} else if (ret == 1) {
int index_new = TCP_server->unconfirmed_connection_queue_index % MAX_INCOMMING_CONNECTIONS;
TCP_Secure_Connection *conn_old = &TCP_server->incomming_connection_queue[i];
TCP_Secure_Connection *conn_new = &TCP_server->unconfirmed_connection_queue[index_new];
if (conn_new->status != TCP_STATUS_NO_STATUS)
kill_TCP_connection(conn_new);
memcpy(conn_new, conn_old, sizeof(TCP_Secure_Connection));
sodium_memzero(conn_old, sizeof(TCP_Secure_Connection));
++TCP_server->unconfirmed_connection_queue_index;
return index_new;
}
return -1;
}
static int do_unconfirmed(TCP_Server *TCP_server, uint32_t i)
{
TCP_Secure_Connection *conn = &TCP_server->unconfirmed_connection_queue[i];
if (conn->status != TCP_STATUS_UNCONFIRMED)
return -1;
uint8_t packet[MAX_PACKET_SIZE];
int len = read_packet_TCP_secure_connection(conn->sock, &conn->next_packet_length, conn->shared_key, conn->recv_nonce,
packet, sizeof(packet));
if (len == 0) {
return -1;
} else if (len == -1) {
kill_TCP_connection(conn);
return -1;
} else {
return confirm_TCP_connection(TCP_server, conn, packet, len);
}
}
static void do_confirmed_recv(TCP_Server *TCP_server, uint32_t i)
{
TCP_Secure_Connection *conn = &TCP_server->accepted_connection_array[i];
uint8_t packet[MAX_PACKET_SIZE];
int len;
while ((len = read_packet_TCP_secure_connection(conn->sock, &conn->next_packet_length, conn->shared_key,
conn->recv_nonce, packet, sizeof(packet)))) {
if (len == -1) {
kill_accepted(TCP_server, i);
break;
}
if (handle_TCP_packet(TCP_server, i, packet, len) == -1) {
kill_accepted(TCP_server, i);
break;
}
}
}
static void do_TCP_incomming(TCP_Server *TCP_server)
{
uint32_t i;
for (i = 0; i < MAX_INCOMMING_CONNECTIONS; ++i) {
do_incoming(TCP_server, i);
}
}
static void do_TCP_unconfirmed(TCP_Server *TCP_server)
{
uint32_t i;
for (i = 0; i < MAX_INCOMMING_CONNECTIONS; ++i) {
do_unconfirmed(TCP_server, i);
}
}
static void do_TCP_confirmed(TCP_Server *TCP_server)
{
#ifdef TCP_SERVER_USE_EPOLL
if (TCP_server->last_run_pinged == unix_time())
return;
TCP_server->last_run_pinged = unix_time();
#endif
uint32_t i;
for (i = 0; i < TCP_server->size_accepted_connections; ++i) {
TCP_Secure_Connection *conn = &TCP_server->accepted_connection_array[i];
if (conn->status != TCP_STATUS_CONFIRMED)
continue;
if (is_timeout(conn->last_pinged, TCP_PING_FREQUENCY)) {
uint8_t ping[1 + sizeof(uint64_t)];
ping[0] = TCP_PACKET_PING;
uint64_t ping_id = random_64b();
if (!ping_id)
++ping_id;
memcpy(ping + 1, &ping_id, sizeof(uint64_t));
int ret = write_packet_TCP_secure_connection(conn, ping, sizeof(ping), 1);
if (ret == 1) {
conn->last_pinged = unix_time();
conn->ping_id = ping_id;
} else {
if (is_timeout(conn->last_pinged, TCP_PING_FREQUENCY + TCP_PING_TIMEOUT)) {
kill_accepted(TCP_server, i);
continue;
}
}
}
if (conn->ping_id && is_timeout(conn->last_pinged, TCP_PING_TIMEOUT)) {
kill_accepted(TCP_server, i);
continue;
}
send_pending_data(conn);
#ifndef TCP_SERVER_USE_EPOLL
do_confirmed_recv(TCP_server, i);
#endif
}
}
#ifdef TCP_SERVER_USE_EPOLL
static void do_TCP_epoll(TCP_Server *TCP_server)
{
#define MAX_EVENTS 16
struct epoll_event events[MAX_EVENTS];
int nfds;
while ((nfds = epoll_wait(TCP_server->efd, events, MAX_EVENTS, 0)) > 0) {
int n;
for (n = 0; n < nfds; ++n) {
sock_t sock = events[n].data.u64 & 0xFFFFFFFF;
int status = (events[n].data.u64 >> 32) & 0xFF, index = (events[n].data.u64 >> 40);
if ((events[n].events & EPOLLERR) || (events[n].events & EPOLLHUP) || (events[n].events & EPOLLRDHUP)) {
switch (status) {
case TCP_SOCKET_LISTENING: {
//should never happen
break;
}
case TCP_SOCKET_INCOMING: {
kill_TCP_connection(&TCP_server->incomming_connection_queue[index]);
break;
}
case TCP_SOCKET_UNCONFIRMED: {
kill_TCP_connection(&TCP_server->unconfirmed_connection_queue[index]);
break;
}
case TCP_SOCKET_CONFIRMED: {
kill_accepted(TCP_server, index);
break;
}
}
continue;
}
if (!(events[n].events & EPOLLIN)) {
continue;
}
switch (status) {
case TCP_SOCKET_LISTENING: {
//socket is from socks_listening, accept connection
struct sockaddr_storage addr;
unsigned int addrlen = sizeof(addr);
while (1) {
sock_t sock_new = accept(sock, (struct sockaddr *)&addr, &addrlen);
if (!sock_valid(sock_new)) {
break;
}
int index_new = accept_connection(TCP_server, sock_new);
if (index_new == -1) {
continue;
}
struct epoll_event ev = {
.events = EPOLLIN | EPOLLET | EPOLLRDHUP,
.data.u64 = sock_new | ((uint64_t)TCP_SOCKET_INCOMING << 32) | ((uint64_t)index_new << 40)
};
if (epoll_ctl(TCP_server->efd, EPOLL_CTL_ADD, sock_new, &ev) == -1) {
kill_TCP_connection(&TCP_server->incomming_connection_queue[index_new]);
continue;
}
}
break;
}
case TCP_SOCKET_INCOMING: {
int index_new;
if ((index_new = do_incoming(TCP_server, index)) != -1) {
events[n].events = EPOLLIN | EPOLLET | EPOLLRDHUP;
events[n].data.u64 = sock | ((uint64_t)TCP_SOCKET_UNCONFIRMED << 32) | ((uint64_t)index_new << 40);
if (epoll_ctl(TCP_server->efd, EPOLL_CTL_MOD, sock, &events[n]) == -1) {
kill_TCP_connection(&TCP_server->unconfirmed_connection_queue[index_new]);
break;
}
}
break;
}
case TCP_SOCKET_UNCONFIRMED: {
int index_new;
if ((index_new = do_unconfirmed(TCP_server, index)) != -1) {
events[n].events = EPOLLIN | EPOLLET | EPOLLRDHUP;
events[n].data.u64 = sock | ((uint64_t)TCP_SOCKET_CONFIRMED << 32) | ((uint64_t)index_new << 40);
if (epoll_ctl(TCP_server->efd, EPOLL_CTL_MOD, sock, &events[n]) == -1) {
//remove from confirmed connections
kill_accepted(TCP_server, index_new);
break;
}
}
break;
}
case TCP_SOCKET_CONFIRMED: {
do_confirmed_recv(TCP_server, index);
break;
}
}
}
}
#undef MAX_EVENTS
}
#endif
void do_TCP_server(TCP_Server *TCP_server)
{
unix_time_update();
#ifdef TCP_SERVER_USE_EPOLL
do_TCP_epoll(TCP_server);
#else
do_TCP_accept_new(TCP_server);
do_TCP_incomming(TCP_server);
do_TCP_unconfirmed(TCP_server);
#endif
do_TCP_confirmed(TCP_server);
}
void kill_TCP_server(TCP_Server *TCP_server)
{
uint32_t i;
for (i = 0; i < TCP_server->num_listening_socks; ++i) {
kill_sock(TCP_server->socks_listening[i]);
}
if (TCP_server->onion) {
set_callback_handle_recv_1(TCP_server->onion, NULL, NULL);
}
bs_list_free(&TCP_server->accepted_key_list);
#ifdef TCP_SERVER_USE_EPOLL
close(TCP_server->efd);
#endif
free(TCP_server->socks_listening);
free(TCP_server->accepted_connection_array);
free(TCP_server);
}
================================================
FILE: toxcore/TCP_server.h
================================================
/*
* TCP_server.h -- Implementation of the TCP relay server part of Tox.
*
* Copyright (C) 2014 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tox is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tox. If not, see