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 ================================================ ![Project Tox](https://raw.github.com/irungentoo/toxcore/master/other/tox.png "Project Tox") *** 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:** [![Build Status](https://travis-ci.org/irungentoo/toxcore.png?branch=master)](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 . * */ #ifndef TCP_SERVER_H #define TCP_SERVER_H #include "crypto_core.h" #include "onion.h" #include "list.h" #ifdef TCP_SERVER_USE_EPOLL #include "sys/epoll.h" #endif #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__MACH__) #define MSG_NOSIGNAL 0 #endif #define MAX_INCOMMING_CONNECTIONS 256 #define TCP_MAX_BACKLOG MAX_INCOMMING_CONNECTIONS #define MAX_PACKET_SIZE 2048 #define TCP_HANDSHAKE_PLAIN_SIZE (crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES) #define TCP_SERVER_HANDSHAKE_SIZE (crypto_box_NONCEBYTES + TCP_HANDSHAKE_PLAIN_SIZE + crypto_box_MACBYTES) #define TCP_CLIENT_HANDSHAKE_SIZE (crypto_box_PUBLICKEYBYTES + TCP_SERVER_HANDSHAKE_SIZE) #define TCP_MAX_OOB_DATA_LENGTH 1024 #define NUM_RESERVED_PORTS 16 #define NUM_CLIENT_CONNECTIONS (256 - NUM_RESERVED_PORTS) #define TCP_PACKET_ROUTING_REQUEST 0 #define TCP_PACKET_ROUTING_RESPONSE 1 #define TCP_PACKET_CONNECTION_NOTIFICATION 2 #define TCP_PACKET_DISCONNECT_NOTIFICATION 3 #define TCP_PACKET_PING 4 #define TCP_PACKET_PONG 5 #define TCP_PACKET_OOB_SEND 6 #define TCP_PACKET_OOB_RECV 7 #define TCP_PACKET_ONION_REQUEST 8 #define TCP_PACKET_ONION_RESPONSE 9 #define ARRAY_ENTRY_SIZE 6 /* frequency to ping connected nodes and timeout in seconds */ #define TCP_PING_FREQUENCY 30 #define TCP_PING_TIMEOUT 10 #ifdef TCP_SERVER_USE_EPOLL #define TCP_SOCKET_LISTENING 0 #define TCP_SOCKET_INCOMING 1 #define TCP_SOCKET_UNCONFIRMED 2 #define TCP_SOCKET_CONFIRMED 3 #endif enum { TCP_STATUS_NO_STATUS, TCP_STATUS_CONNECTED, TCP_STATUS_UNCONFIRMED, TCP_STATUS_CONFIRMED, }; typedef struct TCP_Priority_List TCP_Priority_List; struct TCP_Priority_List { TCP_Priority_List *next; uint16_t size, sent; uint8_t data[]; }; typedef struct TCP_Secure_Connection { uint8_t status; sock_t sock; uint8_t public_key[crypto_box_PUBLICKEYBYTES]; 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; 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 index; uint8_t other_id; } connections[NUM_CLIENT_CONNECTIONS]; 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 identifier; uint64_t last_pinged; uint64_t ping_id; } TCP_Secure_Connection; typedef struct { Onion *onion; #ifdef TCP_SERVER_USE_EPOLL int efd; uint64_t last_run_pinged; #endif sock_t *socks_listening; unsigned int num_listening_socks; uint8_t public_key[crypto_box_PUBLICKEYBYTES]; uint8_t secret_key[crypto_box_SECRETKEYBYTES]; TCP_Secure_Connection incomming_connection_queue[MAX_INCOMMING_CONNECTIONS]; uint16_t incomming_connection_queue_index; TCP_Secure_Connection unconfirmed_connection_queue[MAX_INCOMMING_CONNECTIONS]; uint16_t unconfirmed_connection_queue_index; TCP_Secure_Connection *accepted_connection_array; uint32_t size_accepted_connections; uint32_t num_accepted_connections; uint64_t counter; BS_LIST accepted_key_list; } TCP_Server; /* Create new TCP server instance. */ TCP_Server *new_TCP_server(uint8_t ipv6_enabled, uint16_t num_sockets, const uint16_t *ports, const uint8_t *secret_key, Onion *onion); void wipe_priority_list(TCP_Priority_List *p); /* Run the TCP_server */ void do_TCP_server(TCP_Server *TCP_server); /* Kill the TCP server */ void kill_TCP_server(TCP_Server *TCP_server); /* return the amount of data in the tcp recv buffer. * return 0 on failure. */ unsigned int TCP_socket_data_recv_buffer(sock_t sock); /* 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); /* 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); /* 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); #endif ================================================ FILE: toxcore/assoc.c ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "logger.h" #include "DHT.h" #include "assoc.h" #include "ping.h" #include "LAN_discovery.h" #include #include "util.h" /* * BASIC OVERVIEW: * * Hash: The client_id is hashed with a local hash function. * Hashes are used in multiple places for searching. * Bucket: The first n bits of the client_id are used to * select a bucket. This speeds up sorting, but the more * important reason is to enforce a spread in the space of * client_ids available. * * * Candidates: * * Candidates are kept in buckets of hash tables. The hash * function is calculated from the client_id. Up to * HASH_COLLIDE_COUNT alternative positions are tried if * the initial position is already used by a different entry. * The collision function is multiplicative, not additive. * * A new candidate can bump an existing candidate, if it is * more "desirable": Seen beats Heard. */ /* candidates: alternative places for the same hash value */ #define HASH_COLLIDE_COUNT 5 /* bucket size shall be co-prime to this */ #define HASH_COLLIDE_PRIME 101 /* candidates: bump entries: timeout values for seen/heard to be considered of value */ #define CANDIDATES_SEEN_TIMEOUT 1800 #define CANDIDATES_HEARD_TIMEOUT 600 /* distance/index: index size & access mask */ #define DISTANCE_INDEX_INDEX_BITS (64 - DISTANCE_INDEX_DISTANCE_BITS) #define DISTANCE_INDEX_INDEX_MASK ((1 << DISTANCE_INDEX_INDEX_BITS) - 1) /* types to stay consistent */ typedef uint16_t bucket_t; typedef uint32_t hash_t; typedef uint16_t usecnt_t; /* abbreviations ... */ typedef Assoc_distance_relative_callback dist_rel_cb; typedef Assoc_distance_absolute_callback dist_abs_cb; /* * Client_data wrapped with additional data */ typedef struct Client_entry { hash_t hash; /* shortcuts & rumors: timers and data */ uint64_t getnodes; uint64_t used_at; uint64_t seen_at; uint64_t heard_at; uint16_t seen_family; uint16_t heard_family; IP_Port assoc_heard4; IP_Port assoc_heard6; Client_data client; } Client_entry; typedef struct candidates_bucket { Client_entry *list; /* hashed list */ } candidates_bucket; struct Assoc { hash_t self_hash; /* hash of self_client_id */ uint8_t self_client_id[crypto_box_PUBLICKEYBYTES]; /* don't store entries for this */ /* association centralization: clients not in use */ size_t candidates_bucket_bits; size_t candidates_bucket_count; size_t candidates_bucket_size; candidates_bucket *candidates; uint64_t getnodes; }; /*****************************************************************************/ /* HELPER FUNCTIONS */ /*****************************************************************************/ /* the complete distance would be crypto_box_PUBLICKEYBYTES long... * returns DISTANCE_INDEX_DISTANCE_BITS valid bits */ static uint64_t id_distance(const Assoc *assoc, void *callback_data, const uint8_t *id_ref, const uint8_t *id_test) { /* with BIG_ENDIAN, this would be a one-liner... */ uint64_t retval = 0; uint8_t pos = 0, bits = DISTANCE_INDEX_DISTANCE_BITS; while (bits > 8) { uint8_t distance = abs((int8_t)id_ref[pos] ^ (int8_t)id_test[pos]); retval = (retval << 8) | distance; bits -= 8; pos++; } return (retval << bits) | ((id_ref[pos] ^ id_test[pos]) >> (8 - bits)); } /* qsort() callback for a sorting by id_distance() values */ static int dist_index_comp(const void *a, const void *b) { const uint64_t *_a = a; const uint64_t *_b = b; if (*_a < *_b) return -1; if (*_a > *_b) return 1; return 0; } /* get actual entry to a distance_index */ static Client_entry *dist_index_entry(Assoc *assoc, uint64_t dist_ind) { if ((dist_ind & DISTANCE_INDEX_INDEX_MASK) == DISTANCE_INDEX_INDEX_MASK) return NULL; size_t total = assoc->candidates_bucket_count * assoc->candidates_bucket_size; uint32_t index = dist_ind & DISTANCE_INDEX_INDEX_MASK; if (index < total) { bucket_t b_id = index / assoc->candidates_bucket_size; candidates_bucket *cnd_bckt = &assoc->candidates[b_id]; size_t b_ix = index % assoc->candidates_bucket_size; Client_entry *entry = &cnd_bckt->list[b_ix]; if (entry->hash) return entry; } return NULL; } /* get actual entry's public_key to a distance_index */ static uint8_t *dist_index_id(Assoc *assoc, uint64_t dist_ind) { Client_entry *entry = dist_index_entry(assoc, dist_ind); if (entry) return entry->client.public_key; return NULL; } /* sorts first .. last, i.e. last is included */ static void dist_index_bubble(Assoc *assoc, uint64_t *dist_list, size_t first, size_t last, uint8_t *id, void *custom_data, Assoc_distance_relative_callback dist_rel_func) { size_t i, k; for (i = first; i <= last; i++) { uint8_t *id1 = dist_index_id(assoc, dist_list[i]); for (k = i + 1; k <= last; k++) { uint8_t *id2 = dist_index_id(assoc, dist_list[k]); if (id1 && id2) if (dist_rel_func(assoc, custom_data, id, id1, id2) == 2) { uint64_t swap = dist_list[i]; dist_list[i] = dist_list[k]; dist_list[k] = swap; } } } } /* TODO: Check that there isn't a function like this elsewhere hidden. * E.g. the one which creates a handshake_id isn't usable for this, it must * always map the same ID to the same hash. * * Result is NOT MAPPED to CANDIDATES_TO_KEEP range, i.e. map before using * it for list access. */ static hash_t id_hash(const Assoc *assoc, const uint8_t *id) { uint32_t i, res = 0x19a64e82; for (i = 0; i < crypto_box_PUBLICKEYBYTES; i++) res = ((res << 1) ^ id[i]) + (res >> 31); /* can't have zero as hash, a) marks an unused spot, * b) collision function is multiplicative */ if (!(res % assoc->candidates_bucket_size)) res++; return res; } /* up to HASH_COLLIDE_COUNT calls to different spots, * result IS mapped to CANDIDATES_TO_KEEP range */ static hash_t hash_collide(const Assoc *assoc, hash_t hash) { uint64_t hash64 = hash % assoc->candidates_bucket_size; hash64 = (hash64 * HASH_COLLIDE_PRIME) % assoc->candidates_bucket_size; hash_t retval = hash64; /* this should never happen when CANDIDATES_TO_KEEP is prime and hash not a multiple * (id_hash() checks for a multiple and returns a different hash in that case) * * ( 1 .. (prime - 1) is a group over multiplication and every number has its inverse * in the group, so no multiplication should ever end on zero as long neither * of the two factors was zero-equivalent ) * * BUT: because the usage of the word "never" invokes Murphy's law, catch it */ if (!retval) { #ifdef DEBUG fprintf(stderr, "assoc::hash_collide: hash %u, bucket size %u => %u!", hash, (uint)assoc->candidates_bucket_size, retval); assert(retval != 0); #endif retval = 1; } return retval; } /* returns the "seen" assoc related to the ipp */ static IPPTsPng *entry_assoc(Client_entry *cl_entry, const IP_Port *ipp) { if (!cl_entry) return NULL; if (ipp->ip.family == AF_INET) return &cl_entry->client.assoc4; if (ipp->ip.family == AF_INET6) return &cl_entry->client.assoc6; return NULL; } /* returns the "heard" assoc related to the ipp */ static IP_Port *entry_heard_get(Client_entry *entry, const IP_Port *ipp) { if (ipp->ip.family == AF_INET) return &entry->assoc_heard4; else if (ipp->ip.family == AF_INET6) return &entry->assoc_heard6; else return NULL; } /* store a "heard" entry * overwrites empty entry, does NOT overwrite non-LAN ip with * LAN ip * * returns 1 if the entry did change */ static int entry_heard_store(Client_entry *entry, const IPPTs *ippts) { if (!entry || !ippts) return 0; if (!ipport_isset(&ippts->ip_port)) return 0; IP_Port *heard; const IP_Port *ipp = &ippts->ip_port; if (ipp->ip.family == AF_INET) heard = &entry->assoc_heard4; else if (ipp->ip.family == AF_INET6) heard = &entry->assoc_heard6; else return 0; if (ipport_equal(ipp, heard)) return 0; if (!ipport_isset(heard)) { *heard = *ipp; entry->heard_at = ippts->timestamp; entry->heard_family = ipp->ip.family; return 1; } /* don't destroy a good address with a crappy one * (unless we're very timed out) */ uint8_t LAN_ipp = LAN_ip(ipp->ip) == 0; uint8_t LAN_entry = LAN_ip(heard->ip) == 0; if (LAN_ipp && !LAN_entry && !is_timeout(entry->heard_at, CANDIDATES_HEARD_TIMEOUT)) return 0; *heard = *ipp; entry->heard_at = ippts->timestamp; entry->heard_family = ipp->ip.family; return 1; } /* maps Assoc callback signature to id_closest() */ static int assoc_id_closest(const Assoc *assoc, void *callback_data, const uint8_t *client_id, const uint8_t *client_id1, const uint8_t *client_id2) { return id_closest(client_id, client_id1, client_id2); } static bucket_t id_bucket(const uint8_t *id, uint8_t bits) { /* return the first "bits" bits of id */ bucket_t retval = 0; uint8_t pos = 0; while (bits > 8) { retval = (retval << 8) | id[pos++]; bits -= 8; } return (retval << bits) | (id[pos] >> (8 - bits)); } /*****************************************************************************/ /* CANDIDATES FUNCTIONS */ /*****************************************************************************/ static bucket_t candidates_id_bucket(const Assoc *assoc, const uint8_t *id) { return id_bucket(id, assoc->candidates_bucket_bits); } static uint8_t candidates_search(const Assoc *assoc, const uint8_t *id, hash_t hash, Client_entry **entryptr) { bucket_t bucket = candidates_id_bucket(assoc, id); candidates_bucket *cnd_bckt = &assoc->candidates[bucket]; size_t coll, pos = hash % assoc->candidates_bucket_size; for (coll = 0; coll < HASH_COLLIDE_COUNT; pos = hash_collide(assoc, pos) , coll++) { Client_entry *entry = &cnd_bckt->list[pos]; if (entry->hash == hash) if (id_equal(entry->client.public_key, id)) { *entryptr = entry; return 1; } } *entryptr = NULL; return 0; } static void candidates_update_assoc(const Assoc *assoc, Client_entry *entry, uint8_t used, const IPPTs *ippts_send, const IP_Port *ipp_recv) { if (!assoc || !entry || !ippts_send) return; IPPTsPng *ipptsp = entry_assoc(entry, &ippts_send->ip_port); if (!ipptsp) return; if (used) entry->used_at = unix_time(); /* do NOT do anything related to wanted, that's handled outside, * just update the assoc (in the most sensible way) */ if (ipp_recv) { ipptsp->ip_port = ippts_send->ip_port; ipptsp->timestamp = ippts_send->timestamp; ipptsp->ret_ip_port = *ipp_recv; ipptsp->ret_timestamp = unix_time(); entry->seen_at = unix_time(); entry->seen_family = ippts_send->ip_port.ip.family; return; } entry_heard_store(entry, ippts_send); } static uint8_t candidates_create_internal(const Assoc *assoc, hash_t const hash, const uint8_t *id, uint8_t seen, uint8_t used, bucket_t *bucketptr, size_t *posptr) { if (!assoc || !id || !bucketptr || !posptr) return 0; bucket_t bucket = candidates_id_bucket(assoc, id); candidates_bucket *cnd_bckt = &assoc->candidates[bucket]; size_t coll, pos = hash % assoc->candidates_bucket_size, check; size_t pos_check[6]; memset(pos_check, 0, sizeof(pos_check)); for (coll = 0; coll < HASH_COLLIDE_COUNT; pos = hash_collide(assoc, pos) , coll++) { Client_entry *entry = &cnd_bckt->list[pos]; /* unset */ if (!entry->hash) { *bucketptr = bucket; *posptr = pos; return 1; } /* 0. bad * 1. seen bad, heard good * 2. seen good * 3. used */ // enumerated lists are superior to magic numbers if (!is_timeout(entry->used_at, BAD_NODE_TIMEOUT)) check = USED; else if (!is_timeout(entry->seen_at, CANDIDATES_SEEN_TIMEOUT)) check = SEENG; else if (!is_timeout(entry->heard_at, CANDIDATES_HEARD_TIMEOUT)) check = SEENB_HEARDG; else check = BAD; if (!pos_check[check]) pos_check[check] = pos + 1; } /* used > seen > heard > bad */ size_t i, pos_max = used ? USED : (seen ? SEENG : SEENB_HEARDG); for (i = 0; i < pos_max; i++) if (pos_check[i]) { *bucketptr = bucket; *posptr = pos_check[i] - 1; return 1; } return 0; } static uint8_t candidates_create_new(const Assoc *assoc, hash_t hash, const uint8_t *id, uint8_t used, const IPPTs *ippts_send, const IP_Port *ipp_recv) { if (!assoc || !id || !ippts_send) return 0; bucket_t bucket; size_t pos; if (!candidates_create_internal(assoc, hash, id, ipp_recv != NULL, used, &bucket, &pos)) return 0; candidates_bucket *cnd_bckt = &assoc->candidates[bucket]; Client_entry *entry = &cnd_bckt->list[pos]; memset(entry, 0, sizeof(*entry)); IPPTsPng *ipptsp = entry_assoc(entry, &ippts_send->ip_port); if (!ipptsp) return 0; entry->hash = hash; id_copy(entry->client.public_key, id); if (used) entry->used_at = unix_time(); if (ipp_recv && !ipport_isset(ipp_recv)) ipp_recv = NULL; if (ipp_recv) { entry->seen_at = ippts_send->timestamp; entry->seen_family = ippts_send->ip_port.ip.family; ipptsp->ip_port = ippts_send->ip_port; ipptsp->timestamp = ippts_send->timestamp; ipptsp->ret_ip_port = *ipp_recv; ipptsp->ret_timestamp = unix_time(); } else { IP_Port *heard = entry_heard_get(entry, &ippts_send->ip_port); if (heard) { entry->heard_at = ippts_send->timestamp; entry->heard_family = ippts_send->ip_port.ip.family; *heard = ippts_send->ip_port; } } return 1; } /*****************************************************************************/ static void client_id_self_update(Assoc *assoc) { if (assoc->self_hash) return; size_t i, sum = 0; for (i = 0; i < crypto_box_PUBLICKEYBYTES; i++) sum |= assoc->self_client_id[i]; if (!sum) return; assoc->self_hash = id_hash(assoc, assoc->self_client_id); LOGGER_DEBUG("id is now set, purging cache of self-references"); /* if we already added some (or loaded some) entries, * look and remove if we find a match */ bucket_t b_id = candidates_id_bucket(assoc, assoc->self_client_id); candidates_bucket *cnd_bckt = &assoc->candidates[b_id]; size_t pos = assoc->self_hash % assoc->candidates_bucket_size; for (i = 0; i < HASH_COLLIDE_COUNT; pos = hash_collide(assoc, pos), i++) { Client_entry *entry = &cnd_bckt->list[pos]; if (entry->hash == assoc->self_hash) if (id_equal(entry->client.public_key, assoc->self_client_id)) entry->hash = 0; } } /*****************************************************************************/ /* TRIGGER FUNCTIONS */ /*****************************************************************************/ /* Central entry point for new associations: add a new candidate to the cache * seen should be 0 (zero), if the candidate was announced by someone else, * seen should be 1 (one), if there is confirmed connectivity (a definite response) */ uint8_t Assoc_add_entry(Assoc *assoc, const uint8_t *id, const IPPTs *ippts_send, const IP_Port *ipp_recv, uint8_t used) { if (!assoc || !id || !ippts_send) return 0; if (!assoc->self_hash) { client_id_self_update(assoc); if (!assoc->self_hash) return 0; } if (!ipport_isset(&ippts_send->ip_port)) return 0; if (ipp_recv && !ipport_isset(ipp_recv)) ipp_recv = NULL; hash_t hash = id_hash(assoc, id); if (hash == assoc->self_hash) if (id_equal(id, assoc->self_client_id)) return 0; /* if it's new: * callback, if there's desire, add to clients, else to candidates * * if it's "old": * if it's client: refresh * if it's candidate: * if !ipp_recv, refresh * if ipp_recv: callback, if there's desire, move to candidates */ Client_entry *cnd_entry; if (!candidates_search(assoc, id, hash, &cnd_entry)) { if (candidates_create_new(assoc, hash, id, used, ippts_send, ipp_recv)) return 1; else return 0; } else { candidates_update_assoc(assoc, cnd_entry, used, ippts_send, ipp_recv); return 2; } } /*****************************************************************************/ /* MAIN USE */ /*****************************************************************************/ uint8_t Assoc_get_close_entries(Assoc *assoc, Assoc_close_entries *state) { if (!assoc || !state || !state->wanted_id || !state->result) return 0; if (!assoc->self_hash) { client_id_self_update(assoc); if (!assoc->self_hash) return 0; } if (!state->distance_relative_func) state->distance_relative_func = assoc_id_closest; if (!state->distance_absolute_func) state->distance_absolute_func = id_distance; size_t dist_list_len = assoc->candidates_bucket_count * assoc->candidates_bucket_size; uint64_t dist_list[dist_list_len]; memset(dist_list, ~0, dist_list_len * sizeof(dist_list[0])); bucket_t b; size_t i; for (b = 0; b < assoc->candidates_bucket_count; b++) { candidates_bucket *cnd_bckt = &assoc->candidates[b]; for (i = 0; i < assoc->candidates_bucket_size; i++) { Client_entry *entry = &cnd_bckt->list[i]; if (entry->hash) { if (state->flags & ProtoIPv4) { if (!ipport_isset(&entry->client.assoc4.ip_port)) continue; if (!(state->flags & LANOk)) if (!LAN_ip(entry->client.assoc4.ip_port.ip)) continue; } if (state->flags & ProtoIPv6) { if (!ipport_isset(&entry->client.assoc6.ip_port)) continue; if (!(state->flags & LANOk)) if (!LAN_ip(entry->client.assoc6.ip_port.ip)) continue; } uint64_t dist = state->distance_absolute_func(assoc, state->custom_data, state->wanted_id, entry->client.public_key); uint32_t index = b * assoc->candidates_bucket_size + i; dist_list[index] = (dist << DISTANCE_INDEX_INDEX_BITS) | index; } } } qsort(dist_list, dist_list_len, sizeof(dist_list[0]), dist_index_comp); /* ok, ok, it's not *perfectly* sorted, because we used an absolute distance * go over the result and see if we need to "smoothen things out" * because those should be only very few and short streaks, the worst regularly * used sorting function aka bubble sort is used */ uint64_t dist_prev = ~0; size_t ind_prev = ~0, ind_curr; size_t len = 1; for (ind_curr = 0; ind_curr < dist_list_len; ind_curr++) { /* sorted increasingly, so an invalid entry marks the end */ if ((dist_list[ind_curr] & DISTANCE_INDEX_INDEX_MASK) == DISTANCE_INDEX_INDEX_MASK) break; uint64_t dist_curr = dist_list[ind_curr] >> DISTANCE_INDEX_INDEX_BITS; if (dist_prev == dist_curr) len++; else { if (len > 1) dist_index_bubble(assoc, dist_list, ind_prev, ind_curr - 1, state->wanted_id, state->custom_data, state->distance_relative_func); dist_prev = dist_curr; ind_prev = ind_curr; len = 1; } } if (len > 1) dist_index_bubble(assoc, dist_list, ind_prev, ind_curr - 1, state->wanted_id, state->custom_data, state->distance_relative_func); /* ok, now dist_list is a strictly ascending sorted list of nodes * a) extract CLOSE_QUOTA_USED clients, not timed out * b) extract (1 - QUOTA) (better!) clients & candidates, not timed out * c) save candidates which would be better, if contact can be established */ size_t client_quota_good = 0, pos = 0; size_t client_quota_max = state->count_good; ssize_t taken_last = - 1; for (i = 0; (i < dist_list_len) && (pos < state->count); i++) { /* sorted increasingly, so an invalid entry marks the end */ if ((dist_list[i] & DISTANCE_INDEX_INDEX_MASK) == DISTANCE_INDEX_INDEX_MASK) break; Client_entry *entry = dist_index_entry(assoc, dist_list[i]); if (entry && entry->hash) { if (client_quota_good >= client_quota_max) { state->result[pos++] = &entry->client; taken_last = i; } else { if (state->flags & (ProtoIPv4 | ProtoIPv6)) { if ((state->flags & ProtoIPv4) && is_timeout(entry->client.assoc4.timestamp, BAD_NODE_TIMEOUT)) continue; if ((state->flags & ProtoIPv6) && is_timeout(entry->client.assoc6.timestamp, BAD_NODE_TIMEOUT)) continue; } else if (is_timeout(entry->seen_at, BAD_NODE_TIMEOUT)) continue; state->result[pos++] = &entry->client; client_quota_good++; taken_last = i; } } } /* if we had not enough valid entries the list might still not be filled. * * start again from last taken client, but leave out any requirement */ if (pos < state->count) { for (i = taken_last + 1; (i < dist_list_len) && (pos < state->count); i++) { /* sorted increasingly, so an invalid entry marks the end */ if ((dist_list[i] & DISTANCE_INDEX_INDEX_MASK) == DISTANCE_INDEX_INDEX_MASK) break; Client_entry *entry = dist_index_entry(assoc, dist_list[i]); if (entry && entry->hash) state->result[pos++] = &entry->client; } } return pos; } /*****************************************************************************/ /* GLOBAL STRUCTURE FUNCTIONS */ /*****************************************************************************/ static uint8_t odd_min9_is_prime(size_t value) { size_t i = 3; while (i * i <= value) { if (!(value % i)) return 0; i += 2; } return 1; } static size_t prime_upto_min9(size_t limit) { /* even => odd */ limit = limit - (1 - (limit % 2)); while (!odd_min9_is_prime(limit)) limit -= 2; return limit; } /* create */ Assoc *new_Assoc(size_t bits, size_t entries, const uint8_t *public_id) { if (!public_id) return NULL; Assoc *assoc = calloc(1, sizeof(*assoc)); if (!assoc) return NULL; /* * bits must be in [ 2 .. 15 ] * entries must be a prime */ if (bits < 2) bits = 2; else if (bits > 15) bits = 15; assoc->candidates_bucket_bits = bits; assoc->candidates_bucket_count = 1U << bits; if (entries < 25) { if (entries <= 6) entries = 5; else { entries = entries - (1 - (entries % 2)); /* even => odd */ /* 7..23: all odds but 9&15 are prime */ if (!(entries % 3)) /* 9, 15 */ entries -= 2; /* 7, 13 */ } } else if (entries > ((1 << 17) - 1)) /* 130k+ */ entries = (1 << 17) - 1; else { /* 9+: test and find a prime less or equal */ size_t entries_test = prime_upto_min9(entries); if (entries_test == HASH_COLLIDE_PRIME) /* disallowed */ entries_test = prime_upto_min9(entries_test - 1); if (entries_test != entries) { LOGGER_DEBUG("trimmed %i to %i.\n", (int)entries, (int)entries_test); entries = (size_t)entries_test; } } assoc->candidates_bucket_size = entries; /* allocation: preferably few blobs */ size_t bckt, cix; Client_entry *clients = malloc(sizeof(*clients) * assoc->candidates_bucket_count * assoc->candidates_bucket_size); if (!clients) { free(assoc); return NULL; } candidates_bucket *lists = malloc(sizeof(*lists) * assoc->candidates_bucket_count); if (!lists) { free(assoc); free(clients); return NULL; } for (bckt = 0; bckt < assoc->candidates_bucket_count; bckt++) { candidates_bucket *list = &lists[bckt]; list->list = &clients[bckt * assoc->candidates_bucket_size]; for (cix = 0; cix < assoc->candidates_bucket_size; cix++) list->list[cix].hash = 0; } assoc->candidates = lists; assoc->getnodes = unix_time(); id_copy(assoc->self_client_id, public_id); client_id_self_update(assoc); return assoc; } Assoc *new_Assoc_default(const uint8_t *public_id) { /* original 8, 251 averages to ~32k entries... probably the whole DHT :D * 320 entries is fine, hopefully */ return new_Assoc(6, 15, public_id); } /* own client_id, assocs for this have to be ignored */ void Assoc_self_client_id_changed(Assoc *assoc, const uint8_t *id) { if (assoc && id) { assoc->self_hash = 0; id_copy(assoc->self_client_id, id); client_id_self_update(assoc); } } #ifdef TOX_LOGGER static char *idpart2str(uint8_t *id, size_t len); #endif /* TOX_LOGGER */ /* refresh buckets */ void do_Assoc(Assoc *assoc, DHT *dht) { if (is_timeout(assoc->getnodes, ASSOC_BUCKET_REFRESH)) { assoc->getnodes = unix_time(); size_t candidate = (rand() % assoc->candidates_bucket_count) + assoc->candidates_bucket_count; /* in that bucket or the buckets closest to it: * find the best heard candidate * find the best seen candidate * send getnode() requests to both */ uint8_t *target_id = NULL; Client_entry *heard = NULL, *seen = NULL; size_t i, k, m; for (i = 1; i < assoc->candidates_bucket_count; i++) { if (i % 2) k = - (i >> 1); else k = i >> 1; size_t bckt = (candidate + k) % assoc->candidates_bucket_count; for (m = 0; m < assoc->candidates_bucket_size; m++) if (assoc->candidates[bckt].list[m].hash) { Client_entry *entry = &assoc->candidates[bckt].list[m]; if (!is_timeout(entry->getnodes, CANDIDATES_SEEN_TIMEOUT)) continue; if (!target_id) target_id = entry->client.public_key; if (entry->seen_at) { if (!seen) if (!is_timeout(entry->seen_at, CANDIDATES_SEEN_TIMEOUT)) seen = entry; } if (entry->heard_at) { if (!heard) if (!is_timeout(entry->heard_at, CANDIDATES_HEARD_TIMEOUT)) heard = entry; } if (seen && heard) break; } if (seen && heard) break; } if (seen) { IPPTsPng *ippts = seen->seen_family == AF_INET ? &seen->client.assoc4 : &seen->client.assoc6; LOGGER_DEBUG("[%u] => S[%s...] %s:%u", (uint32_t)(candidate % assoc->candidates_bucket_count), idpart2str(seen->client.public_key, 8), ip_ntoa(&ippts->ip_port.ip), htons(ippts->ip_port.port)); DHT_getnodes(dht, &ippts->ip_port, seen->client.public_key, target_id); seen->getnodes = unix_time(); } if (heard && (heard != seen)) { IP_Port *ipp = heard->heard_family == AF_INET ? &heard->assoc_heard4 : &heard->assoc_heard6; LOGGER_DEBUG("[%u] => H[%s...] %s:%u", (uint32_t)(candidate % assoc->candidates_bucket_count), idpart2str(heard->client.public_key, 8), ip_ntoa(&ipp->ip), htons(ipp->port)); DHT_getnodes(dht, ipp, heard->client.public_key, target_id); heard->getnodes = unix_time(); } LOGGER_SCOPE ( if ( !heard && !seen ) LOGGER_DEBUG("[%u] => no nodes to talk to??", (uint32_t)(candidate % assoc->candidates_bucket_count)); ); } } /* destroy */ void kill_Assoc(Assoc *assoc) { if (assoc) { free(assoc->candidates->list); free(assoc->candidates); free(assoc); } } #ifdef TOX_LOGGER static char buffer[crypto_box_PUBLICKEYBYTES * 2 + 1]; static char *idpart2str(uint8_t *id, size_t len) { if (len > crypto_box_PUBLICKEYBYTES) len = crypto_box_PUBLICKEYBYTES; size_t i; for (i = 0; i < len; i++) sprintf(buffer + i * 2, "%02hhx", id[i]); buffer[len * 2] = 0; return buffer; } void Assoc_status(const Assoc *assoc) { if (!assoc) { LOGGER_TRACE("Assoc status: no assoc"); return; } LOGGER_TRACE("[b:p] hash => [id...] used, seen, heard"); size_t bid, cid, total = 0; for (bid = 0; bid < assoc->candidates_bucket_count; bid++) { candidates_bucket *bucket = &assoc->candidates[bid]; for (cid = 0; cid < assoc->candidates_bucket_size; cid++) { Client_entry *entry = &bucket->list[cid]; if (entry->hash) { total++; LOGGER_TRACE("[%3i:%3i] %08x => [%s...] %i, %i(%c), %i(%c)\n", (int)bid, (int)cid, entry->hash, idpart2str(entry->client.public_key, 8), entry->used_at ? (int)(unix_time() - entry->used_at) : 0, entry->seen_at ? (int)(unix_time() - entry->seen_at) : 0, entry->seen_at ? (entry->seen_family == AF_INET ? '4' : (entry->seen_family == AF_INET6 ? '6' : '?')) : '?', entry->heard_at ? (int)(unix_time() - entry->heard_at) : 0, entry->heard_at ? (entry->heard_family == AF_INET ? '4' : (entry->heard_family == AF_INET6 ? '6' : '?')) : '?'); } } } if (total) { LOGGER_TRACE("Total: %i entries, table usage %i%%.\n", (int)total, (int)(total * 100 / (assoc->candidates_bucket_count * assoc->candidates_bucket_size))); } } #endif /* TOX_LOGGER */ ================================================ FILE: toxcore/assoc.h ================================================ #ifndef __ASSOC_H__ #define __ASSOC_H__ /* used by rendezvous */ #define ASSOC_AVAILABLE /* For the legalese parts, see tox.h. */ /* enumerated lists are superior to magic numbers */ enum NODE_STATUS { BAD, SEENB_HEARDG, SEENG, USED }; /* * Module to store currently unused ID <=> IP associations * for a potential future use */ typedef struct Assoc Assoc; /*****************************************************************************/ /* custom distance handler, if it's not ID-distance based * return values exactly like id_closest() */ typedef int (*Assoc_distance_relative_callback)(const Assoc *assoc, void *callback_data, const uint8_t *client_id, const uint8_t *client_id1, const uint8_t *client_id2); #define DISTANCE_INDEX_DISTANCE_BITS 44 /* absolute distance: can be same for different client_id_check values * return value should have DISTANCE_INDEX_DISTANCE_BITS valid bits */ typedef uint64_t (*Assoc_distance_absolute_callback)(const Assoc *assoc, void *callback_data, const uint8_t *client_id_ref, const uint8_t *client_id_check); /*****************************************************************************/ /* Central entry point for new associations: add a new candidate to the cache * returns 1 if entry is stored, 2 if existing entry was updated, 0 else */ uint8_t Assoc_add_entry(Assoc *assoc, const uint8_t *id, const IPPTs *ippts_send, const IP_Port *ipp_recv, uint8_t used); /*****************************************************************************/ typedef enum AssocCloseEntriesFlags { ProtoIPv4 = 1, ProtoIPv6 = 2, LANOk = 4, } AssocCloseEntriesFlags; typedef struct Assoc_close_entries { void *custom_data; /* given to distance functions */ uint8_t *wanted_id; /* the target client_id */ uint8_t flags; /* additional flags */ Assoc_distance_relative_callback distance_relative_func; Assoc_distance_absolute_callback distance_absolute_func; uint8_t count_good; /* that many should be "good" w.r.t. timeout */ uint8_t count; /* allocated number of close_indices */ Client_data **result; } Assoc_close_entries; /* find up to close_count nodes to put into close_nodes_used of ID_Nodes * the distance functions can be NULL, then standard distance functions will be used * the caller is responsible for allocating close_indices of sufficient size * * returns 0 on error * returns the number of found nodes and the list of indices usable by Assoc_client() * the caller is assumed to be registered from Assoc_register_callback() * if they aren't, they should copy the Client_data and call Assoc_client_drop() */ uint8_t Assoc_get_close_entries(Assoc *assoc, Assoc_close_entries *close_entries); /*****************************************************************************/ /* create: default sizes (6, 5 => 320 entries) */ Assoc *new_Assoc_default(const uint8_t *public_id); /* create: customized sizes * total is (2^bits) * entries * bits should be between 2 and 15 (else it's trimmed) * entries will be reduced to the closest prime smaller or equal * * preferably bits should be large and entries small to ensure spread * in the search space (e. g. 5, 5 is preferable to 2, 41) */ Assoc *new_Assoc(size_t bits, size_t entries, const uint8_t *public_id); /* public_id changed (loaded), update which entry isn't stored */ void Assoc_self_client_id_changed(Assoc *assoc, const uint8_t *public_id); /* every 45s send out a getnodes() for a "random" bucket */ #define ASSOC_BUCKET_REFRESH 45 /* refresh bucket's data from time to time * this must be called only from DHT */ void do_Assoc(Assoc *assoc, DHT *dht); /* destroy */ void kill_Assoc(Assoc *assoc); #ifdef TOX_LOGGER void Assoc_status(const Assoc *assoc); #endif /* TOX_LOGGER */ #endif /* !__ASSOC_H__ */ ================================================ FILE: toxcore/crypto_core.c ================================================ /* net_crypto.c * * Functions for the core crypto. * * NOTE: This code has to be perfect. We don't mess around with encryption. * * 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 "crypto_core.h" #if crypto_box_PUBLICKEYBYTES != 32 #error crypto_box_PUBLICKEYBYTES is required to be 32 bytes for public_key_cmp to work, #endif /* compare 2 public keys of length crypto_box_PUBLICKEYBYTES, not vulnerable to timing attacks. returns 0 if both mem locations of length are equal, return -1 if they are not. */ int public_key_cmp(const uint8_t *pk1, const uint8_t *pk2) { return crypto_verify_32(pk1, pk2); } /* return a random number. */ uint32_t random_int(void) { uint32_t randnum; randombytes((uint8_t *)&randnum , sizeof(randnum)); return randnum; } uint64_t random_64b(void) { uint64_t randnum; randombytes((uint8_t *)&randnum, sizeof(randnum)); return randnum; } /* Check if a Tox public key crypto_box_PUBLICKEYBYTES is valid or not. * This should only be used for input validation. * * return 0 if it isn't. * return 1 if it is. */ int public_key_valid(const uint8_t *public_key) { if (public_key[31] >= 128) /* Last bit of key is always zero. */ return 0; return 1; } /* Precomputes the shared key from their public_key and our secret_key. * This way we can avoid an expensive elliptic curve scalar multiply for each * encrypt/decrypt operation. * enc_key has to be crypto_box_BEFORENMBYTES bytes long. */ void encrypt_precompute(const uint8_t *public_key, const uint8_t *secret_key, uint8_t *enc_key) { crypto_box_beforenm(enc_key, public_key, secret_key); } int encrypt_data_symmetric(const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *plain, uint32_t length, uint8_t *encrypted) { if (length == 0 || !secret_key || !nonce || !plain || !encrypted) return -1; uint8_t temp_plain[length + crypto_box_ZEROBYTES]; uint8_t temp_encrypted[length + crypto_box_MACBYTES + crypto_box_BOXZEROBYTES]; memset(temp_plain, 0, crypto_box_ZEROBYTES); memcpy(temp_plain + crypto_box_ZEROBYTES, plain, length); // Pad the message with 32 0 bytes. if (crypto_box_afternm(temp_encrypted, temp_plain, length + crypto_box_ZEROBYTES, nonce, secret_key) != 0) return -1; /* Unpad the encrypted message. */ memcpy(encrypted, temp_encrypted + crypto_box_BOXZEROBYTES, length + crypto_box_MACBYTES); return length + crypto_box_MACBYTES; } int decrypt_data_symmetric(const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *encrypted, uint32_t length, uint8_t *plain) { if (length <= crypto_box_BOXZEROBYTES || !secret_key || !nonce || !encrypted || !plain) return -1; uint8_t temp_plain[length + crypto_box_ZEROBYTES]; uint8_t temp_encrypted[length + crypto_box_BOXZEROBYTES]; memset(temp_encrypted, 0, crypto_box_BOXZEROBYTES); memcpy(temp_encrypted + crypto_box_BOXZEROBYTES, encrypted, length); // Pad the message with 16 0 bytes. if (crypto_box_open_afternm(temp_plain, temp_encrypted, length + crypto_box_BOXZEROBYTES, nonce, secret_key) != 0) return -1; memcpy(plain, temp_plain + crypto_box_ZEROBYTES, length - crypto_box_MACBYTES); return length - crypto_box_MACBYTES; } int encrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *plain, uint32_t length, uint8_t *encrypted) { if (!public_key || !secret_key) return -1; uint8_t k[crypto_box_BEFORENMBYTES]; encrypt_precompute(public_key, secret_key, k); int ret = encrypt_data_symmetric(k, nonce, plain, length, encrypted); sodium_memzero(k, sizeof k); return ret; } int decrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *encrypted, uint32_t length, uint8_t *plain) { if (!public_key || !secret_key) return -1; uint8_t k[crypto_box_BEFORENMBYTES]; encrypt_precompute(public_key, secret_key, k); int ret = decrypt_data_symmetric(k, nonce, encrypted, length, plain); sodium_memzero(k, sizeof k); return ret; } /* Increment the given nonce by 1. */ void increment_nonce(uint8_t *nonce) { /* FIXME use increment_nonce_number(nonce, 1) or sodium_increment (change to little endian) * NOTE don't use breaks inside this loop * In particular, make sure, as far as possible, * that loop bounds and their potential underflow or overflow * are independent of user-controlled input (you may have heard of the Heartbleed bug). */ uint32_t i = crypto_box_NONCEBYTES; uint_fast16_t carry = 1U; for (; i != 0; --i) { carry += (uint_fast16_t) nonce[i - 1]; nonce[i - 1] = (uint8_t) carry; carry >>= 8; } } /* increment the given nonce by num */ void increment_nonce_number(uint8_t *nonce, uint32_t host_order_num) { /* NOTE don't use breaks inside this loop * In particular, make sure, as far as possible, * that loop bounds and their potential underflow or overflow * are independent of user-controlled input (you may have heard of the Heartbleed bug). */ const uint32_t big_endian_num = htonl(host_order_num); const uint8_t *const num_vec = (const uint8_t *) &big_endian_num; uint8_t num_as_nonce[crypto_box_NONCEBYTES] = {0}; num_as_nonce[crypto_box_NONCEBYTES - 4] = num_vec[0]; num_as_nonce[crypto_box_NONCEBYTES - 3] = num_vec[1]; num_as_nonce[crypto_box_NONCEBYTES - 2] = num_vec[2]; num_as_nonce[crypto_box_NONCEBYTES - 1] = num_vec[3]; uint32_t i = crypto_box_NONCEBYTES; uint_fast16_t carry = 0U; for (; i != 0; --i) { carry += (uint_fast16_t) nonce[i - 1] + (uint_fast16_t) num_as_nonce[i - 1]; nonce[i - 1] = (unsigned char) carry; carry >>= 8; } } /* Fill the given nonce with random bytes. */ void random_nonce(uint8_t *nonce) { randombytes(nonce, crypto_box_NONCEBYTES); } /* Fill a key crypto_box_KEYBYTES big with random bytes */ void new_symmetric_key(uint8_t *key) { randombytes(key, crypto_box_KEYBYTES); } /* Gives a nonce guaranteed to be different from previous ones.*/ void new_nonce(uint8_t *nonce) { random_nonce(nonce); } /* Create a request to peer. * send_public_key and send_secret_key are the pub/secret keys of the sender. * recv_public_key is public key of receiver. * packet must be an array of MAX_CRYPTO_REQUEST_SIZE big. * Data represents the data we send with the request with length being the length of the data. * request_id is the id of the request (32 = friend request, 254 = ping request). * * return -1 on failure. * return the length of the created packet on success. */ int create_request(const uint8_t *send_public_key, const uint8_t *send_secret_key, uint8_t *packet, const uint8_t *recv_public_key, const uint8_t *data, uint32_t length, uint8_t request_id) { if (!send_public_key || !packet || !recv_public_key || !data) return -1; if (MAX_CRYPTO_REQUEST_SIZE < length + 1 + crypto_box_PUBLICKEYBYTES * 2 + crypto_box_NONCEBYTES + 1 + crypto_box_MACBYTES) return -1; uint8_t *nonce = packet + 1 + crypto_box_PUBLICKEYBYTES * 2; new_nonce(nonce); uint8_t temp[MAX_CRYPTO_REQUEST_SIZE]; // FIXME sodium_memzero before exit function memcpy(temp + 1, data, length); temp[0] = request_id; int len = encrypt_data(recv_public_key, send_secret_key, nonce, temp, length + 1, 1 + crypto_box_PUBLICKEYBYTES * 2 + crypto_box_NONCEBYTES + packet); if (len == -1) return -1; packet[0] = NET_PACKET_CRYPTO; memcpy(packet + 1, recv_public_key, crypto_box_PUBLICKEYBYTES); memcpy(packet + 1 + crypto_box_PUBLICKEYBYTES, send_public_key, crypto_box_PUBLICKEYBYTES); return len + 1 + crypto_box_PUBLICKEYBYTES * 2 + crypto_box_NONCEBYTES; } /* Puts the senders public key in the request in public_key, the data from the request * in data if a friend or ping request was sent to us and returns the length of the data. * packet is the request packet and length is its length. * * return -1 if not valid request. */ int handle_request(const uint8_t *self_public_key, const uint8_t *self_secret_key, uint8_t *public_key, uint8_t *data, uint8_t *request_id, const uint8_t *packet, uint16_t length) { if (!self_public_key || !public_key || !data || !request_id || !packet) return -1; if (length <= crypto_box_PUBLICKEYBYTES * 2 + crypto_box_NONCEBYTES + 1 + crypto_box_MACBYTES || length > MAX_CRYPTO_REQUEST_SIZE) return -1; if (public_key_cmp(packet + 1, self_public_key) != 0) return -1; memcpy(public_key, packet + 1 + crypto_box_PUBLICKEYBYTES, crypto_box_PUBLICKEYBYTES); const uint8_t *nonce = packet + 1 + crypto_box_PUBLICKEYBYTES * 2; uint8_t temp[MAX_CRYPTO_REQUEST_SIZE]; // FIXME sodium_memzero before exit function int len1 = decrypt_data(public_key, self_secret_key, nonce, packet + 1 + crypto_box_PUBLICKEYBYTES * 2 + crypto_box_NONCEBYTES, length - (crypto_box_PUBLICKEYBYTES * 2 + crypto_box_NONCEBYTES + 1), temp); if (len1 == -1 || len1 == 0) return -1; request_id[0] = temp[0]; --len1; memcpy(data, temp + 1, len1); return len1; } ================================================ FILE: toxcore/crypto_core.h ================================================ /* crypto_core.h * * Functions for the core crypto. * * 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 CORE_CRYPTO_H #define CORE_CRYPTO_H #include "network.h" #ifndef VANILLA_NACL /* We use libsodium by default. */ #include #else #include #include #include #include #include #include #include #define crypto_box_MACBYTES (crypto_box_ZEROBYTES - crypto_box_BOXZEROBYTES) /* I know */ #define sodium_memcmp(a, b, c) memcmp(a, b, c) #define sodium_memzero(a, c) memset(a, 0, c) #endif #define crypto_box_KEYBYTES (crypto_box_BEFORENMBYTES) /* compare 2 public keys of length crypto_box_PUBLICKEYBYTES, not vulnerable to timing attacks. returns 0 if both mem locations of length are equal, return -1 if they are not. */ int public_key_cmp(const uint8_t *pk1, const uint8_t *pk2); /* return a random number. * * random_int for a 32bin int. * random_64b for a 64bit int. */ uint32_t random_int(void); uint64_t random_64b(void); /* Check if a Tox public key crypto_box_PUBLICKEYBYTES is valid or not. * This should only be used for input validation. * * return 0 if it isn't. * return 1 if it is. */ int public_key_valid(const uint8_t *public_key); /* Encrypts plain of length length to encrypted of length + 16 using the * public key(32 bytes) of the receiver and the secret key of the sender and a 24 byte nonce. * * return -1 if there was a problem. * return length of encrypted data if everything was fine. */ int encrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *plain, uint32_t length, uint8_t *encrypted); /* Decrypts encrypted of length length to plain of length length - 16 using the * public key(32 bytes) of the sender, the secret key of the receiver and a 24 byte nonce. * * return -1 if there was a problem (decryption failed). * return length of plain data if everything was fine. */ int decrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *encrypted, uint32_t length, uint8_t *plain); /* Fast encrypt/decrypt operations. Use if this is not a one-time communication. encrypt_precompute does the shared-key generation once so it does not have to be preformed on every encrypt/decrypt. */ void encrypt_precompute(const uint8_t *public_key, const uint8_t *secret_key, uint8_t *enc_key); /* Encrypts plain of length length to encrypted of length + 16 using a * secret key crypto_box_KEYBYTES big and a 24 byte nonce. * * return -1 if there was a problem. * return length of encrypted data if everything was fine. */ int encrypt_data_symmetric(const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *plain, uint32_t length, uint8_t *encrypted); /* Decrypts encrypted of length length to plain of length length - 16 using a * secret key crypto_box_KEYBYTES big and a 24 byte nonce. * * return -1 if there was a problem (decryption failed). * return length of plain data if everything was fine. */ int decrypt_data_symmetric(const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *encrypted, uint32_t length, uint8_t *plain); /* Increment the given nonce by 1. */ void increment_nonce(uint8_t *nonce); /* increment the given nonce by num */ void increment_nonce_number(uint8_t *nonce, uint32_t host_order_num); /* Fill the given nonce with random bytes. */ void random_nonce(uint8_t *nonce); /* Fill a key crypto_box_KEYBYTES big with random bytes */ void new_symmetric_key(uint8_t *key); /*Gives a nonce guaranteed to be different from previous ones.*/ void new_nonce(uint8_t *nonce); #define MAX_CRYPTO_REQUEST_SIZE 1024 #define CRYPTO_PACKET_FRIEND_REQ 32 /* Friend request crypto packet ID. */ #define CRYPTO_PACKET_HARDENING 48 /* Hardening crypto packet ID. */ #define CRYPTO_PACKET_DHTPK 156 #define CRYPTO_PACKET_NAT_PING 254 /* NAT ping crypto packet ID. */ /* Create a request to peer. * send_public_key and send_secret_key are the pub/secret keys of the sender. * recv_public_key is public key of receiver. * packet must be an array of MAX_CRYPTO_REQUEST_SIZE big. * Data represents the data we send with the request with length being the length of the data. * request_id is the id of the request (32 = friend request, 254 = ping request). * * return -1 on failure. * return the length of the created packet on success. */ int create_request(const uint8_t *send_public_key, const uint8_t *send_secret_key, uint8_t *packet, const uint8_t *recv_public_key, const uint8_t *data, uint32_t length, uint8_t request_id); /* puts the senders public key in the request in public_key, the data from the request in data if a friend or ping request was sent to us and returns the length of the data. packet is the request packet and length is its length return -1 if not valid request. */ int handle_request(const uint8_t *self_public_key, const uint8_t *self_secret_key, uint8_t *public_key, uint8_t *data, uint8_t *request_id, const uint8_t *packet, uint16_t length); #endif ================================================ FILE: toxcore/friend_connection.c ================================================ /* friend_connection.c * * Connection to friends. * * 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 "friend_connection.h" #include "util.h" /* return 1 if the friendcon_id is not valid. * return 0 if the friendcon_id is valid. */ static uint8_t friendconn_id_not_valid(const Friend_Connections *fr_c, int friendcon_id) { if ((unsigned int)friendcon_id >= fr_c->num_cons) return 1; if (fr_c->conns == NULL) return 1; if (fr_c->conns[friendcon_id].status == FRIENDCONN_STATUS_NONE) return 1; return 0; } /* Set the size of the friend connections list to num. * * return -1 if realloc fails. * return 0 if it succeeds. */ static int realloc_friendconns(Friend_Connections *fr_c, uint32_t num) { if (num == 0) { free(fr_c->conns); fr_c->conns = NULL; return 0; } Friend_Conn *newgroup_cons = realloc(fr_c->conns, num * sizeof(Friend_Conn)); if (newgroup_cons == NULL) return -1; fr_c->conns = newgroup_cons; return 0; } /* Create a new empty friend connection. * * return -1 on failure. * return friendcon_id on success. */ static int create_friend_conn(Friend_Connections *fr_c) { uint32_t i; for (i = 0; i < fr_c->num_cons; ++i) { if (fr_c->conns[i].status == FRIENDCONN_STATUS_NONE) return i; } int id = -1; if (realloc_friendconns(fr_c, fr_c->num_cons + 1) == 0) { id = fr_c->num_cons; ++fr_c->num_cons; memset(&(fr_c->conns[id]), 0, sizeof(Friend_Conn)); } return id; } /* Wipe a friend connection. * * return -1 on failure. * return 0 on success. */ static int wipe_friend_conn(Friend_Connections *fr_c, int friendcon_id) { if (friendconn_id_not_valid(fr_c, friendcon_id)) return -1; uint32_t i; memset(&(fr_c->conns[friendcon_id]), 0 , sizeof(Friend_Conn)); for (i = fr_c->num_cons; i != 0; --i) { if (fr_c->conns[i - 1].status != FRIENDCONN_STATUS_NONE) break; } if (fr_c->num_cons != i) { fr_c->num_cons = i; realloc_friendconns(fr_c, fr_c->num_cons); } return 0; } static Friend_Conn *get_conn(const Friend_Connections *fr_c, int friendcon_id) { if (friendconn_id_not_valid(fr_c, friendcon_id)) return 0; return &fr_c->conns[friendcon_id]; } /* return friendcon_id corresponding to the real public key on success. * return -1 on failure. */ int getfriend_conn_id_pk(Friend_Connections *fr_c, const uint8_t *real_pk) { uint32_t i; for (i = 0; i < fr_c->num_cons; ++i) { Friend_Conn *friend_con = get_conn(fr_c, i); if (friend_con) { if (public_key_cmp(friend_con->real_public_key, real_pk) == 0) return i; } } return -1; } /* Add a TCP relay associated to the friend. * * return -1 on failure. * return 0 on success. */ int friend_add_tcp_relay(Friend_Connections *fr_c, int friendcon_id, IP_Port ip_port, const uint8_t *public_key) { Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) return -1; /* Local ip and same pk means that they are hosting a TCP relay. */ if (Local_ip(ip_port.ip) && public_key_cmp(friend_con->dht_temp_pk, public_key) == 0) { if (friend_con->dht_ip_port.ip.family != 0) { ip_port.ip = friend_con->dht_ip_port.ip; } else { friend_con->hosting_tcp_relay = 0; } } unsigned int i; uint16_t index = friend_con->tcp_relay_counter % FRIEND_MAX_STORED_TCP_RELAYS; for (i = 0; i < FRIEND_MAX_STORED_TCP_RELAYS; ++i) { if (friend_con->tcp_relays[i].ip_port.ip.family != 0 && public_key_cmp(friend_con->tcp_relays[i].public_key, public_key) == 0) { memset(&friend_con->tcp_relays[i], 0, sizeof(Node_format)); } } friend_con->tcp_relays[index].ip_port = ip_port; memcpy(friend_con->tcp_relays[index].public_key, public_key, crypto_box_PUBLICKEYBYTES); ++friend_con->tcp_relay_counter; return add_tcp_relay_peer(fr_c->net_crypto, friend_con->crypt_connection_id, ip_port, public_key); } /* Connect to number saved relays for friend. */ static void connect_to_saved_tcp_relays(Friend_Connections *fr_c, int friendcon_id, unsigned int number) { Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) return; unsigned int i; for (i = 0; (i < FRIEND_MAX_STORED_TCP_RELAYS) && (number != 0); ++i) { uint16_t index = (friend_con->tcp_relay_counter - (i + 1)) % FRIEND_MAX_STORED_TCP_RELAYS; if (friend_con->tcp_relays[index].ip_port.ip.family) { if (add_tcp_relay_peer(fr_c->net_crypto, friend_con->crypt_connection_id, friend_con->tcp_relays[index].ip_port, friend_con->tcp_relays[index].public_key) == 0) { --number; } } } } static unsigned int send_relays(Friend_Connections *fr_c, int friendcon_id) { Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) return 0; Node_format nodes[MAX_SHARED_RELAYS]; uint8_t data[1024]; int n, length; n = copy_connected_tcp_relays(fr_c->net_crypto, nodes, MAX_SHARED_RELAYS); int i; for (i = 0; i < n; ++i) { /* Associated the relays being sent with this connection. On receiving the peer will do the same which will establish the connection. */ friend_add_tcp_relay(fr_c, friendcon_id, nodes[i].ip_port, nodes[i].public_key); } length = pack_nodes(data + 1, sizeof(data) - 1, nodes, n); if (length <= 0) return 0; data[0] = PACKET_ID_SHARE_RELAYS; ++length; if (write_cryptpacket(fr_c->net_crypto, friend_con->crypt_connection_id, data, length, 0) != -1) { friend_con->share_relays_lastsent = unix_time(); return 1; } return 0; } /* callback for recv TCP relay nodes. */ static int tcp_relay_node_callback(void *object, uint32_t number, IP_Port ip_port, const uint8_t *public_key) { Friend_Connections *fr_c = object; Friend_Conn *friend_con = get_conn(fr_c, number); if (!friend_con) return -1; if (friend_con->crypt_connection_id != -1) { return friend_add_tcp_relay(fr_c, number, ip_port, public_key); } else { return add_tcp_relay(fr_c->net_crypto, ip_port, public_key); } } static int friend_new_connection(Friend_Connections *fr_c, int friendcon_id); /* Callback for DHT ip_port changes. */ static void dht_ip_callback(void *object, int32_t number, IP_Port ip_port) { Friend_Connections *fr_c = object; Friend_Conn *friend_con = get_conn(fr_c, number); if (!friend_con) return; if (friend_con->crypt_connection_id == -1) { friend_new_connection(fr_c, number); } set_direct_ip_port(fr_c->net_crypto, friend_con->crypt_connection_id, ip_port, 1); friend_con->dht_ip_port = ip_port; friend_con->dht_ip_port_lastrecv = unix_time(); if (friend_con->hosting_tcp_relay) { friend_add_tcp_relay(fr_c, number, ip_port, friend_con->dht_temp_pk); friend_con->hosting_tcp_relay = 0; } } static void change_dht_pk(Friend_Connections *fr_c, int friendcon_id, const uint8_t *dht_public_key) { Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) return; friend_con->dht_pk_lastrecv = unix_time(); if (friend_con->dht_lock) { if (DHT_delfriend(fr_c->dht, friend_con->dht_temp_pk, friend_con->dht_lock) != 0) { printf("a. Could not delete dht peer. Please report this.\n"); return; } friend_con->dht_lock = 0; } DHT_addfriend(fr_c->dht, dht_public_key, dht_ip_callback, fr_c, friendcon_id, &friend_con->dht_lock); memcpy(friend_con->dht_temp_pk, dht_public_key, crypto_box_PUBLICKEYBYTES); } static int handle_status(void *object, int number, uint8_t status) { Friend_Connections *fr_c = object; Friend_Conn *friend_con = get_conn(fr_c, number); if (!friend_con) return -1; _Bool call_cb = 0; if (status) { /* Went online. */ call_cb = 1; friend_con->status = FRIENDCONN_STATUS_CONNECTED; friend_con->ping_lastrecv = unix_time(); friend_con->share_relays_lastsent = 0; onion_set_friend_online(fr_c->onion_c, friend_con->onion_friendnum, status); } else { /* Went offline. */ if (friend_con->status != FRIENDCONN_STATUS_CONNECTING) { call_cb = 1; friend_con->dht_pk_lastrecv = unix_time(); onion_set_friend_online(fr_c->onion_c, friend_con->onion_friendnum, status); } friend_con->status = FRIENDCONN_STATUS_CONNECTING; friend_con->crypt_connection_id = -1; friend_con->hosting_tcp_relay = 0; } if (call_cb) { unsigned int i; for (i = 0; i < MAX_FRIEND_CONNECTION_CALLBACKS; ++i) { if (friend_con->callbacks[i].status_callback) friend_con->callbacks[i].status_callback(friend_con->callbacks[i].status_callback_object, friend_con->callbacks[i].status_callback_id, status); } } return 0; } /* Callback for dht public key changes. */ static void dht_pk_callback(void *object, int32_t number, const uint8_t *dht_public_key) { Friend_Connections *fr_c = object; Friend_Conn *friend_con = get_conn(fr_c, number); if (!friend_con) return; if (public_key_cmp(friend_con->dht_temp_pk, dht_public_key) == 0) return; change_dht_pk(fr_c, number, dht_public_key); /* if pk changed, create a new connection.*/ if (friend_con->crypt_connection_id != -1) { crypto_kill(fr_c->net_crypto, friend_con->crypt_connection_id); friend_con->crypt_connection_id = -1; handle_status(object, number, 0); /* Going offline. */ } friend_new_connection(fr_c, number); onion_set_friend_DHT_pubkey(fr_c->onion_c, friend_con->onion_friendnum, dht_public_key); } static int handle_packet(void *object, int number, uint8_t *data, uint16_t length) { if (length == 0) return -1; Friend_Connections *fr_c = object; Friend_Conn *friend_con = get_conn(fr_c, number); if (!friend_con) return -1; if (data[0] == PACKET_ID_FRIEND_REQUESTS) { if (fr_c->fr_request_callback) fr_c->fr_request_callback(fr_c->fr_request_object, friend_con->real_public_key, data, length); return 0; } else if (data[0] == PACKET_ID_ALIVE) { friend_con->ping_lastrecv = unix_time(); return 0; } else if (data[0] == PACKET_ID_SHARE_RELAYS) { Node_format nodes[MAX_SHARED_RELAYS]; int n; if ((n = unpack_nodes(nodes, MAX_SHARED_RELAYS, NULL, data + 1, length - 1, 1)) == -1) return -1; int j; for (j = 0; j < n; j++) { friend_add_tcp_relay(fr_c, number, nodes[j].ip_port, nodes[j].public_key); } return 0; } unsigned int i; for (i = 0; i < MAX_FRIEND_CONNECTION_CALLBACKS; ++i) { if (friend_con->callbacks[i].data_callback) friend_con->callbacks[i].data_callback(friend_con->callbacks[i].data_callback_object, friend_con->callbacks[i].data_callback_id, data, length); friend_con = get_conn(fr_c, number); if (!friend_con) return -1; } return 0; } static int handle_lossy_packet(void *object, int number, const uint8_t *data, uint16_t length) { if (length == 0) return -1; Friend_Connections *fr_c = object; Friend_Conn *friend_con = get_conn(fr_c, number); if (!friend_con) return -1; unsigned int i; for (i = 0; i < MAX_FRIEND_CONNECTION_CALLBACKS; ++i) { if (friend_con->callbacks[i].lossy_data_callback) friend_con->callbacks[i].lossy_data_callback(friend_con->callbacks[i].lossy_data_callback_object, friend_con->callbacks[i].lossy_data_callback_id, data, length); friend_con = get_conn(fr_c, number); if (!friend_con) return -1; } return 0; } static int handle_new_connections(void *object, New_Connection *n_c) { Friend_Connections *fr_c = object; int friendcon_id = getfriend_conn_id_pk(fr_c, n_c->public_key); Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); if (friend_con) { if (friend_con->crypt_connection_id != -1) return -1; int id = accept_crypto_connection(fr_c->net_crypto, n_c); if (id == -1) { return -1; } connection_status_handler(fr_c->net_crypto, id, &handle_status, fr_c, friendcon_id); connection_data_handler(fr_c->net_crypto, id, &handle_packet, fr_c, friendcon_id); connection_lossy_data_handler(fr_c->net_crypto, id, &handle_lossy_packet, fr_c, friendcon_id); friend_con->crypt_connection_id = id; if (n_c->source.ip.family != AF_INET && n_c->source.ip.family != AF_INET6) { set_direct_ip_port(fr_c->net_crypto, friend_con->crypt_connection_id, friend_con->dht_ip_port, 0); } else { friend_con->dht_ip_port = n_c->source; friend_con->dht_ip_port_lastrecv = unix_time(); } if (public_key_cmp(friend_con->dht_temp_pk, n_c->dht_public_key) != 0) { change_dht_pk(fr_c, friendcon_id, n_c->dht_public_key); } nc_dht_pk_callback(fr_c->net_crypto, id, &dht_pk_callback, fr_c, friendcon_id); return 0; } return -1; } static int friend_new_connection(Friend_Connections *fr_c, int friendcon_id) { Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) return -1; if (friend_con->crypt_connection_id != -1) { return -1; } /* If dht_temp_pk does not contains a pk. */ if (!friend_con->dht_lock) { return -1; } int id = new_crypto_connection(fr_c->net_crypto, friend_con->real_public_key, friend_con->dht_temp_pk); if (id == -1) return -1; friend_con->crypt_connection_id = id; connection_status_handler(fr_c->net_crypto, id, &handle_status, fr_c, friendcon_id); connection_data_handler(fr_c->net_crypto, id, &handle_packet, fr_c, friendcon_id); connection_lossy_data_handler(fr_c->net_crypto, id, &handle_lossy_packet, fr_c, friendcon_id); nc_dht_pk_callback(fr_c->net_crypto, id, &dht_pk_callback, fr_c, friendcon_id); return 0; } static int send_ping(const Friend_Connections *fr_c, int friendcon_id) { Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) return -1; uint8_t ping = PACKET_ID_ALIVE; int64_t ret = write_cryptpacket(fr_c->net_crypto, friend_con->crypt_connection_id, &ping, sizeof(ping), 0); if (ret != -1) { friend_con->ping_lastsent = unix_time(); return 0; } return -1; } /* Increases lock_count for the connection with friendcon_id by 1. * * return 0 on success. * return -1 on failure. */ int friend_connection_lock(Friend_Connections *fr_c, int friendcon_id) { Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) return -1; ++friend_con->lock_count; return 0; } /* return FRIENDCONN_STATUS_CONNECTED if the friend is connected. * return FRIENDCONN_STATUS_CONNECTING if the friend isn't connected. * return FRIENDCONN_STATUS_NONE on failure. */ unsigned int friend_con_connected(Friend_Connections *fr_c, int friendcon_id) { Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) return 0; return friend_con->status; } /* Copy public keys associated to friendcon_id. * * return 0 on success. * return -1 on failure. */ int get_friendcon_public_keys(uint8_t *real_pk, uint8_t *dht_temp_pk, Friend_Connections *fr_c, int friendcon_id) { Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) return -1; if (real_pk) memcpy(real_pk, friend_con->real_public_key, crypto_box_PUBLICKEYBYTES); if (dht_temp_pk) memcpy(dht_temp_pk, friend_con->dht_temp_pk, crypto_box_PUBLICKEYBYTES); return 0; } /* Set temp dht key for connection. */ void set_dht_temp_pk(Friend_Connections *fr_c, int friendcon_id, const uint8_t *dht_temp_pk) { dht_pk_callback(fr_c, friendcon_id, dht_temp_pk); } /* Set the callbacks for the friend connection. * index is the index (0 to (MAX_FRIEND_CONNECTION_CALLBACKS - 1)) we want the callback to set in the array. * * return 0 on success. * return -1 on failure */ int friend_connection_callbacks(Friend_Connections *fr_c, int friendcon_id, unsigned int index, int (*status_callback)(void *object, int id, uint8_t status), int (*data_callback)(void *object, int id, uint8_t *data, uint16_t length), int (*lossy_data_callback)(void *object, int id, const uint8_t *data, uint16_t length), void *object, int number) { Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) return -1; if (index >= MAX_FRIEND_CONNECTION_CALLBACKS) return -1; friend_con->callbacks[index].status_callback = status_callback; friend_con->callbacks[index].data_callback = data_callback; friend_con->callbacks[index].lossy_data_callback = lossy_data_callback; friend_con->callbacks[index].status_callback_object = friend_con->callbacks[index].data_callback_object = friend_con->callbacks[index].lossy_data_callback_object = object; friend_con->callbacks[index].status_callback_id = friend_con->callbacks[index].data_callback_id = friend_con->callbacks[index].lossy_data_callback_id = number; return 0; } /* return the crypt_connection_id for the connection. * * return crypt_connection_id on success. * return -1 on failure. */ int friend_connection_crypt_connection_id(Friend_Connections *fr_c, int friendcon_id) { Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) return -1; return friend_con->crypt_connection_id; } /* Create a new friend connection. * If one to that real public key already exists, increase lock count and return it. * * return -1 on failure. * return connection id on success. */ int new_friend_connection(Friend_Connections *fr_c, const uint8_t *real_public_key) { int friendcon_id = getfriend_conn_id_pk(fr_c, real_public_key); if (friendcon_id != -1) { ++fr_c->conns[friendcon_id].lock_count; return friendcon_id; } friendcon_id = create_friend_conn(fr_c); if (friendcon_id == -1) return -1; int32_t onion_friendnum = onion_addfriend(fr_c->onion_c, real_public_key); if (onion_friendnum == -1) return -1; Friend_Conn *friend_con = &fr_c->conns[friendcon_id]; friend_con->crypt_connection_id = -1; friend_con->status = FRIENDCONN_STATUS_CONNECTING; memcpy(friend_con->real_public_key, real_public_key, crypto_box_PUBLICKEYBYTES); friend_con->onion_friendnum = onion_friendnum; recv_tcp_relay_handler(fr_c->onion_c, onion_friendnum, &tcp_relay_node_callback, fr_c, friendcon_id); onion_dht_pk_callback(fr_c->onion_c, onion_friendnum, &dht_pk_callback, fr_c, friendcon_id); return friendcon_id; } /* Kill a friend connection. * * return -1 on failure. * return 0 on success. */ int kill_friend_connection(Friend_Connections *fr_c, int friendcon_id) { Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) return -1; if (friend_con->lock_count) { --friend_con->lock_count; return 0; } onion_delfriend(fr_c->onion_c, friend_con->onion_friendnum); crypto_kill(fr_c->net_crypto, friend_con->crypt_connection_id); if (friend_con->dht_lock) { DHT_delfriend(fr_c->dht, friend_con->dht_temp_pk, friend_con->dht_lock); } return wipe_friend_conn(fr_c, friendcon_id); } /* Set friend request callback. * * This function will be called every time a friend request packet is received. */ void set_friend_request_callback(Friend_Connections *fr_c, int (*fr_request_callback)(void *, const uint8_t *, const uint8_t *, uint16_t), void *object) { fr_c->fr_request_callback = fr_request_callback; fr_c->fr_request_object = object; oniondata_registerhandler(fr_c->onion_c, CRYPTO_PACKET_FRIEND_REQ, fr_request_callback, object); } /* Send a Friend request packet. * * return -1 if failure. * return 0 if it sent the friend request directly to the friend. * return the number of peers it was routed through if it did not send it directly. */ int send_friend_request_packet(Friend_Connections *fr_c, int friendcon_id, uint32_t nospam_num, const uint8_t *data, uint16_t length) { if (1 + sizeof(nospam_num) + length > ONION_CLIENT_MAX_DATA_SIZE || length == 0) return -1; Friend_Conn *friend_con = get_conn(fr_c, friendcon_id); if (!friend_con) return -1; uint8_t packet[1 + sizeof(nospam_num) + length]; memcpy(packet + 1, &nospam_num, sizeof(nospam_num)); memcpy(packet + 1 + sizeof(nospam_num), data, length); if (friend_con->status == FRIENDCONN_STATUS_CONNECTED) { packet[0] = PACKET_ID_FRIEND_REQUESTS; return write_cryptpacket(fr_c->net_crypto, friend_con->crypt_connection_id, packet, sizeof(packet), 0) != -1; } else { packet[0] = CRYPTO_PACKET_FRIEND_REQ; int num = send_onion_data(fr_c->onion_c, friend_con->onion_friendnum, packet, sizeof(packet)); if (num <= 0) return -1; return num; } } /* Create new friend_connections instance. */ Friend_Connections *new_friend_connections(Onion_Client *onion_c) { if (!onion_c) return NULL; Friend_Connections *temp = calloc(1, sizeof(Friend_Connections)); if (temp == NULL) return NULL; temp->dht = onion_c->dht; temp->net_crypto = onion_c->c; temp->onion_c = onion_c; new_connection_handler(temp->net_crypto, &handle_new_connections, temp); LANdiscovery_init(temp->dht); return temp; } /* Send a LAN discovery packet every LAN_DISCOVERY_INTERVAL seconds. */ static void LANdiscovery(Friend_Connections *fr_c) { if (fr_c->last_LANdiscovery + LAN_DISCOVERY_INTERVAL < unix_time()) { send_LANdiscovery(htons(TOX_PORT_DEFAULT), fr_c->dht); fr_c->last_LANdiscovery = unix_time(); } } /* main friend_connections loop. */ void do_friend_connections(Friend_Connections *fr_c) { uint32_t i; uint64_t temp_time = unix_time(); for (i = 0; i < fr_c->num_cons; ++i) { Friend_Conn *friend_con = get_conn(fr_c, i); if (friend_con) { if (friend_con->status == FRIENDCONN_STATUS_CONNECTING) { if (friend_con->dht_pk_lastrecv + FRIEND_DHT_TIMEOUT < temp_time) { if (friend_con->dht_lock) { DHT_delfriend(fr_c->dht, friend_con->dht_temp_pk, friend_con->dht_lock); friend_con->dht_lock = 0; } } if (friend_con->dht_ip_port_lastrecv + FRIEND_DHT_TIMEOUT < temp_time) { friend_con->dht_ip_port.ip.family = 0; } if (friend_con->dht_lock) { if (friend_new_connection(fr_c, i) == 0) { set_direct_ip_port(fr_c->net_crypto, friend_con->crypt_connection_id, friend_con->dht_ip_port, 0); connect_to_saved_tcp_relays(fr_c, i, (MAX_FRIEND_TCP_CONNECTIONS / 2)); /* Only fill it half up. */ } } } else if (friend_con->status == FRIENDCONN_STATUS_CONNECTED) { if (friend_con->ping_lastsent + FRIEND_PING_INTERVAL < temp_time) { send_ping(fr_c, i); } if (friend_con->share_relays_lastsent + SHARE_RELAYS_INTERVAL < temp_time) { send_relays(fr_c, i); } if (friend_con->ping_lastrecv + FRIEND_CONNECTION_TIMEOUT < temp_time) { /* If we stopped receiving ping packets, kill it. */ crypto_kill(fr_c->net_crypto, friend_con->crypt_connection_id); friend_con->crypt_connection_id = -1; handle_status(fr_c, i, 0); /* Going offline. */ } } } } LANdiscovery(fr_c); } /* Free everything related with friend_connections. */ void kill_friend_connections(Friend_Connections *fr_c) { if (!fr_c) return; uint32_t i; for (i = 0; i < fr_c->num_cons; ++i) { kill_friend_connection(fr_c, i); } LANdiscovery_kill(fr_c->dht); free(fr_c); } ================================================ FILE: toxcore/friend_connection.h ================================================ /* friend_connection.h * * Connection to friends. * * 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 FRIEND_CONNECTION_H #define FRIEND_CONNECTION_H #include "net_crypto.h" #include "DHT.h" #include "LAN_discovery.h" #include "onion_client.h" #define MAX_FRIEND_CONNECTION_CALLBACKS 2 #define MESSENGER_CALLBACK_INDEX 0 #define GROUPCHAT_CALLBACK_INDEX 1 #define PACKET_ID_ALIVE 16 #define PACKET_ID_SHARE_RELAYS 17 #define PACKET_ID_FRIEND_REQUESTS 18 /* Interval between the sending of ping packets. */ #define FRIEND_PING_INTERVAL 8 /* If no packets are received from friend in this time interval, kill the connection. */ #define FRIEND_CONNECTION_TIMEOUT (FRIEND_PING_INTERVAL * 4) /* Time before friend is removed from the DHT after last hearing about him. */ #define FRIEND_DHT_TIMEOUT BAD_NODE_TIMEOUT #define FRIEND_MAX_STORED_TCP_RELAYS (MAX_FRIEND_TCP_CONNECTIONS * 4) /* Max number of tcp relays sent to friends */ #define MAX_SHARED_RELAYS (RECOMMENDED_FRIEND_TCP_CONNECTIONS) /* Interval between the sending of tcp relay information */ #define SHARE_RELAYS_INTERVAL (5 * 60) enum { FRIENDCONN_STATUS_NONE, FRIENDCONN_STATUS_CONNECTING, FRIENDCONN_STATUS_CONNECTED }; typedef struct { uint8_t status; uint8_t real_public_key[crypto_box_PUBLICKEYBYTES]; uint8_t dht_temp_pk[crypto_box_PUBLICKEYBYTES]; uint16_t dht_lock; IP_Port dht_ip_port; uint64_t dht_pk_lastrecv, dht_ip_port_lastrecv; int onion_friendnum; int crypt_connection_id; uint64_t ping_lastrecv, ping_lastsent; uint64_t share_relays_lastsent; struct { int (*status_callback)(void *object, int id, uint8_t status); void *status_callback_object; int status_callback_id; int (*data_callback)(void *object, int id, uint8_t *data, uint16_t length); void *data_callback_object; int data_callback_id; int (*lossy_data_callback)(void *object, int id, const uint8_t *data, uint16_t length); void *lossy_data_callback_object; int lossy_data_callback_id; } callbacks[MAX_FRIEND_CONNECTION_CALLBACKS]; uint16_t lock_count; Node_format tcp_relays[FRIEND_MAX_STORED_TCP_RELAYS]; uint16_t tcp_relay_counter; _Bool hosting_tcp_relay; } Friend_Conn; typedef struct { Net_Crypto *net_crypto; DHT *dht; Onion_Client *onion_c; Friend_Conn *conns; uint32_t num_cons; int (*fr_request_callback)(void *object, const uint8_t *source_pubkey, const uint8_t *data, uint16_t len); void *fr_request_object; uint64_t last_LANdiscovery; } Friend_Connections; /* return friendcon_id corresponding to the real public key on success. * return -1 on failure. */ int getfriend_conn_id_pk(Friend_Connections *fr_c, const uint8_t *real_pk); /* Increases lock_count for the connection with friendcon_id by 1. * * return 0 on success. * return -1 on failure. */ int friend_connection_lock(Friend_Connections *fr_c, int friendcon_id); /* return FRIENDCONN_STATUS_CONNECTED if the friend is connected. * return FRIENDCONN_STATUS_CONNECTING if the friend isn't connected. * return FRIENDCONN_STATUS_NONE on failure. */ unsigned int friend_con_connected(Friend_Connections *fr_c, int friendcon_id); /* Copy public keys associated to friendcon_id. * * return 0 on success. * return -1 on failure. */ int get_friendcon_public_keys(uint8_t *real_pk, uint8_t *dht_temp_pk, Friend_Connections *fr_c, int friendcon_id); /* Set temp dht key for connection. */ void set_dht_temp_pk(Friend_Connections *fr_c, int friendcon_id, const uint8_t *dht_temp_pk); /* Add a TCP relay associated to the friend. * * return -1 on failure. * return 0 on success. */ int friend_add_tcp_relay(Friend_Connections *fr_c, int friendcon_id, IP_Port ip_port, const uint8_t *public_key); /* Set the callbacks for the friend connection. * index is the index (0 to (MAX_FRIEND_CONNECTION_CALLBACKS - 1)) we want the callback to set in the array. * * return 0 on success. * return -1 on failure */ int friend_connection_callbacks(Friend_Connections *fr_c, int friendcon_id, unsigned int index, int (*status_callback)(void *object, int id, uint8_t status), int (*data_callback)(void *object, int id, uint8_t *data, uint16_t length), int (*lossy_data_callback)(void *object, int id, const uint8_t *data, uint16_t length), void *object, int number); /* return the crypt_connection_id for the connection. * * return crypt_connection_id on success. * return -1 on failure. */ int friend_connection_crypt_connection_id(Friend_Connections *fr_c, int friendcon_id); /* Create a new friend connection. * If one to that real public key already exists, increase lock count and return it. * * return -1 on failure. * return connection id on success. */ int new_friend_connection(Friend_Connections *fr_c, const uint8_t *real_public_key); /* Kill a friend connection. * * return -1 on failure. * return 0 on success. */ int kill_friend_connection(Friend_Connections *fr_c, int friendcon_id); /* Send a Friend request packet. * * return -1 if failure. * return 0 if it sent the friend request directly to the friend. * return the number of peers it was routed through if it did not send it directly. */ int send_friend_request_packet(Friend_Connections *fr_c, int friendcon_id, uint32_t nospam_num, const uint8_t *data, uint16_t length); /* Set friend request callback. * * This function will be called every time a friend request is received. */ void set_friend_request_callback(Friend_Connections *fr_c, int (*fr_request_callback)(void *, const uint8_t *, const uint8_t *, uint16_t), void *object); /* Create new friend_connections instance. */ Friend_Connections *new_friend_connections(Onion_Client *onion_c); /* main friend_connections loop. */ void do_friend_connections(Friend_Connections *fr_c); /* Free everything related with friend_connections. */ void kill_friend_connections(Friend_Connections *fr_c); #endif ================================================ FILE: toxcore/friend_requests.c ================================================ /* friend_requests.c * * Handle friend requests. * * 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 "friend_requests.h" #include "util.h" /* Set and get the nospam variable used to prevent one type of friend request spam. */ void set_nospam(Friend_Requests *fr, uint32_t num) { fr->nospam = num; } uint32_t get_nospam(const Friend_Requests *fr) { return fr->nospam; } /* Set the function that will be executed when a friend request is received. */ void callback_friendrequest(Friend_Requests *fr, void (*function)(void *, const uint8_t *, const uint8_t *, size_t, void *), void *object, void *userdata) { fr->handle_friendrequest = function; fr->handle_friendrequest_isset = 1; fr->handle_friendrequest_object = object; fr->handle_friendrequest_userdata = userdata; } /* Set the function used to check if a friend request should be displayed to the user or not. */ void set_filter_function(Friend_Requests *fr, int (*function)(const uint8_t *, void *), void *userdata) { fr->filter_function = function; fr->filter_function_userdata = userdata; } /* Add to list of received friend requests. */ static void addto_receivedlist(Friend_Requests *fr, const uint8_t *real_pk) { if (fr->received_requests_index >= MAX_RECEIVED_STORED) fr->received_requests_index = 0; id_copy(fr->received_requests[fr->received_requests_index], real_pk); ++fr->received_requests_index; } /* Check if a friend request was already received. * * return 0 if it did not. * return 1 if it did. */ static int request_received(Friend_Requests *fr, const uint8_t *real_pk) { uint32_t i; for (i = 0; i < MAX_RECEIVED_STORED; ++i) if (id_equal(fr->received_requests[i], real_pk)) return 1; return 0; } /* Remove real pk from received_requests list. * * return 0 if it removed it successfully. * return -1 if it didn't find it. */ int remove_request_received(Friend_Requests *fr, const uint8_t *real_pk) { uint32_t i; for (i = 0; i < MAX_RECEIVED_STORED; ++i) { if (id_equal(fr->received_requests[i], real_pk)) { sodium_memzero(fr->received_requests[i], crypto_box_PUBLICKEYBYTES); return 0; } } return -1; } static int friendreq_handlepacket(void *object, const uint8_t *source_pubkey, const uint8_t *packet, uint16_t length) { Friend_Requests *fr = object; if (length <= 1 + sizeof(fr->nospam) || length > ONION_CLIENT_MAX_DATA_SIZE) return 1; ++packet; --length; if (fr->handle_friendrequest_isset == 0) return 1; if (request_received(fr, source_pubkey)) return 1; if (memcmp(packet, &fr->nospam, sizeof(fr->nospam)) != 0) return 1; if (fr->filter_function) if ((*fr->filter_function)(source_pubkey, fr->filter_function_userdata) != 0) return 1; addto_receivedlist(fr, source_pubkey); uint32_t message_len = length - sizeof(fr->nospam); uint8_t message[message_len + 1]; memcpy(message, packet + sizeof(fr->nospam), message_len); message[sizeof(message) - 1] = 0; /* Be sure the message is null terminated. */ (*fr->handle_friendrequest)(fr->handle_friendrequest_object, source_pubkey, message, message_len, fr->handle_friendrequest_userdata); return 0; } void friendreq_init(Friend_Requests *fr, Friend_Connections *fr_c) { set_friend_request_callback(fr_c, &friendreq_handlepacket, fr); } ================================================ FILE: toxcore/friend_requests.h ================================================ /* friend_requests.h * * Handle friend requests. * * 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 FRIEND_REQUESTS_H #define FRIEND_REQUESTS_H #include "friend_connection.h" #define MAX_FRIEND_REQUEST_DATA_SIZE (ONION_CLIENT_MAX_DATA_SIZE - (1 + sizeof(uint32_t))) typedef struct { uint32_t nospam; void (*handle_friendrequest)(void *, const uint8_t *, const uint8_t *, size_t, void *); uint8_t handle_friendrequest_isset; void *handle_friendrequest_object; void *handle_friendrequest_userdata; int (*filter_function)(const uint8_t *, void *); void *filter_function_userdata; /* NOTE: The following is just a temporary fix for the multiple friend requests received at the same time problem. * TODO: Make this better (This will most likely tie in with the way we will handle spam.) */ #define MAX_RECEIVED_STORED 32 uint8_t received_requests[MAX_RECEIVED_STORED][crypto_box_PUBLICKEYBYTES]; uint16_t received_requests_index; } Friend_Requests; /* Set and get the nospam variable used to prevent one type of friend request spam. */ void set_nospam(Friend_Requests *fr, uint32_t num); uint32_t get_nospam(const Friend_Requests *fr); /* Remove real_pk from received_requests list. * * return 0 if it removed it successfully. * return -1 if it didn't find it. */ int remove_request_received(Friend_Requests *fr, const uint8_t *real_pk); /* Set the function that will be executed when a friend request for us is received. * Function format is function(uint8_t * public_key, uint8_t * data, size_t length, void * userdata) */ void callback_friendrequest(Friend_Requests *fr, void (*function)(void *, const uint8_t *, const uint8_t *, size_t, void *), void *object, void *userdata); /* Set the function used to check if a friend request should be displayed to the user or not. * Function format is int function(uint8_t * public_key, void * userdata) * It must return 0 if the request is ok (anything else if it is bad.) */ void set_filter_function(Friend_Requests *fr, int (*function)(const uint8_t *, void *), void *userdata); /* Sets up friendreq packet handlers. */ void friendreq_init(Friend_Requests *fr, Friend_Connections *fr_c); #endif ================================================ FILE: toxcore/group.c ================================================ /* group.c * * Slightly better groupchats implementation. * * 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 "group.h" #include "util.h" /* return 1 if the groupnumber is not valid. * return 0 if the groupnumber is valid. */ static uint8_t groupnumber_not_valid(const Group_Chats *g_c, int groupnumber) { if ((unsigned int)groupnumber >= g_c->num_chats) return 1; if (g_c->chats == NULL) return 1; if (g_c->chats[groupnumber].status == GROUPCHAT_STATUS_NONE) return 1; return 0; } /* Set the size of the groupchat list to num. * * return -1 if realloc fails. * return 0 if it succeeds. */ static int realloc_groupchats(Group_Chats *g_c, uint32_t num) { if (num == 0) { free(g_c->chats); g_c->chats = NULL; return 0; } Group_c *newgroup_chats = realloc(g_c->chats, num * sizeof(Group_c)); if (newgroup_chats == NULL) return -1; g_c->chats = newgroup_chats; return 0; } /* Create a new empty groupchat connection. * * return -1 on failure. * return groupnumber on success. */ static int create_group_chat(Group_Chats *g_c) { uint32_t i; for (i = 0; i < g_c->num_chats; ++i) { if (g_c->chats[i].status == GROUPCHAT_STATUS_NONE) return i; } int id = -1; if (realloc_groupchats(g_c, g_c->num_chats + 1) == 0) { id = g_c->num_chats; ++g_c->num_chats; memset(&(g_c->chats[id]), 0, sizeof(Group_c)); } return id; } /* Wipe a groupchat. * * return -1 on failure. * return 0 on success. */ static int wipe_group_chat(Group_Chats *g_c, int groupnumber) { if (groupnumber_not_valid(g_c, groupnumber)) return -1; uint32_t i; sodium_memzero(&(g_c->chats[groupnumber]), sizeof(Group_c)); for (i = g_c->num_chats; i != 0; --i) { if (g_c->chats[i - 1].status != GROUPCHAT_STATUS_NONE) break; } if (g_c->num_chats != i) { g_c->num_chats = i; realloc_groupchats(g_c, g_c->num_chats); } return 0; } static Group_c *get_group_c(const Group_Chats *g_c, int groupnumber) { if (groupnumber_not_valid(g_c, groupnumber)) return 0; return &g_c->chats[groupnumber]; } /* * check if peer with real_pk is in peer array. * * return peer index if peer is in chat. * return -1 if peer is not in chat. * * TODO: make this more efficient. */ static int peer_in_chat(const Group_c *chat, const uint8_t *real_pk) { uint32_t i; for (i = 0; i < chat->numpeers; ++i) if (id_equal(chat->group[i].real_pk, real_pk)) return i; return -1; } /* * check if group with identifier is in group array. * * return group number if peer is in list. * return -1 if group is not in list. * * TODO: make this more efficient and maybe use constant time comparisons? */ static int get_group_num(const Group_Chats *g_c, const uint8_t *identifier) { uint32_t i; for (i = 0; i < g_c->num_chats; ++i) if (sodium_memcmp(g_c->chats[i].identifier, identifier, GROUP_IDENTIFIER_LENGTH) == 0) return i; return -1; } /* * check if peer with peer_number is in peer array. * * return peer number if peer is in chat. * return -1 if peer is not in chat. * * TODO: make this more efficient. */ static int get_peer_index(Group_c *g, uint16_t peer_number) { uint32_t i; for (i = 0; i < g->numpeers; ++i) if (g->group[i].peer_number == peer_number) return i; return -1; } static uint64_t calculate_comp_value(const uint8_t *pk1, const uint8_t *pk2) { uint64_t cmp1 = 0, cmp2 = 0; unsigned int i; for (i = 0; i < sizeof(uint64_t); ++i) { cmp1 = (cmp1 << 8) + (uint64_t)pk1[i]; cmp2 = (cmp2 << 8) + (uint64_t)pk2[i]; } return (cmp1 - cmp2); } enum { GROUPCHAT_CLOSEST_NONE, GROUPCHAT_CLOSEST_ADDED, GROUPCHAT_CLOSEST_REMOVED }; static int friend_in_close(Group_c *g, int friendcon_id); static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, int groupnumber, uint8_t closest, uint8_t lock); static int add_to_closest(Group_Chats *g_c, int groupnumber, const uint8_t *real_pk, const uint8_t *temp_pk) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; if (public_key_cmp(g->real_pk, real_pk) == 0) return -1; unsigned int i; unsigned int index = DESIRED_CLOSE_CONNECTIONS; for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { if (g->closest_peers[i].entry && public_key_cmp(real_pk, g->closest_peers[i].real_pk) == 0) { return 0; } } for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { if (g->closest_peers[i].entry == 0) { index = i; break; } } if (index == DESIRED_CLOSE_CONNECTIONS) { uint64_t comp_val = calculate_comp_value(g->real_pk, real_pk); uint64_t comp_d = 0; for (i = 0; i < (DESIRED_CLOSE_CONNECTIONS / 2); ++i) { uint64_t comp; comp = calculate_comp_value(g->real_pk, g->closest_peers[i].real_pk); if (comp > comp_val && comp > comp_d) { index = i; comp_d = comp; } } comp_val = calculate_comp_value(real_pk, g->real_pk); for (i = (DESIRED_CLOSE_CONNECTIONS / 2); i < DESIRED_CLOSE_CONNECTIONS; ++i) { uint64_t comp = calculate_comp_value(g->closest_peers[i].real_pk, g->real_pk); if (comp > comp_val && comp > comp_d) { index = i; comp_d = comp; } } } if (index == DESIRED_CLOSE_CONNECTIONS) { return -1; } uint8_t old_real_pk[crypto_box_PUBLICKEYBYTES]; uint8_t old_temp_pk[crypto_box_PUBLICKEYBYTES]; uint8_t old = 0; if (g->closest_peers[index].entry) { memcpy(old_real_pk, g->closest_peers[index].real_pk, crypto_box_PUBLICKEYBYTES); memcpy(old_temp_pk, g->closest_peers[index].temp_pk, crypto_box_PUBLICKEYBYTES); old = 1; } g->closest_peers[index].entry = 1; memcpy(g->closest_peers[index].real_pk, real_pk, crypto_box_PUBLICKEYBYTES); memcpy(g->closest_peers[index].temp_pk, temp_pk, crypto_box_PUBLICKEYBYTES); if (old) { add_to_closest(g_c, groupnumber, old_real_pk, old_temp_pk); } if (!g->changed) g->changed = GROUPCHAT_CLOSEST_ADDED; return 0; } static unsigned int pk_in_closest_peers(Group_c *g, uint8_t *real_pk) { unsigned int i; for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { if (!g->closest_peers[i].entry) continue; if (public_key_cmp(g->closest_peers[i].real_pk, real_pk) == 0) return 1; } return 0; } static int send_packet_online(Friend_Connections *fr_c, int friendcon_id, uint16_t group_num, uint8_t *identifier); static int connect_to_closest(Group_Chats *g_c, int groupnumber) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; if (!g->changed) return 0; unsigned int i; if (g->changed == GROUPCHAT_CLOSEST_REMOVED) { for (i = 0; i < g->numpeers; ++i) { add_to_closest(g_c, groupnumber, g->group[i].real_pk, g->group[i].temp_pk); } } for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->close[i].type == GROUPCHAT_CLOSE_NONE) continue; if (!g->close[i].closest) continue; uint8_t real_pk[crypto_box_PUBLICKEYBYTES]; uint8_t dht_temp_pk[crypto_box_PUBLICKEYBYTES]; get_friendcon_public_keys(real_pk, dht_temp_pk, g_c->fr_c, g->close[i].number); if (!pk_in_closest_peers(g, real_pk)) { g->close[i].type = GROUPCHAT_CLOSE_NONE; kill_friend_connection(g_c->fr_c, g->close[i].number); } } for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { if (!g->closest_peers[i].entry) continue; int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, g->closest_peers[i].real_pk); uint8_t lock = 1; if (friendcon_id == -1) { friendcon_id = new_friend_connection(g_c->fr_c, g->closest_peers[i].real_pk); lock = 0; if (friendcon_id == -1) { continue; } set_dht_temp_pk(g_c->fr_c, friendcon_id, g->closest_peers[i].temp_pk); } add_conn_to_groupchat(g_c, friendcon_id, groupnumber, 1, lock); if (friend_con_connected(g_c->fr_c, friendcon_id) == FRIENDCONN_STATUS_CONNECTED) { send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->identifier); } } g->changed = GROUPCHAT_CLOSEST_NONE; return 0; } /* * Add a peer to the group chat. * * return peer_index if success or peer already in chat. * return -1 if error. */ static int addpeer(Group_Chats *g_c, int groupnumber, const uint8_t *real_pk, const uint8_t *temp_pk, uint16_t peer_number) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; //TODO int peer_index = peer_in_chat(g, real_pk); if (peer_index != -1) { id_copy(g->group[peer_index].temp_pk, temp_pk); if (g->group[peer_index].peer_number != peer_number) return -1; return peer_index; } peer_index = get_peer_index(g, peer_number); if (peer_index != -1) return -1; Group_Peer *temp; temp = realloc(g->group, sizeof(Group_Peer) * (g->numpeers + 1)); if (temp == NULL) return -1; memset(&(temp[g->numpeers]), 0, sizeof(Group_Peer)); g->group = temp; id_copy(g->group[g->numpeers].real_pk, real_pk); id_copy(g->group[g->numpeers].temp_pk, temp_pk); g->group[g->numpeers].peer_number = peer_number; g->group[g->numpeers].last_recv = unix_time(); ++g->numpeers; add_to_closest(g_c, groupnumber, real_pk, temp_pk); if (g_c->peer_namelistchange) g_c->peer_namelistchange(g_c->m, groupnumber, g->numpeers - 1, CHAT_CHANGE_PEER_ADD, g_c->group_namelistchange_userdata); if (g->peer_on_join) g->peer_on_join(g->object, groupnumber, g->numpeers - 1); return (g->numpeers - 1); } static int remove_close_conn(Group_Chats *g_c, int groupnumber, int friendcon_id) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; uint32_t i; for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->close[i].type == GROUPCHAT_CLOSE_NONE) continue; if (g->close[i].number == (unsigned int)friendcon_id) { g->close[i].type = GROUPCHAT_CLOSE_NONE; kill_friend_connection(g_c->fr_c, friendcon_id); return 0; } } return -1; } /* * Delete a peer from the group chat. * * return 0 if success * return -1 if error. */ static int delpeer(Group_Chats *g_c, int groupnumber, int peer_index) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; uint32_t i; for (i = 0; i < DESIRED_CLOSE_CONNECTIONS; ++i) { /* If peer is in closest_peers list, remove it. */ if (g->closest_peers[i].entry && id_equal(g->closest_peers[i].real_pk, g->group[peer_index].real_pk)) { g->closest_peers[i].entry = 0; g->changed = GROUPCHAT_CLOSEST_REMOVED; break; } } int friendcon_id = getfriend_conn_id_pk(g_c->fr_c, g->group[peer_index].real_pk); if (friendcon_id != -1) { remove_close_conn(g_c, groupnumber, friendcon_id); } Group_Peer *temp; --g->numpeers; void *peer_object = g->group[peer_index].object; if (g->numpeers == 0) { free(g->group); g->group = NULL; } else { if (g->numpeers != (uint32_t)peer_index) memcpy(&g->group[peer_index], &g->group[g->numpeers], sizeof(Group_Peer)); temp = realloc(g->group, sizeof(Group_Peer) * (g->numpeers)); if (temp == NULL) return -1; g->group = temp; } if (g_c->peer_namelistchange) g_c->peer_namelistchange(g_c->m, groupnumber, peer_index, CHAT_CHANGE_PEER_DEL, g_c->group_namelistchange_userdata); if (g->peer_on_leave) g->peer_on_leave(g->object, groupnumber, peer_index, peer_object); return 0; } static int setnick(Group_Chats *g_c, int groupnumber, int peer_index, const uint8_t *nick, uint16_t nick_len) { if (nick_len > MAX_NAME_LENGTH) return -1; Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; /* same name as already stored? */ if (g->group[peer_index].nick_len == nick_len) if (nick_len == 0 || !memcmp(g->group[peer_index].nick, nick, nick_len)) return 0; if (nick_len) memcpy(g->group[peer_index].nick, nick, nick_len); g->group[peer_index].nick_len = nick_len; if (g_c->peer_namelistchange) g_c->peer_namelistchange(g_c->m, groupnumber, peer_index, CHAT_CHANGE_PEER_NAME, g_c->group_namelistchange_userdata); return 0; } static int settitle(Group_Chats *g_c, int groupnumber, int peer_index, const uint8_t *title, uint8_t title_len) { if (title_len > MAX_NAME_LENGTH || title_len == 0) return -1; Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; /* same as already set? */ if (g->title_len == title_len && !memcmp(g->title, title, title_len)) return 0; memcpy(g->title, title, title_len); g->title_len = title_len; if (g_c->title_callback) g_c->title_callback(g_c->m, groupnumber, peer_index, title, title_len, g_c->title_callback_userdata); return 0; } static void set_conns_type_close(Group_Chats *g_c, int groupnumber, int friendcon_id, uint8_t type) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return; uint32_t i; for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->close[i].type == GROUPCHAT_CLOSE_NONE) continue; if (g->close[i].number != (unsigned int)friendcon_id) continue; if (type == GROUPCHAT_CLOSE_ONLINE) { send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->identifier); } else { g->close[i].type = type; } } } /* Set the type for all close connections with friendcon_id */ static void set_conns_status_groups(Group_Chats *g_c, int friendcon_id, uint8_t type) { uint32_t i; for (i = 0; i < g_c->num_chats; ++i) { set_conns_type_close(g_c, i, friendcon_id, type); } } static int handle_status(void *object, int friendcon_id, uint8_t status) { Group_Chats *g_c = object; if (status) { /* Went online */ set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CLOSE_ONLINE); } else { /* Went offline */ set_conns_status_groups(g_c, friendcon_id, GROUPCHAT_CLOSE_CONNECTION); //TODO remove timedout connections? } return 0; } static int handle_packet(void *object, int friendcon_id, uint8_t *data, uint16_t length); static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uint16_t length); /* Add friend to group chat. * * return close index on success * return -1 on failure. */ static int add_conn_to_groupchat(Group_Chats *g_c, int friendcon_id, int groupnumber, uint8_t closest, uint8_t lock) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; uint16_t i, ind = MAX_GROUP_CONNECTIONS; for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->close[i].type == GROUPCHAT_CLOSE_NONE) { ind = i; continue; } if (g->close[i].number == (uint32_t)friendcon_id) { g->close[i].closest = closest; return i; /* Already in list. */ } } if (ind == MAX_GROUP_CONNECTIONS) return -1; if (lock) friend_connection_lock(g_c->fr_c, friendcon_id); g->close[ind].type = GROUPCHAT_CLOSE_CONNECTION; g->close[ind].number = friendcon_id; g->close[ind].closest = closest; //TODO friend_connection_callbacks(g_c->m->fr_c, friendcon_id, GROUPCHAT_CALLBACK_INDEX, &handle_status, &handle_packet, &handle_lossy, g_c, friendcon_id); return ind; } /* Creates a new groupchat and puts it in the chats array. * * type is one of GROUPCHAT_TYPE_* * * return group number on success. * return -1 on failure. */ int add_groupchat(Group_Chats *g_c, uint8_t type) { int groupnumber = create_group_chat(g_c); if (groupnumber == -1) return -1; Group_c *g = &g_c->chats[groupnumber]; g->status = GROUPCHAT_STATUS_CONNECTED; g->number_joined = -1; new_symmetric_key(g->identifier + 1); g->identifier[0] = type; g->peer_number = 0; /* Founder is peer 0. */ memcpy(g->real_pk, g_c->m->net_crypto->self_public_key, crypto_box_PUBLICKEYBYTES); int peer_index = addpeer(g_c, groupnumber, g->real_pk, g_c->m->dht->self_public_key, 0); if (peer_index == -1) { return -1; } setnick(g_c, groupnumber, peer_index, g_c->m->name, g_c->m->name_length); return groupnumber; } static int group_kill_peer_send(const Group_Chats *g_c, int groupnumber, uint16_t peer_num); /* Delete a groupchat from the chats array. * * return 0 on success. * return -1 if failure. */ int del_groupchat(Group_Chats *g_c, int groupnumber) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; group_kill_peer_send(g_c, groupnumber, g->peer_number); unsigned int i; for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->close[i].type == GROUPCHAT_CLOSE_NONE) continue; g->close[i].type = GROUPCHAT_CLOSE_NONE; kill_friend_connection(g_c->fr_c, g->close[i].number); } for (i = 0; i < g->numpeers; ++i) { if (g->peer_on_leave) g->peer_on_leave(g->object, groupnumber, i, g->group[i].object); } free(g->group); if (g->group_on_delete) g->group_on_delete(g->object, groupnumber); return wipe_group_chat(g_c, groupnumber); } /* Copy the public key of peernumber who is in groupnumber to pk. * pk must be crypto_box_PUBLICKEYBYTES long. * * returns 0 on success * returns -1 on failure */ int group_peer_pubkey(const Group_Chats *g_c, int groupnumber, int peernumber, uint8_t *pk) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; if ((uint32_t)peernumber >= g->numpeers) return -1; memcpy(pk, g->group[peernumber].real_pk, crypto_box_PUBLICKEYBYTES); return 0; } /* Copy the name of peernumber who is in groupnumber to name. * name must be at least MAX_NAME_LENGTH long. * * return length of name if success * return -1 if failure */ int group_peername(const Group_Chats *g_c, int groupnumber, int peernumber, uint8_t *name) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; if ((uint32_t)peernumber >= g->numpeers) return -1; if (g->group[peernumber].nick_len == 0) { memcpy(name, "Tox User", 8); return 8; } memcpy(name, g->group[peernumber].nick, g->group[peernumber].nick_len); return g->group[peernumber].nick_len; } /* List all the peers in the group chat. * * Copies the names of the peers to the name[length][MAX_NAME_LENGTH] array. * * Copies the lengths of the names to lengths[length] * * returns the number of peers on success. * * return -1 on failure. */ int group_names(const Group_Chats *g_c, int groupnumber, uint8_t names[][MAX_NAME_LENGTH], uint16_t lengths[], uint16_t length) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; unsigned int i; for (i = 0; i < g->numpeers && i < length; ++i) { lengths[i] = group_peername(g_c, groupnumber, i, names[i]); } return i; } /* Return the number of peers in the group chat on success. * return -1 on failure */ int group_number_peers(const Group_Chats *g_c, int groupnumber) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; return g->numpeers; } /* return 1 if the peernumber corresponds to ours. * return 0 on failure. */ unsigned int group_peernumber_is_ours(const Group_Chats *g_c, int groupnumber, int peernumber) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return 0; if (g->status != GROUPCHAT_STATUS_CONNECTED) return 0; if ((uint32_t)peernumber >= g->numpeers) return 0; return g->peer_number == g->group[peernumber].peer_number; } /* return the type of groupchat (GROUPCHAT_TYPE_) that groupnumber is. * * return -1 on failure. * return type on success. */ int group_get_type(const Group_Chats *g_c, int groupnumber) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; return g->identifier[0]; } /* Send a group packet to friendcon_id. * * return 1 on success * return 0 on failure */ static unsigned int send_packet_group_peer(Friend_Connections *fr_c, int friendcon_id, uint8_t packet_id, uint16_t group_num, const uint8_t *data, uint16_t length) { if (1 + sizeof(uint16_t) + length > MAX_CRYPTO_DATA_SIZE) return 0; group_num = htons(group_num); uint8_t packet[1 + sizeof(uint16_t) + length]; packet[0] = packet_id; memcpy(packet + 1, &group_num, sizeof(uint16_t)); memcpy(packet + 1 + sizeof(uint16_t), data, length); return write_cryptpacket(fr_c->net_crypto, friend_connection_crypt_connection_id(fr_c, friendcon_id), packet, sizeof(packet), 0) != -1; } /* Send a group lossy packet to friendcon_id. * * return 1 on success * return 0 on failure */ static unsigned int send_lossy_group_peer(Friend_Connections *fr_c, int friendcon_id, uint8_t packet_id, uint16_t group_num, const uint8_t *data, uint16_t length) { if (1 + sizeof(uint16_t) + length > MAX_CRYPTO_DATA_SIZE) return 0; group_num = htons(group_num); uint8_t packet[1 + sizeof(uint16_t) + length]; packet[0] = packet_id; memcpy(packet + 1, &group_num, sizeof(uint16_t)); memcpy(packet + 1 + sizeof(uint16_t), data, length); return send_lossy_cryptpacket(fr_c->net_crypto, friend_connection_crypt_connection_id(fr_c, friendcon_id), packet, sizeof(packet)) != -1; } #define INVITE_PACKET_SIZE (1 + sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH) #define INVITE_ID 0 #define INVITE_RESPONSE_PACKET_SIZE (1 + sizeof(uint16_t) * 2 + GROUP_IDENTIFIER_LENGTH) #define INVITE_RESPONSE_ID 1 /* invite friendnumber to groupnumber * return 0 on success * return -1 on failure */ int invite_friend(Group_Chats *g_c, int32_t friendnumber, int groupnumber) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; uint8_t invite[INVITE_PACKET_SIZE]; invite[0] = INVITE_ID; uint16_t groupchat_num = htons((uint16_t)groupnumber); memcpy(invite + 1, &groupchat_num, sizeof(groupchat_num)); memcpy(invite + 1 + sizeof(groupchat_num), g->identifier, GROUP_IDENTIFIER_LENGTH); if (send_group_invite_packet(g_c->m, friendnumber, invite, sizeof(invite))) { return 0; } else { wipe_group_chat(g_c, groupnumber); return -1; } } static unsigned int send_peer_query(Group_Chats *g_c, int friendcon_id, uint16_t group_num); /* Join a group (you need to have been invited first.) * * expected_type is the groupchat type we expect the chat we are joining is. * * returns group number on success * returns -1 on failure. */ int join_groupchat(Group_Chats *g_c, int32_t friendnumber, uint8_t expected_type, const uint8_t *data, uint16_t length) { if (length != sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH) return -1; if (data[sizeof(uint16_t)] != expected_type) return -1; int friendcon_id = getfriendcon_id(g_c->m, friendnumber); if (friendcon_id == -1) return -1; if (get_group_num(g_c, data + sizeof(uint16_t)) != -1) return -1; int groupnumber = create_group_chat(g_c); if (groupnumber == -1) return -1; Group_c *g = &g_c->chats[groupnumber]; uint16_t group_num = htons(groupnumber); g->status = GROUPCHAT_STATUS_VALID; g->number_joined = -1; memcpy(g->real_pk, g_c->m->net_crypto->self_public_key, crypto_box_PUBLICKEYBYTES); uint8_t response[INVITE_RESPONSE_PACKET_SIZE]; response[0] = INVITE_RESPONSE_ID; memcpy(response + 1, &group_num, sizeof(uint16_t)); memcpy(response + 1 + sizeof(uint16_t), data, sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH); if (send_group_invite_packet(g_c->m, friendnumber, response, sizeof(response))) { uint16_t other_groupnum; memcpy(&other_groupnum, data, sizeof(other_groupnum)); other_groupnum = ntohs(other_groupnum); memcpy(g->identifier, data + sizeof(uint16_t), GROUP_IDENTIFIER_LENGTH); int close_index = add_conn_to_groupchat(g_c, friendcon_id, groupnumber, 0, 1); if (close_index != -1) { g->close[close_index].group_number = other_groupnum; g->close[close_index].type = GROUPCHAT_CLOSE_ONLINE; g->number_joined = friendcon_id; } send_peer_query(g_c, friendcon_id, other_groupnum); return groupnumber; } else { g->status = GROUPCHAT_STATUS_NONE; return -1; } } /* Set the callback for group invites. * * Function(Group_Chats *g_c, int32_t friendnumber, uint8_t type, uint8_t *data, uint16_t length, void *userdata) * * data of length is what needs to be passed to join_groupchat(). */ void g_callback_group_invite(Group_Chats *g_c, void (*function)(Messenger *m, int32_t, uint8_t, const uint8_t *, uint16_t, void *), void *userdata) { g_c->invite_callback = function; g_c->invite_callback_userdata = userdata; } /* Set the callback for group messages. * * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * message, uint16_t length, void *userdata) */ void g_callback_group_message(Group_Chats *g_c, void (*function)(Messenger *m, int, int, const uint8_t *, uint16_t, void *), void *userdata) { g_c->message_callback = function; g_c->message_callback_userdata = userdata; } /* Set the callback for group actions. * * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * message, uint16_t length, void *userdata) */ void g_callback_group_action(Group_Chats *g_c, void (*function)(Messenger *m, int, int, const uint8_t *, uint16_t, void *), void *userdata) { g_c->action_callback = function; g_c->action_callback_userdata = userdata; } /* Set handlers for custom lossy packets. * * NOTE: Handler must return 0 if packet is to be relayed, -1 if the packet should not be relayed. * * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber, void *group peer object (set with group_peer_set_object), const uint8_t *packet, uint16_t length) */ void group_lossy_packet_registerhandler(Group_Chats *g_c, uint8_t byte, int (*function)(void *, int, int, void *, const uint8_t *, uint16_t)) { g_c->lossy_packethandlers[byte].function = function; } /* Set callback function for peer name list changes. * * It gets called every time the name list changes(new peer/name, deleted peer) * Function(Group_Chats *g_c, int groupnumber, int peernumber, TOX_CHAT_CHANGE change, void *userdata) */ void g_callback_group_namelistchange(Group_Chats *g_c, void (*function)(Messenger *m, int, int, uint8_t, void *), void *userdata) { g_c->peer_namelistchange = function; g_c->group_namelistchange_userdata = userdata; } /* Set callback function for title changes. * * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * title, uint8_t length, void *userdata) * if friendgroupnumber == -1, then author is unknown (e.g. initial joining the group) */ void g_callback_group_title(Group_Chats *g_c, void (*function)(Messenger *m, int, int, const uint8_t *, uint8_t, void *), void *userdata) { g_c->title_callback = function; g_c->title_callback_userdata = userdata; } /* Set a function to be called when a new peer joins a group chat. * * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber) * * return 0 on success. * return -1 on failure. */ int callback_groupchat_peer_new(const Group_Chats *g_c, int groupnumber, void (*function)(void *, int, int)) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; g->peer_on_join = function; return 0; } /* Set a function to be called when a peer leaves a group chat. * * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber, void *group peer object (set with group_peer_set_object)) * * return 0 on success. * return -1 on failure. */ int callback_groupchat_peer_delete(Group_Chats *g_c, int groupnumber, void (*function)(void *, int, int, void *)) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; g->peer_on_leave = function; return 0; } /* Set a function to be called when the group chat is deleted. * * Function(void *group object (set with group_set_object), int groupnumber) * * return 0 on success. * return -1 on failure. */ int callback_groupchat_delete(Group_Chats *g_c, int groupnumber, void (*function)(void *, int)) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; g->group_on_delete = function; return 0; } static unsigned int send_message_group(const Group_Chats *g_c, int groupnumber, uint8_t message_id, const uint8_t *data, uint16_t len); #define GROUP_MESSAGE_PING_ID 0 int group_ping_send(const Group_Chats *g_c, int groupnumber) { if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_PING_ID, 0, 0)) { return 0; } else { return -1; } } #define GROUP_MESSAGE_NEW_PEER_ID 16 #define GROUP_MESSAGE_NEW_PEER_LENGTH (sizeof(uint16_t) + crypto_box_PUBLICKEYBYTES * 2) /* send a new_peer message * return 0 on success * return -1 on failure */ int group_new_peer_send(const Group_Chats *g_c, int groupnumber, uint16_t peer_num, const uint8_t *real_pk, uint8_t *temp_pk) { uint8_t packet[GROUP_MESSAGE_NEW_PEER_LENGTH]; peer_num = htons(peer_num); memcpy(packet, &peer_num, sizeof(uint16_t)); memcpy(packet + sizeof(uint16_t), real_pk, crypto_box_PUBLICKEYBYTES); memcpy(packet + sizeof(uint16_t) + crypto_box_PUBLICKEYBYTES, temp_pk, crypto_box_PUBLICKEYBYTES); if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_NEW_PEER_ID, packet, sizeof(packet))) { return 0; } else { return -1; } } #define GROUP_MESSAGE_KILL_PEER_ID 17 #define GROUP_MESSAGE_KILL_PEER_LENGTH (sizeof(uint16_t)) /* send a kill_peer message * return 0 on success * return -1 on failure */ static int group_kill_peer_send(const Group_Chats *g_c, int groupnumber, uint16_t peer_num) { uint8_t packet[GROUP_MESSAGE_KILL_PEER_LENGTH]; peer_num = htons(peer_num); memcpy(packet, &peer_num, sizeof(uint16_t)); if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_KILL_PEER_ID, packet, sizeof(packet))) { return 0; } else { return -1; } } #define GROUP_MESSAGE_NAME_ID 48 /* send a name message * return 0 on success * return -1 on failure */ static int group_name_send(const Group_Chats *g_c, int groupnumber, const uint8_t *nick, uint16_t nick_len) { if (nick_len > MAX_NAME_LENGTH) return -1; if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_NAME_ID, nick, nick_len)) { return 0; } else { return -1; } } #define GROUP_MESSAGE_TITLE_ID 49 /* set the group's title, limited to MAX_NAME_LENGTH * return 0 on success * return -1 on failure */ int group_title_send(const Group_Chats *g_c, int groupnumber, const uint8_t *title, uint8_t title_len) { if (title_len > MAX_NAME_LENGTH || title_len == 0) return -1; Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; /* same as already set? */ if (g->title_len == title_len && !memcmp(g->title, title, title_len)) return 0; memcpy(g->title, title, title_len); g->title_len = title_len; if (g->numpeers == 1) return 0; if (send_message_group(g_c, groupnumber, GROUP_MESSAGE_TITLE_ID, title, title_len)) return 0; else return -1; } /* Get group title from groupnumber and put it in title. * title needs to be a valid memory location with a max_length size of at least MAX_NAME_LENGTH (128) bytes. * * return length of copied title if success. * return -1 if failure. */ int group_title_get(const Group_Chats *g_c, int groupnumber, uint8_t *title, uint32_t max_length) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; if (g->title_len == 0 || g->title_len > MAX_NAME_LENGTH) return -1; if (max_length > g->title_len) max_length = g->title_len; memcpy(title, g->title, max_length); return max_length; } static void handle_friend_invite_packet(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length) { Group_Chats *g_c = m->group_chat_object; if (length <= 1) return; const uint8_t *invite_data = data + 1; uint16_t invite_length = length - 1; switch (data[0]) { case INVITE_ID: { if (length != INVITE_PACKET_SIZE) return; int groupnumber = get_group_num(g_c, data + 1 + sizeof(uint16_t)); if (groupnumber == -1) { if (g_c->invite_callback) g_c->invite_callback(m, friendnumber, *(invite_data + sizeof(uint16_t)), invite_data, invite_length, g_c->invite_callback_userdata); return; } break; } case INVITE_RESPONSE_ID: { if (length != INVITE_RESPONSE_PACKET_SIZE) return; uint16_t other_groupnum, groupnum; memcpy(&groupnum, data + 1 + sizeof(uint16_t), sizeof(uint16_t)); groupnum = ntohs(groupnum); Group_c *g = get_group_c(g_c, groupnum); if (!g) return; if (sodium_memcmp(data + 1 + sizeof(uint16_t) * 2, g->identifier, GROUP_IDENTIFIER_LENGTH) != 0) return; uint16_t peer_number = rand(); /* TODO: what if two people enter the group at the same time and are given the same peer_number by different nodes? */ unsigned int tries = 0; while (get_peer_index(g, peer_number) != -1) { peer_number = rand(); ++tries; if (tries > 32) return; } memcpy(&other_groupnum, data + 1, sizeof(uint16_t)); other_groupnum = ntohs(other_groupnum); int friendcon_id = getfriendcon_id(m, friendnumber); uint8_t real_pk[crypto_box_PUBLICKEYBYTES], temp_pk[crypto_box_PUBLICKEYBYTES]; get_friendcon_public_keys(real_pk, temp_pk, g_c->fr_c, friendcon_id); addpeer(g_c, groupnum, real_pk, temp_pk, peer_number); int close_index = add_conn_to_groupchat(g_c, friendcon_id, groupnum, 0, 1); if (close_index != -1) { g->close[close_index].group_number = other_groupnum; g->close[close_index].type = GROUPCHAT_CLOSE_ONLINE; } group_new_peer_send(g_c, groupnum, peer_number, real_pk, temp_pk); break; } default: return; } } /* Find index of friend in the close list; * * returns index on success * returns -1 on failure. */ static int friend_in_close(Group_c *g, int friendcon_id) { unsigned int i; for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->close[i].type == GROUPCHAT_CLOSE_NONE) continue; if (g->close[i].number != (uint32_t)friendcon_id) continue; return i; } return -1; } /* return number of connected close connections. */ static unsigned int count_close_connected(Group_c *g) { unsigned int i, count = 0; for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->close[i].type == GROUPCHAT_CLOSE_ONLINE) { ++count; } } return count; } #define ONLINE_PACKET_DATA_SIZE (sizeof(uint16_t) + GROUP_IDENTIFIER_LENGTH) static int send_packet_online(Friend_Connections *fr_c, int friendcon_id, uint16_t group_num, uint8_t *identifier) { uint8_t packet[1 + ONLINE_PACKET_DATA_SIZE]; group_num = htons(group_num); packet[0] = PACKET_ID_ONLINE_PACKET; memcpy(packet + 1, &group_num, sizeof(uint16_t)); memcpy(packet + 1 + sizeof(uint16_t), identifier, GROUP_IDENTIFIER_LENGTH); return write_cryptpacket(fr_c->net_crypto, friend_connection_crypt_connection_id(fr_c, friendcon_id), packet, sizeof(packet), 0) != -1; } static unsigned int send_peer_kill(Group_Chats *g_c, int friendcon_id, uint16_t group_num); static int handle_packet_online(Group_Chats *g_c, int friendcon_id, uint8_t *data, uint16_t length) { if (length != ONLINE_PACKET_DATA_SIZE) return -1; int groupnumber = get_group_num(g_c, data + sizeof(uint16_t)); uint16_t other_groupnum; memcpy(&other_groupnum, data, sizeof(uint16_t)); other_groupnum = ntohs(other_groupnum); Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; int index = friend_in_close(g, friendcon_id); if (index == -1) return -1; if (g->close[index].type == GROUPCHAT_CLOSE_ONLINE) { return -1; } if (count_close_connected(g) == 0) { send_peer_query(g_c, friendcon_id, other_groupnum); } g->close[index].group_number = other_groupnum; g->close[index].type = GROUPCHAT_CLOSE_ONLINE; send_packet_online(g_c->fr_c, friendcon_id, groupnumber, g->identifier); if (g->number_joined != -1 && count_close_connected(g) >= DESIRED_CLOSE_CONNECTIONS) { int fr_close_index = friend_in_close(g, g->number_joined); if (fr_close_index == -1) return -1; if (!g->close[fr_close_index].closest) { g->close[fr_close_index].type = GROUPCHAT_CLOSE_NONE; send_peer_kill(g_c, g->close[fr_close_index].number, g->close[fr_close_index].group_number); kill_friend_connection(g_c->fr_c, g->close[fr_close_index].number); g->number_joined = -1; } } return 0; } #define PEER_KILL_ID 1 #define PEER_QUERY_ID 8 #define PEER_RESPONSE_ID 9 #define PEER_TITLE_ID 10 // we could send title with invite, but then if it changes between sending and accepting inv, joinee won't see it /* return 1 on success. * return 0 on failure */ static unsigned int send_peer_kill(Group_Chats *g_c, int friendcon_id, uint16_t group_num) { uint8_t packet[1]; packet[0] = PEER_KILL_ID; return send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_GROUPCHAT, group_num, packet, sizeof(packet)); } /* return 1 on success. * return 0 on failure */ static unsigned int send_peer_query(Group_Chats *g_c, int friendcon_id, uint16_t group_num) { uint8_t packet[1]; packet[0] = PEER_QUERY_ID; return send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_GROUPCHAT, group_num, packet, sizeof(packet)); } /* return number of peers sent on success. * return 0 on failure. */ static unsigned int send_peers(Group_Chats *g_c, int groupnumber, int friendcon_id, uint16_t group_num) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; uint8_t packet[MAX_CRYPTO_DATA_SIZE - (1 + sizeof(uint16_t))]; packet[0] = PEER_RESPONSE_ID; uint8_t *p = packet + 1; uint16_t sent = 0; unsigned int i; for (i = 0; i < g->numpeers; ++i) { if ((p - packet) + sizeof(uint16_t) + crypto_box_PUBLICKEYBYTES * 2 + 1 + g->group[i].nick_len > sizeof(packet)) { if (send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_GROUPCHAT, group_num, packet, (p - packet))) { sent = i; } else { return sent; } p = packet + 1; } uint16_t peer_num = htons(g->group[i].peer_number); memcpy(p, &peer_num, sizeof(peer_num)); p += sizeof(peer_num); memcpy(p, g->group[i].real_pk, crypto_box_PUBLICKEYBYTES); p += crypto_box_PUBLICKEYBYTES; memcpy(p, g->group[i].temp_pk, crypto_box_PUBLICKEYBYTES); p += crypto_box_PUBLICKEYBYTES; *p = g->group[i].nick_len; p += 1; memcpy(p, g->group[i].nick, g->group[i].nick_len); p += g->group[i].nick_len; } if (sent != i) { if (send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_GROUPCHAT, group_num, packet, (p - packet))) { sent = i; } } if (g->title_len) { uint8_t Packet[1 + g->title_len]; Packet[0] = PEER_TITLE_ID; memcpy(Packet + 1, g->title, g->title_len); send_packet_group_peer(g_c->fr_c, friendcon_id, PACKET_ID_DIRECT_GROUPCHAT, group_num, Packet, sizeof(Packet)); } return sent; } static int handle_send_peers(Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length) { if (length == 0) return -1; Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; const uint8_t *d = data; while ((unsigned int)(length - (d - data)) >= sizeof(uint16_t) + crypto_box_PUBLICKEYBYTES * 2 + 1) { uint16_t peer_num; memcpy(&peer_num, d, sizeof(peer_num)); peer_num = ntohs(peer_num); d += sizeof(uint16_t); int peer_index = addpeer(g_c, groupnumber, d, d + crypto_box_PUBLICKEYBYTES, peer_num); if (peer_index == -1) return -1; if (g->status == GROUPCHAT_STATUS_VALID && public_key_cmp(d, g_c->m->net_crypto->self_public_key) == 0) { g->peer_number = peer_num; g->status = GROUPCHAT_STATUS_CONNECTED; group_name_send(g_c, groupnumber, g_c->m->name, g_c->m->name_length); } d += crypto_box_PUBLICKEYBYTES * 2; uint8_t name_length = *d; d += 1; if (name_length > (length - (d - data)) || name_length > MAX_NAME_LENGTH) return -1; setnick(g_c, groupnumber, peer_index, d, name_length); d += name_length; } return 0; } static void handle_direct_packet(Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length, int close_index) { if (length == 0) return; switch (data[0]) { case PEER_KILL_ID: { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return; if (!g->close[close_index].closest) { g->close[close_index].type = GROUPCHAT_CLOSE_NONE; kill_friend_connection(g_c->fr_c, g->close[close_index].number); } } break; case PEER_QUERY_ID: { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return; send_peers(g_c, groupnumber, g->close[close_index].number, g->close[close_index].group_number); } break; case PEER_RESPONSE_ID: { handle_send_peers(g_c, groupnumber, data + 1, length - 1); } break; case PEER_TITLE_ID: { settitle(g_c, groupnumber, -1, data + 1, length - 1); } break; } } #define MIN_MESSAGE_PACKET_LEN (sizeof(uint16_t) * 2 + sizeof(uint32_t) + 1) /* Send message to all close except receiver (if receiver isn't -1) * NOTE: this function appends the group chat number to the data passed to it. * * return number of messages sent. */ static unsigned int send_message_all_close(const Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length, int receiver) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return 0; uint16_t i, sent = 0; for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->close[i].type != GROUPCHAT_CLOSE_ONLINE) continue; if ((int)i == receiver) continue; if (send_packet_group_peer(g_c->fr_c, g->close[i].number, PACKET_ID_MESSAGE_GROUPCHAT, g->close[i].group_number, data, length)) ++sent; } return sent; } /* Send lossy message to all close except receiver (if receiver isn't -1) * NOTE: this function appends the group chat number to the data passed to it. * * return number of messages sent. */ static unsigned int send_lossy_all_close(const Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length, int receiver) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return 0; unsigned int i, sent = 0, num_connected_closest = 0, connected_closest[DESIRED_CLOSE_CONNECTIONS]; for (i = 0; i < MAX_GROUP_CONNECTIONS; ++i) { if (g->close[i].type != GROUPCHAT_CLOSE_ONLINE) continue; if ((int)i == receiver) continue; if (g->close[i].closest) { connected_closest[num_connected_closest] = i; ++num_connected_closest; continue; } if (send_lossy_group_peer(g_c->fr_c, g->close[i].number, PACKET_ID_LOSSY_GROUPCHAT, g->close[i].group_number, data, length)) ++sent; } if (!num_connected_closest) { return sent; } unsigned int to_send = 0; uint64_t comp_val_old = ~0; for (i = 0; i < num_connected_closest; ++i) { uint8_t real_pk[crypto_box_PUBLICKEYBYTES]; uint8_t dht_temp_pk[crypto_box_PUBLICKEYBYTES]; get_friendcon_public_keys(real_pk, dht_temp_pk, g_c->fr_c, g->close[connected_closest[i]].number); uint64_t comp_val = calculate_comp_value(g->real_pk, real_pk); if (comp_val < comp_val_old) { to_send = connected_closest[i]; comp_val_old = comp_val; } } if (send_lossy_group_peer(g_c->fr_c, g->close[to_send].number, PACKET_ID_LOSSY_GROUPCHAT, g->close[to_send].group_number, data, length)) { ++sent; } unsigned int to_send_other = 0; comp_val_old = ~0; for (i = 0; i < num_connected_closest; ++i) { uint8_t real_pk[crypto_box_PUBLICKEYBYTES]; uint8_t dht_temp_pk[crypto_box_PUBLICKEYBYTES]; get_friendcon_public_keys(real_pk, dht_temp_pk, g_c->fr_c, g->close[connected_closest[i]].number); uint64_t comp_val = calculate_comp_value(real_pk, g->real_pk); if (comp_val < comp_val_old) { to_send_other = connected_closest[i]; comp_val_old = comp_val; } } if (to_send_other == to_send) { return sent; } if (send_lossy_group_peer(g_c->fr_c, g->close[to_send_other].number, PACKET_ID_LOSSY_GROUPCHAT, g->close[to_send_other].group_number, data, length)) { ++sent; } return sent; } #define MAX_GROUP_MESSAGE_DATA_LEN (MAX_CRYPTO_DATA_SIZE - (1 + MIN_MESSAGE_PACKET_LEN)) /* Send data of len with message_id to groupnumber. * * return number of peers it was sent to on success. * return 0 on failure. */ static unsigned int send_message_group(const Group_Chats *g_c, int groupnumber, uint8_t message_id, const uint8_t *data, uint16_t len) { if (len > MAX_GROUP_MESSAGE_DATA_LEN) return 0; Group_c *g = get_group_c(g_c, groupnumber); if (!g) return 0; if (g->status != GROUPCHAT_STATUS_CONNECTED) return 0; uint8_t packet[sizeof(uint16_t) + sizeof(uint32_t) + 1 + len]; uint16_t peer_num = htons(g->peer_number); memcpy(packet, &peer_num, sizeof(peer_num)); ++g->message_number; if (!g->message_number) ++g->message_number; uint32_t message_num = htonl(g->message_number); memcpy(packet + sizeof(uint16_t), &message_num, sizeof(message_num)); packet[sizeof(uint16_t) + sizeof(uint32_t)] = message_id; if (len) memcpy(packet + sizeof(uint16_t) + sizeof(uint32_t) + 1, data, len); return send_message_all_close(g_c, groupnumber, packet, sizeof(packet), -1); } /* send a group message * return 0 on success * return -1 on failure */ int group_message_send(const Group_Chats *g_c, int groupnumber, const uint8_t *message, uint16_t length) { if (send_message_group(g_c, groupnumber, PACKET_ID_MESSAGE, message, length)) { return 0; } else { return -1; } } /* send a group action * return 0 on success * return -1 on failure */ int group_action_send(const Group_Chats *g_c, int groupnumber, const uint8_t *action, uint16_t length) { if (send_message_group(g_c, groupnumber, PACKET_ID_ACTION, action, length)) { return 0; } else { return -1; } } /* High level function to send custom lossy packets. * * return -1 on failure. * return 0 on success. */ int send_group_lossy_packet(const Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length) { //TODO: length check here? Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; uint8_t packet[sizeof(uint16_t) * 2 + length]; uint16_t peer_number = htons(g->peer_number); memcpy(packet, &peer_number, sizeof(uint16_t)); uint16_t message_num = htons(g->lossy_message_number); memcpy(packet + sizeof(uint16_t), &message_num, sizeof(uint16_t)); memcpy(packet + sizeof(uint16_t) * 2, data, length); if (send_lossy_all_close(g_c, groupnumber, packet, sizeof(packet), -1) == 0) { return -1; } ++g->lossy_message_number; return 0; } static void handle_message_packet_group(Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length, int close_index) { if (length < sizeof(uint16_t) + sizeof(uint32_t) + 1) return; Group_c *g = get_group_c(g_c, groupnumber); if (!g) return; uint16_t peer_number; memcpy(&peer_number, data, sizeof(uint16_t)); peer_number = ntohs(peer_number); int index = get_peer_index(g, peer_number); if (index == -1) { /* We don't know the peer this packet came from so we query the list of peers from that peer. (They would not have relayed it if they didn't know the peer.) */ send_peer_query(g_c, g->close[close_index].number, g->close[close_index].group_number); return; } uint32_t message_number; memcpy(&message_number, data + sizeof(uint16_t), sizeof(message_number)); message_number = ntohl(message_number); if (g->group[index].last_message_number == 0) { g->group[index].last_message_number = message_number; } else if (message_number - g->group[index].last_message_number > 64 || message_number == g->group[index].last_message_number) { return; } g->group[index].last_message_number = message_number; uint8_t message_id = data[sizeof(uint16_t) + sizeof(message_number)]; const uint8_t *msg_data = data + sizeof(uint16_t) + sizeof(message_number) + 1; uint16_t msg_data_len = length - (sizeof(uint16_t) + sizeof(message_number) + 1); switch (message_id) { case GROUP_MESSAGE_PING_ID: { if (msg_data_len != 0) return; g->group[index].last_recv = unix_time(); } break; case GROUP_MESSAGE_NEW_PEER_ID: { if (msg_data_len != GROUP_MESSAGE_NEW_PEER_LENGTH) return; uint16_t new_peer_number; memcpy(&new_peer_number, msg_data, sizeof(uint16_t)); new_peer_number = ntohs(new_peer_number); addpeer(g_c, groupnumber, msg_data + sizeof(uint16_t), msg_data + sizeof(uint16_t) + crypto_box_PUBLICKEYBYTES, new_peer_number); } break; case GROUP_MESSAGE_KILL_PEER_ID: { if (msg_data_len != GROUP_MESSAGE_KILL_PEER_LENGTH) return; uint16_t kill_peer_number; memcpy(&kill_peer_number, msg_data, sizeof(uint16_t)); kill_peer_number = ntohs(kill_peer_number); if (peer_number == kill_peer_number) { delpeer(g_c, groupnumber, index); } else { return; //TODO } } break; case GROUP_MESSAGE_NAME_ID: { if (setnick(g_c, groupnumber, index, msg_data, msg_data_len) == -1) return; } break; case GROUP_MESSAGE_TITLE_ID: { if (settitle(g_c, groupnumber, index, msg_data, msg_data_len) == -1) return; } break; case PACKET_ID_MESSAGE: { if (msg_data_len == 0) return; uint8_t newmsg[msg_data_len + 1]; memcpy(newmsg, msg_data, msg_data_len); newmsg[msg_data_len] = 0; //TODO if (g_c->message_callback) g_c->message_callback(g_c->m, groupnumber, index, newmsg, msg_data_len, g_c->message_callback_userdata); break; } case PACKET_ID_ACTION: { if (msg_data_len == 0) return; uint8_t newmsg[msg_data_len + 1]; memcpy(newmsg, msg_data, msg_data_len); newmsg[msg_data_len] = 0; //TODO if (g_c->action_callback) g_c->action_callback(g_c->m, groupnumber, index, newmsg, msg_data_len, g_c->action_callback_userdata); break; } default: return; } send_message_all_close(g_c, groupnumber, data, length, -1/*TODO close_index*/); } static int handle_packet(void *object, int friendcon_id, uint8_t *data, uint16_t length) { Group_Chats *g_c = object; if (length < 1 + sizeof(uint16_t) + 1) return -1; if (data[0] == PACKET_ID_ONLINE_PACKET) { return handle_packet_online(g_c, friendcon_id, data + 1, length - 1); } if (data[0] != PACKET_ID_DIRECT_GROUPCHAT && data[0] != PACKET_ID_MESSAGE_GROUPCHAT) return -1; uint16_t groupnumber; memcpy(&groupnumber, data + 1, sizeof(uint16_t)); groupnumber = ntohs(groupnumber); Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; int index = friend_in_close(g, friendcon_id); if (index == -1) return -1; switch (data[0]) { case PACKET_ID_DIRECT_GROUPCHAT: { handle_direct_packet(g_c, groupnumber, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index); break; } case PACKET_ID_MESSAGE_GROUPCHAT: { handle_message_packet_group(g_c, groupnumber, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index); break; } default: { return 0; } } return 0; } /* Did we already receive the lossy packet or not. * * return -1 on failure. * return 0 if packet was not received. * return 1 if packet was received. * * TODO: test this */ static unsigned int lossy_packet_not_received(Group_c *g, int peer_index, uint16_t message_number) { if (peer_index == -1) return -1; if (g->group[peer_index].bottom_lossy_number == g->group[peer_index].top_lossy_number) { g->group[peer_index].top_lossy_number = message_number; g->group[peer_index].bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; return 0; } if ((uint16_t)(message_number - g->group[peer_index].bottom_lossy_number) < MAX_LOSSY_COUNT) { if (g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT]) { return 1; } g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; return 0; } if ((uint16_t)(message_number - g->group[peer_index].bottom_lossy_number) > (1 << 15)) return -1; uint16_t top_distance = message_number - g->group[peer_index].top_lossy_number; if (top_distance >= MAX_LOSSY_COUNT) { sodium_memzero(g->group[peer_index].recv_lossy, sizeof(g->group[peer_index].recv_lossy)); g->group[peer_index].top_lossy_number = message_number; g->group[peer_index].bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; return 0; } if (top_distance < MAX_LOSSY_COUNT) { unsigned int i; for (i = g->group[peer_index].bottom_lossy_number; i != (g->group[peer_index].bottom_lossy_number + top_distance); ++i) { g->group[peer_index].recv_lossy[i % MAX_LOSSY_COUNT] = 0; } g->group[peer_index].top_lossy_number = message_number; g->group[peer_index].bottom_lossy_number = (message_number - MAX_LOSSY_COUNT) + 1; g->group[peer_index].recv_lossy[message_number % MAX_LOSSY_COUNT] = 1; return 0; } return -1; } static int handle_lossy(void *object, int friendcon_id, const uint8_t *data, uint16_t length) { Group_Chats *g_c = object; if (length < 1 + sizeof(uint16_t) * 3 + 1) return -1; if (data[0] != PACKET_ID_LOSSY_GROUPCHAT) return -1; uint16_t groupnumber, peer_number, message_number; memcpy(&groupnumber, data + 1, sizeof(uint16_t)); memcpy(&peer_number, data + 1 + sizeof(uint16_t), sizeof(uint16_t)); memcpy(&message_number, data + 1 + sizeof(uint16_t) * 2, sizeof(uint16_t)); groupnumber = ntohs(groupnumber); peer_number = ntohs(peer_number); message_number = ntohs(message_number); Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; int index = friend_in_close(g, friendcon_id); if (index == -1) return -1; if (peer_number == g->peer_number) return -1; int peer_index = get_peer_index(g, peer_number); if (peer_index == -1) return -1; if (lossy_packet_not_received(g, peer_index, message_number)) return -1; const uint8_t *lossy_data = data + 1 + sizeof(uint16_t) * 3; uint16_t lossy_length = length - (1 + sizeof(uint16_t) * 3); uint8_t message_id = lossy_data[0]; ++lossy_data; --lossy_length; if (g_c->lossy_packethandlers[message_id].function) { if (g_c->lossy_packethandlers[message_id].function(g->object, groupnumber, peer_index, g->group[peer_index].object, lossy_data, lossy_length) == -1) { return -1; } } else { return -1; } send_lossy_all_close(g_c, groupnumber, data + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), index); return 0; } /* Set the object that is tied to the group chat. * * return 0 on success. * return -1 on failure */ int group_set_object(const Group_Chats *g_c, int groupnumber, void *object) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; g->object = object; return 0; } /* Set the object that is tied to the group peer. * * return 0 on success. * return -1 on failure */ int group_peer_set_object(const Group_Chats *g_c, int groupnumber, int peernumber, void *object) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; if ((uint32_t)peernumber >= g->numpeers) return -1; g->group[peernumber].object = object; return 0; } /* Return the object tide to the group chat previously set by group_set_object. * * return NULL on failure. * return object on success. */ void *group_get_object(const Group_Chats *g_c, int groupnumber) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return NULL; return g->object; } /* Return the object tide to the group chat peer previously set by group_peer_set_object. * * return NULL on failure. * return object on success. */ void *group_peer_get_object(const Group_Chats *g_c, int groupnumber, int peernumber) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return NULL; if ((uint32_t)peernumber >= g->numpeers) return NULL; return g->group[peernumber].object; } /* Interval in seconds to send ping messages */ #define GROUP_PING_INTERVAL 20 static int ping_groupchat(Group_Chats *g_c, int groupnumber) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; if (is_timeout(g->last_sent_ping, GROUP_PING_INTERVAL)) { if (group_ping_send(g_c, groupnumber) != -1) /* Ping */ g->last_sent_ping = unix_time(); } return 0; } static int groupchat_clear_timedout(Group_Chats *g_c, int groupnumber) { Group_c *g = get_group_c(g_c, groupnumber); if (!g) return -1; uint32_t i; for (i = 0; i < g->numpeers; ++i) { if (g->peer_number != g->group[i].peer_number && is_timeout(g->group[i].last_recv, GROUP_PING_INTERVAL * 3)) { delpeer(g_c, groupnumber, i); } if (g->group == NULL || i >= g->numpeers) break; } return 0; } /* Send current name (set in messenger) to all online groups. */ void send_name_all_groups(Group_Chats *g_c) { unsigned int i; for (i = 0; i < g_c->num_chats; ++i) { Group_c *g = get_group_c(g_c, i); if (!g) continue; if (g->status == GROUPCHAT_STATUS_CONNECTED) { group_name_send(g_c, i, g_c->m->name, g_c->m->name_length); } } } /* Create new groupchat instance. */ Group_Chats *new_groupchats(Messenger *m) { if (!m) return NULL; Group_Chats *temp = calloc(1, sizeof(Group_Chats)); if (temp == NULL) return NULL; temp->m = m; temp->fr_c = m->fr_c; m->group_chat_object = temp; m_callback_group_invite(m, &handle_friend_invite_packet); return temp; } /* main groupchats loop. */ void do_groupchats(Group_Chats *g_c) { unsigned int i; for (i = 0; i < g_c->num_chats; ++i) { Group_c *g = get_group_c(g_c, i); if (!g) continue; if (g->status == GROUPCHAT_STATUS_CONNECTED) { connect_to_closest(g_c, i); ping_groupchat(g_c, i); groupchat_clear_timedout(g_c, i); } } //TODO } /* Free everything related with group chats. */ void kill_groupchats(Group_Chats *g_c) { unsigned int i; for (i = 0; i < g_c->num_chats; ++i) { del_groupchat(g_c, i); } m_callback_group_invite(g_c->m, NULL); g_c->m->group_chat_object = 0; free(g_c); } /* Return the number of chats in the instance m. * You should use this to determine how much memory to allocate * for copy_chatlist. */ uint32_t count_chatlist(Group_Chats *g_c) { uint32_t ret = 0; uint32_t i; for (i = 0; i < g_c->num_chats; i++) { if (g_c->chats[i].status != GROUPCHAT_STATUS_NONE) { ret++; } } return ret; } /* Copy a list of valid chat 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_chatlist(Group_Chats *g_c, int32_t *out_list, uint32_t list_size) { if (!out_list) { return 0; } if (g_c->num_chats == 0) { return 0; } uint32_t i, ret = 0; for (i = 0; i < g_c->num_chats; ++i) { if (ret >= list_size) { break; /* Abandon ship */ } if (g_c->chats[i].status > GROUPCHAT_STATUS_NONE) { out_list[ret] = i; ret++; } } return ret; } ================================================ FILE: toxcore/group.h ================================================ /* group.h * * Slightly better groupchats implementation. * * 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 GROUP_H #define GROUP_H #include "Messenger.h" enum { GROUPCHAT_STATUS_NONE, GROUPCHAT_STATUS_VALID, GROUPCHAT_STATUS_CONNECTED }; enum { GROUPCHAT_TYPE_TEXT, GROUPCHAT_TYPE_AV }; #define MAX_LOSSY_COUNT 256 typedef struct { uint8_t real_pk[crypto_box_PUBLICKEYBYTES]; uint8_t temp_pk[crypto_box_PUBLICKEYBYTES]; uint64_t last_recv; uint32_t last_message_number; uint8_t nick[MAX_NAME_LENGTH]; uint8_t nick_len; uint16_t peer_number; uint8_t recv_lossy[MAX_LOSSY_COUNT]; uint16_t bottom_lossy_number, top_lossy_number; void *object; } Group_Peer; #define DESIRED_CLOSE_CONNECTIONS 4 #define MAX_GROUP_CONNECTIONS 16 #define GROUP_IDENTIFIER_LENGTH (1 + crypto_box_KEYBYTES) /* type + crypto_box_KEYBYTES so we can use new_symmetric_key(...) to fill it */ enum { GROUPCHAT_CLOSE_NONE, GROUPCHAT_CLOSE_CONNECTION, GROUPCHAT_CLOSE_ONLINE }; typedef struct { uint8_t status; Group_Peer *group; uint32_t numpeers; struct { uint8_t type; /* GROUPCHAT_CLOSE_* */ uint8_t closest; uint32_t number; uint16_t group_number; } close[MAX_GROUP_CONNECTIONS]; uint8_t real_pk[crypto_box_PUBLICKEYBYTES]; struct { uint8_t entry; uint8_t real_pk[crypto_box_PUBLICKEYBYTES]; uint8_t temp_pk[crypto_box_PUBLICKEYBYTES]; } closest_peers[DESIRED_CLOSE_CONNECTIONS]; uint8_t changed; uint8_t identifier[GROUP_IDENTIFIER_LENGTH]; uint8_t title[MAX_NAME_LENGTH]; uint8_t title_len; uint32_t message_number; uint16_t lossy_message_number; uint16_t peer_number; uint64_t last_sent_ping; int number_joined; /* friendcon_id of person that invited us to the chat. (-1 means none) */ void *object; void (*peer_on_join)(void *, int, int); void (*peer_on_leave)(void *, int, int, void *); void (*group_on_delete)(void *, int); } Group_c; typedef struct { Messenger *m; Friend_Connections *fr_c; Group_c *chats; uint32_t num_chats; void (*invite_callback)(Messenger *m, int32_t, uint8_t, const uint8_t *, uint16_t, void *); void *invite_callback_userdata; void (*message_callback)(Messenger *m, int, int, const uint8_t *, uint16_t, void *); void *message_callback_userdata; void (*action_callback)(Messenger *m, int, int, const uint8_t *, uint16_t, void *); void *action_callback_userdata; void (*peer_namelistchange)(Messenger *m, int, int, uint8_t, void *); void *group_namelistchange_userdata; void (*title_callback)(Messenger *m, int, int, const uint8_t *, uint8_t, void *); void *title_callback_userdata; struct { int (*function)(void *, int, int, void *, const uint8_t *, uint16_t); } lossy_packethandlers[256]; } Group_Chats; /* Set the callback for group invites. * * Function(Group_Chats *g_c, int32_t friendnumber, uint8_t type, uint8_t *data, uint16_t length, void *userdata) * * data of length is what needs to be passed to join_groupchat(). */ void g_callback_group_invite(Group_Chats *g_c, void (*function)(Messenger *m, int32_t, uint8_t, const uint8_t *, uint16_t, void *), void *userdata); /* Set the callback for group messages. * * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * message, uint16_t length, void *userdata) */ void g_callback_group_message(Group_Chats *g_c, void (*function)(Messenger *m, int, int, const uint8_t *, uint16_t, void *), void *userdata); /* Set the callback for group actions. * * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * message, uint16_t length, void *userdata) */ void g_callback_group_action(Group_Chats *g_c, void (*function)(Messenger *m, int, int, const uint8_t *, uint16_t, void *), void *userdata); /* Set callback function for title changes. * * Function(Group_Chats *g_c, int groupnumber, int friendgroupnumber, uint8_t * title, uint8_t length, void *userdata) * if friendgroupnumber == -1, then author is unknown (e.g. initial joining the group) */ void g_callback_group_title(Group_Chats *g_c, void (*function)(Messenger *m, int, int, const uint8_t *, uint8_t, void *), void *userdata); /* Set callback function for peer name list changes. * * It gets called every time the name list changes(new peer/name, deleted peer) * Function(Group_Chats *g_c, int groupnumber, int peernumber, TOX_CHAT_CHANGE change, void *userdata) */ enum { CHAT_CHANGE_PEER_ADD, CHAT_CHANGE_PEER_DEL, CHAT_CHANGE_PEER_NAME, }; void g_callback_group_namelistchange(Group_Chats *g_c, void (*function)(Messenger *m, int, int, uint8_t, void *), void *userdata); /* Creates a new groupchat and puts it in the chats array. * * type is one of GROUPCHAT_TYPE_* * * return group number on success. * return -1 on failure. */ int add_groupchat(Group_Chats *g_c, uint8_t type); /* Delete a groupchat from the chats array. * * return 0 on success. * return -1 if failure. */ int del_groupchat(Group_Chats *g_c, int groupnumber); /* Copy the public key of peernumber who is in groupnumber to pk. * pk must be crypto_box_PUBLICKEYBYTES long. * * returns 0 on success * returns -1 on failure */ int group_peer_pubkey(const Group_Chats *g_c, int groupnumber, int peernumber, uint8_t *pk); /* Copy the name of peernumber who is in groupnumber to name. * name must be at least MAX_NAME_LENGTH long. * * return length of name if success * return -1 if failure */ int group_peername(const Group_Chats *g_c, int groupnumber, int peernumber, uint8_t *name); /* invite friendnumber to groupnumber * return 0 on success * return -1 on failure */ int invite_friend(Group_Chats *g_c, int32_t friendnumber, int groupnumber); /* Join a group (you need to have been invited first.) * * expected_type is the groupchat type we expect the chat we are joining is. * * returns group number on success * returns -1 on failure. */ int join_groupchat(Group_Chats *g_c, int32_t friendnumber, uint8_t expected_type, const uint8_t *data, uint16_t length); /* send a group message * return 0 on success * return -1 on failure */ int group_message_send(const Group_Chats *g_c, int groupnumber, const uint8_t *message, uint16_t length); /* send a group action * return 0 on success * return -1 on failure */ int group_action_send(const Group_Chats *g_c, int groupnumber, const uint8_t *action, uint16_t length); /* set the group's title, limited to MAX_NAME_LENGTH * return 0 on success * return -1 on failure */ int group_title_send(const Group_Chats *g_c, int groupnumber, const uint8_t *title, uint8_t title_len); /* Get group title from groupnumber and put it in title. * title needs to be a valid memory location with a max_length size of at least MAX_NAME_LENGTH (128) bytes. * * return length of copied title if success. * return -1 if failure. */ int group_title_get(const Group_Chats *g_c, int groupnumber, uint8_t *title, uint32_t max_length); /* Return the number of peers in the group chat on success. * return -1 on failure */ int group_number_peers(const Group_Chats *g_c, int groupnumber); /* return 1 if the peernumber corresponds to ours. * return 0 on failure. */ unsigned int group_peernumber_is_ours(const Group_Chats *g_c, int groupnumber, int peernumber); /* List all the peers in the group chat. * * Copies the names of the peers to the name[length][MAX_NAME_LENGTH] array. * * Copies the lengths of the names to lengths[length] * * returns the number of peers on success. * * return -1 on failure. */ int group_names(const Group_Chats *g_c, int groupnumber, uint8_t names[][MAX_NAME_LENGTH], uint16_t lengths[], uint16_t length); /* Set handlers for custom lossy packets. * * NOTE: Handler must return 0 if packet is to be relayed, -1 if the packet should not be relayed. * * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber, void *group peer object (set with group_peer_set_object), const uint8_t *packet, uint16_t length) */ void group_lossy_packet_registerhandler(Group_Chats *g_c, uint8_t byte, int (*function)(void *, int, int, void *, const uint8_t *, uint16_t)); /* High level function to send custom lossy packets. * * return -1 on failure. * return 0 on success. */ int send_group_lossy_packet(const Group_Chats *g_c, int groupnumber, const uint8_t *data, uint16_t length); /* Return the number of chats in the instance m. * You should use this to determine how much memory to allocate * for copy_chatlist. */ uint32_t count_chatlist(Group_Chats *g_c); /* Copy a list of valid chat 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_chatlist(Group_Chats *g_c, int32_t *out_list, uint32_t list_size); /* return the type of groupchat (GROUPCHAT_TYPE_) that groupnumber is. * * return -1 on failure. * return type on success. */ int group_get_type(const Group_Chats *g_c, int groupnumber); /* Send current name (set in messenger) to all online groups. */ void send_name_all_groups(Group_Chats *g_c); /* Set the object that is tied to the group chat. * * return 0 on success. * return -1 on failure */ int group_set_object(const Group_Chats *g_c, int groupnumber, void *object); /* Set the object that is tied to the group peer. * * return 0 on success. * return -1 on failure */ int group_peer_set_object(const Group_Chats *g_c, int groupnumber, int peernumber, void *object); /* Return the object tide to the group chat previously set by group_set_object. * * return NULL on failure. * return object on success. */ void *group_get_object(const Group_Chats *g_c, int groupnumber); /* Return the object tide to the group chat peer previously set by group_peer_set_object. * * return NULL on failure. * return object on success. */ void *group_peer_get_object(const Group_Chats *g_c, int groupnumber, int peernumber); /* Set a function to be called when a new peer joins a group chat. * * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber) * * return 0 on success. * return -1 on failure. */ int callback_groupchat_peer_new(const Group_Chats *g_c, int groupnumber, void (*function)(void *, int, int)); /* Set a function to be called when a peer leaves a group chat. * * Function(void *group object (set with group_set_object), int groupnumber, int friendgroupnumber, void *group peer object (set with group_peer_set_object)) * * return 0 on success. * return -1 on failure. */ int callback_groupchat_peer_delete(Group_Chats *g_c, int groupnumber, void (*function)(void *, int, int, void *)); /* Set a function to be called when the group chat is deleted. * * Function(void *group object (set with group_set_object), int groupnumber) * * return 0 on success. * return -1 on failure. */ int callback_groupchat_delete(Group_Chats *g_c, int groupnumber, void (*function)(void *, int)); /* Create new groupchat instance. */ Group_Chats *new_groupchats(Messenger *m); /* main groupchats loop. */ void do_groupchats(Group_Chats *g_c); /* Free everything related with group chats. */ void kill_groupchats(Group_Chats *g_c); #endif ================================================ FILE: toxcore/list.c ================================================ /* list.h * * Simple struct with functions to create a list which associates ids with data * -Allows for finding ids associated with data such as IPs or public keys in a short time * -Should only be used if there are relatively few add/remove calls to the list * * 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 . * */ #include "list.h" /* Basically, the elements in the list are placed in order so that they can be searched for easily * -each element is seen as a big-endian integer when ordering them * -the ids array is maintained so that each id always matches * -the search algorithm cuts down the time to find the id associated with a piece of data * at the cost of slow add/remove functions for large lists * -Starts at 1/2 of the array, compares the element in the array with the data, * then moves +/- 1/4 of the array depending on whether the value is greater or lower, * then +- 1/8, etc, until the value is matched or its position where it should be in the array is found * -some considerations since the array size is never perfect */ #define INDEX(i) (~i) /* Find data in list * * return value: * >= 0 : index of data in array * < 0 : no match, returns index (return value is INDEX(index)) where * the data should be inserted */ static int find(const BS_LIST *list, const uint8_t *data) { //should work well, but could be improved if (list->n == 0) { return INDEX(0); } uint32_t i = list->n / 2; //current position in the array uint32_t delta = i / 2; //how much we move in the array if (!delta) { delta = 1; } int d = -1; //used to determine if closest match is found //closest match is found if we move back to where we have already been while (1) { int r = memcmp(data, list->data + list->element_size * i, list->element_size); if (r == 0) { return i; } if (r > 0) { //data is greater //move down i += delta; if (d == 0 || i == list->n) { //reached bottom of list, or closest match return INDEX(i); } delta = (delta) / 2; if (delta == 0) { delta = 1; d = 1; } } else { //data is smaller if (d == 1 || i == 0) { //reached top or list or closest match return INDEX(i); } //move up i -= delta; delta = (delta) / 2; if (delta == 0) { delta = 1; d = 0; } } } } /* Resized the list list * * return value: * 1 : success * 0 : failure */ static int resize(BS_LIST *list, uint32_t new_size) { void *p; p = realloc(list->data, list->element_size * new_size); if (!p) { return 0; } else { list->data = p; } p = realloc(list->ids, sizeof(int) * new_size); if (!p) { return 0; } else { list->ids = p; } return 1; } int bs_list_init(BS_LIST *list, uint32_t element_size, uint32_t initial_capacity) { //set initial values list->n = 0; list->element_size = element_size; list->capacity = 0; list->data = NULL; list->ids = NULL; if (initial_capacity != 0) { if (!resize(list, initial_capacity)) { return 0; } } list->capacity = initial_capacity; return 1; } void bs_list_free(BS_LIST *list) { //free both arrays free(list->data); free(list->ids); } int bs_list_find(const BS_LIST *list, const uint8_t *data) { int r = find(list, data); //return only -1 and positive values if (r < 0) { return -1; } return list->ids[r]; } int bs_list_add(BS_LIST *list, const uint8_t *data, int id) { //find where the new element should be inserted //see: return value of find() int i = find(list, data); if (i >= 0) { //already in list return 0; } i = ~i; //increase the size of the arrays if needed if (list->n == list->capacity) { // 1.5 * n + 1 const uint32_t new_capacity = list->n + list->n / 2 + 1; if (!resize(list, new_capacity)) { return 0; } list->capacity = new_capacity; } //insert data to element array memmove(list->data + (i + 1) * list->element_size, list->data + i * list->element_size, (list->n - i) * list->element_size); memcpy(list->data + i * list->element_size, data, list->element_size); //insert id to id array memmove(&list->ids[i + 1], &list->ids[i], (list->n - i) * sizeof(int)); list->ids[i] = id; //increase n list->n++; return 1; } int bs_list_remove(BS_LIST *list, const uint8_t *data, int id) { int i = find(list, data); if (i < 0) { return 0; } if (list->ids[i] != id) { //this should never happen return 0; } //decrease the size of the arrays if needed if (list->n < list->capacity / 2) { const uint32_t new_capacity = list->capacity / 2; if (resize(list, new_capacity)) { list->capacity = new_capacity; } } list->n--; memmove(list->data + i * list->element_size, list->data + (i + 1) * list->element_size, (list->n - i) * list->element_size); memmove(&list->ids[i], &list->ids[i + 1], (list->n - i) * sizeof(int)); return 1; } int bs_list_trim(BS_LIST *list) { if (!resize(list, list->n)) { return 0; } list->capacity = list->n; return 1; } ================================================ FILE: toxcore/list.h ================================================ /* list.h * * Simple struct with functions to create a list which associates ids with data * -Allows for finding ids associated with data such as IPs or public keys in a short time * -Should only be used if there are relatively few add/remove calls to the list * * 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 LIST_H #define LIST_H #include #include #include typedef struct { uint32_t n; //number of elements uint32_t capacity; //number of elements memory is allocated for uint32_t element_size; //size of the elements uint8_t *data; //array of elements int *ids; //array of element ids } BS_LIST; /* Initialize a list, element_size is the size of the elements in the list and * initial_capacity is the number of elements the memory will be initially allocated for * * return value: * 1 : success * 0 : failure */ int bs_list_init(BS_LIST *list, uint32_t element_size, uint32_t initial_capacity); /* Free a list initiated with list_init */ void bs_list_free(BS_LIST *list); /* Retrieve the id of an element in the list * * return value: * >= 0 : id associated with data * -1 : failure */ int bs_list_find(const BS_LIST *list, const uint8_t *data); /* Add an element with associated id to the list * * return value: * 1 : success * 0 : failure (data already in list) */ int bs_list_add(BS_LIST *list, const uint8_t *data, int id); /* Remove element from the list * * return value: * 1 : success * 0 : failure (element not found or id does not match) */ int bs_list_remove(BS_LIST *list, const uint8_t *data, int id); /* Removes the memory overhead * * return value: * 1 : success * 0 : failure */ int bs_list_trim(BS_LIST *list); #endif ================================================ FILE: toxcore/logger.c ================================================ /* logger.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 "logger.h" #include "crypto_core.h" /* for random_int() */ #include #include #include #include #include #include #include #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) # define getpid() ((unsigned) GetCurrentProcessId()) # define SFILE(FILE__M) (strrchr(FILE__M, '\\') ? strrchr(FILE__M, '\\') + 1 : FILE__M) # define WIN_CR "\r" #else # define SFILE(FILE__M) (strrchr(FILE__M, '/') ? strrchr(FILE__M, '/') + 1 : FILE__M) # define WIN_CR "" #endif struct Logger { FILE *log_file; LOG_LEVEL level; uint64_t start_time; /* Time when lib loaded */ char *id; /* Allocate these once */ char *tstr; char *posstr; char *msg; /* For thread synchronisation */ pthread_mutex_t mutex[1]; }; Logger *global = NULL; const char *LOG_LEVEL_STR [] = { [LOG_TRACE] = "TRACE", [LOG_DEBUG] = "DEBUG", [LOG_INFO] = "INFO" , [LOG_WARNING] = "WARN" , [LOG_ERROR] = "ERROR", }; char *strtime(char *dest, size_t max_len) { time_t timer; struct tm *tm_info; time(&timer); tm_info = localtime(&timer); strftime(dest, max_len, "%m:%d %H:%M:%S", tm_info); return dest; } /** * Public Functions */ Logger *logger_new (const char *file_name, LOG_LEVEL level, const char *id) { #ifndef TOX_LOGGER /* Disabled */ return NULL; #endif Logger *retu = calloc(1, sizeof(Logger)); if (!retu) return NULL; if (pthread_mutex_init(retu->mutex, NULL) != 0) { free(retu); return NULL; } if (!(retu->log_file = fopen(file_name, "ab"))) { fprintf(stderr, "Error opening logger file: %s; info: %s" WIN_CR "\n", file_name, strerror(errno)); free(retu); pthread_mutex_destroy(retu->mutex); return NULL; } if (!(retu->tstr = calloc(16, sizeof (char))) || !(retu->posstr = calloc(300, sizeof (char))) || !(retu->msg = calloc(4096, sizeof (char)))) goto FAILURE; if (id) { if (!(retu->id = calloc(strlen(id) + 1, 1))) goto FAILURE; strcpy(retu->id, id); } else { if (!(retu->id = malloc(8))) goto FAILURE; snprintf(retu->id, 8, "%u", random_int()); } retu->level = level; retu->start_time = current_time_monotonic(); fprintf(retu->log_file, "Successfully created and running logger id: %s; time: %s" WIN_CR "\n", retu->id, strtime(retu->tstr, 16)); return retu; FAILURE: fprintf(stderr, "Failed to create logger!" WIN_CR "\n"); pthread_mutex_destroy(retu->mutex); fclose(retu->log_file); free(retu->tstr); free(retu->posstr); free(retu->msg); free(retu->id); free(retu); return NULL; } void logger_kill(Logger *log) { #ifndef TOX_LOGGER /* Disabled */ return; #endif if (!log) return; pthread_mutex_lock(log->mutex); free(log->id); free(log->tstr); free(log->posstr); free(log->msg); if (fclose(log->log_file) != 0) perror("Could not close log file"); pthread_mutex_unlock(log->mutex); pthread_mutex_destroy(log->mutex); free(log); } void logger_kill_global(void) { logger_kill(global); global = NULL; } void logger_set_global(Logger *log) { #ifndef TOX_LOGGER /* Disabled */ return; #endif global = log; } Logger *logger_get_global(void) { #ifndef TOX_LOGGER /* Disabled */ return NULL; #endif return global; } void logger_write (Logger *log, LOG_LEVEL level, const char *file, int line, const char *format, ...) { #ifndef TOX_LOGGER /* Disabled */ return; #endif static const char *logger_format = "%s " /* Logger id string */ "%-16s" /* Time string of format: %m:%d %H:%M:%S */ "%-12u " /* Thread id */ "%-5s " /* Logger lever string */ "%-20s " /* File:line string */ "- %s" /* Output message */ WIN_CR "\n"; /* Every new print new line */ Logger *this_log = log ? log : global; if (!this_log) return; /* Don't print levels lesser than set one */ if (this_log->level > level) return; pthread_mutex_lock(this_log->mutex); /* Set position str */ snprintf(this_log->posstr, 300, "%s:%d", SFILE(file), line); /* Set message */ va_list args; va_start (args, format); vsnprintf(this_log->msg, 4096, format, args); va_end (args); fprintf(this_log->log_file, logger_format, this_log->id, strtime(this_log->tstr, 16), pthread_self(), LOG_LEVEL_STR[level], this_log->posstr, this_log->msg); fflush(this_log->log_file); pthread_mutex_unlock(this_log->mutex); } ================================================ FILE: toxcore/logger.h ================================================ /* logger.h * * 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 TOXLOGGER_H #define TOXLOGGER_H #include /* In case these are undefined; define 'empty' */ #ifndef LOGGER_OUTPUT_FILE # define LOGGER_OUTPUT_FILE "" #endif #ifndef LOGGER_LEVEL # define LOGGER_LEVEL LOG_ERROR #endif typedef enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR } LOG_LEVEL; typedef struct Logger Logger; /** * Set 'level' as the lowest printable level. If id == NULL, random number is used. */ Logger *logger_new (const char *file_name, LOG_LEVEL level, const char *id); void logger_kill (Logger *log); void logger_kill_global (void); /** * Global logger setter and getter. */ void logger_set_global (Logger *log); Logger *logger_get_global (void); /** * Main write function. If logging disabled does nothing. If log == NULL uses global logger. */ void logger_write (Logger *log, LOG_LEVEL level, const char *file, int line, const char *format, ...); /* To do some checks or similar only when logging, use this */ #ifdef TOX_LOGGER # define LOGGER_SCOPE(__SCOPE_DO__) do { __SCOPE_DO__ } while(0) # define LOGGER_WRITE(log, level, format, ...) \ logger_write(log, level, __FILE__, __LINE__, format, ##__VA_ARGS__) #else /* # warning "Logging disabled" */ # define LOGGER_SCOPE(__SCOPE_DO__) do {} while(0) # define LOGGER_WRITE(log, level, format, ...) do {} while(0) #endif /* TOX_LOGGER */ /* To log with an logger */ #define LOGGER_TRACE_(log, format, ...) LOGGER_WRITE(log, LOG_TRACE, format, ##__VA_ARGS__) #define LOGGER_DEBUG_(log, format, ...) LOGGER_WRITE(log, LOG_DEBUG, format, ##__VA_ARGS__) #define LOGGER_INFO_(log, format, ...) LOGGER_WRITE(log, LOG_INFO, format, ##__VA_ARGS__) #define LOGGER_WARNING_(log, format, ...) LOGGER_WRITE(log, LOG_WARNING, format, ##__VA_ARGS__) #define LOGGER_ERROR_(log, format, ...) LOGGER_WRITE(log, LOG_ERROR, format, ##__VA_ARGS__) /* To log with the global logger */ #define LOGGER_TRACE(format, ...) LOGGER_TRACE_(NULL, format, ##__VA_ARGS__) #define LOGGER_DEBUG(format, ...) LOGGER_DEBUG_(NULL, format, ##__VA_ARGS__) #define LOGGER_INFO(format, ...) LOGGER_INFO_(NULL, format, ##__VA_ARGS__) #define LOGGER_WARNING(format, ...) LOGGER_WARNING_(NULL, format, ##__VA_ARGS__) #define LOGGER_ERROR(format, ...) LOGGER_ERROR_(NULL, format, ##__VA_ARGS__) #endif /* TOXLOGGER_H */ ================================================ FILE: toxcore/misc_tools.h ================================================ /* misc_tools.h * * 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 . * */ #ifndef MISC_TOOLS_H #define MISC_TOOLS_H /****************************Algorithms*************************** * Macro/generic definitions for useful algorithms *****************************************************************/ /* Creates a new quick_sort implementation for arrays of the specified type. * For a type T (eg: int, char), creates a function named T_quick_sort. * * Quick Sort: Complexity O(nlogn) * arr - the array to sort * n - the sort index (should be called with n = length(arr)) * cmpfn - a function that compares two values of type type. * Must return -1, 0, 1 for a < b, a == b, and a > b respectively. */ /* Must be called in the header file. */ #define declare_quick_sort(type) \ void type##_quick_sort(type *arr, int n, int (*cmpfn)(type, type)); /* Must be called in the C file. */ #define make_quick_sort(type) \ void type##_quick_sort(type *arr, int n, int (*cmpfn)(type, type)) \ { \ if ((n) < 2) \ return; \ type _p_ = (arr)[(n) / 2]; \ type *_l_ = (arr); \ type *_r_ = (arr) + n - 1; \ while (_l_ <= _r_) { \ if (cmpfn(*_l_, _p_) == -1) { \ ++_l_; \ continue; \ } \ if (cmpfn(*_r_, _p_) == 1) { \ --_r_; \ continue; \ } \ type _t_ = *_l_; \ *_l_++ = *_r_; \ *_r_-- = _t_; \ } \ type##_quick_sort((arr), _r_ - (arr) + 1, cmpfn); \ type##_quick_sort(_l_, (arr) + n - _l_, cmpfn); \ } #endif // MISC_TOOLS_H ================================================ FILE: toxcore/net_crypto.c ================================================ /* net_crypto.c * * Functions for the core network crypto. * * NOTE: This code has to be perfect. We don't mess around with encryption. * * 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 "net_crypto.h" #include "util.h" #include "math.h" #include "logger.h" static uint8_t crypt_connection_id_not_valid(const Net_Crypto *c, int crypt_connection_id) { if ((uint32_t)crypt_connection_id >= c->crypto_connections_length) return 1; if (c->crypto_connections == NULL) return 1; if (c->crypto_connections[crypt_connection_id].status == CRYPTO_CONN_NO_CONNECTION) return 1; return 0; } /* cookie timeout in seconds */ #define COOKIE_TIMEOUT 15 #define COOKIE_DATA_LENGTH (crypto_box_PUBLICKEYBYTES * 2) #define COOKIE_CONTENTS_LENGTH (sizeof(uint64_t) + COOKIE_DATA_LENGTH) #define COOKIE_LENGTH (crypto_box_NONCEBYTES + COOKIE_CONTENTS_LENGTH + crypto_box_MACBYTES) #define COOKIE_REQUEST_PLAIN_LENGTH (COOKIE_DATA_LENGTH + sizeof(uint64_t)) #define COOKIE_REQUEST_LENGTH (1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + COOKIE_REQUEST_PLAIN_LENGTH + crypto_box_MACBYTES) #define COOKIE_RESPONSE_LENGTH (1 + crypto_box_NONCEBYTES + COOKIE_LENGTH + sizeof(uint64_t) + crypto_box_MACBYTES) /* Create a cookie request packet and put it in packet. * dht_public_key is the dht public key of the other * * packet must be of size COOKIE_REQUEST_LENGTH or bigger. * * return -1 on failure. * return COOKIE_REQUEST_LENGTH on success. */ static int create_cookie_request(const Net_Crypto *c, uint8_t *packet, uint8_t *dht_public_key, uint64_t number, uint8_t *shared_key) { uint8_t plain[COOKIE_REQUEST_PLAIN_LENGTH]; uint8_t padding[crypto_box_PUBLICKEYBYTES] = {0}; memcpy(plain, c->self_public_key, crypto_box_PUBLICKEYBYTES); memcpy(plain + crypto_box_PUBLICKEYBYTES, padding, crypto_box_PUBLICKEYBYTES); memcpy(plain + (crypto_box_PUBLICKEYBYTES * 2), &number, sizeof(uint64_t)); DHT_get_shared_key_sent(c->dht, shared_key, dht_public_key); uint8_t nonce[crypto_box_NONCEBYTES]; new_nonce(nonce); packet[0] = NET_PACKET_COOKIE_REQUEST; memcpy(packet + 1, c->dht->self_public_key, crypto_box_PUBLICKEYBYTES); memcpy(packet + 1 + crypto_box_PUBLICKEYBYTES, nonce, crypto_box_NONCEBYTES); int len = encrypt_data_symmetric(shared_key, nonce, plain, sizeof(plain), packet + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES); if (len != COOKIE_REQUEST_PLAIN_LENGTH + crypto_box_MACBYTES) return -1; return (1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + len); } /* Create cookie of length COOKIE_LENGTH from bytes of length COOKIE_DATA_LENGTH using encryption_key * * return -1 on failure. * return 0 on success. */ static int create_cookie(uint8_t *cookie, const uint8_t *bytes, const uint8_t *encryption_key) { uint8_t contents[COOKIE_CONTENTS_LENGTH]; uint64_t temp_time = unix_time(); memcpy(contents, &temp_time, sizeof(temp_time)); memcpy(contents + sizeof(temp_time), bytes, COOKIE_DATA_LENGTH); new_nonce(cookie); int len = encrypt_data_symmetric(encryption_key, cookie, contents, sizeof(contents), cookie + crypto_box_NONCEBYTES); if (len != COOKIE_LENGTH - crypto_box_NONCEBYTES) return -1; return 0; } /* Open cookie of length COOKIE_LENGTH to bytes of length COOKIE_DATA_LENGTH using encryption_key * * return -1 on failure. * return 0 on success. */ static int open_cookie(uint8_t *bytes, const uint8_t *cookie, const uint8_t *encryption_key) { uint8_t contents[COOKIE_CONTENTS_LENGTH]; int len = decrypt_data_symmetric(encryption_key, cookie, cookie + crypto_box_NONCEBYTES, COOKIE_LENGTH - crypto_box_NONCEBYTES, contents); if (len != sizeof(contents)) return -1; uint64_t cookie_time; memcpy(&cookie_time, contents, sizeof(cookie_time)); uint64_t temp_time = unix_time(); if (cookie_time + COOKIE_TIMEOUT < temp_time || temp_time < cookie_time) return -1; memcpy(bytes, contents + sizeof(cookie_time), COOKIE_DATA_LENGTH); return 0; } /* Create a cookie response packet and put it in packet. * request_plain must be COOKIE_REQUEST_PLAIN_LENGTH bytes. * packet must be of size COOKIE_RESPONSE_LENGTH or bigger. * * return -1 on failure. * return COOKIE_RESPONSE_LENGTH on success. */ static int create_cookie_response(const Net_Crypto *c, uint8_t *packet, const uint8_t *request_plain, const uint8_t *shared_key, const uint8_t *dht_public_key) { uint8_t cookie_plain[COOKIE_DATA_LENGTH]; memcpy(cookie_plain, request_plain, crypto_box_PUBLICKEYBYTES); memcpy(cookie_plain + crypto_box_PUBLICKEYBYTES, dht_public_key, crypto_box_PUBLICKEYBYTES); uint8_t plain[COOKIE_LENGTH + sizeof(uint64_t)]; if (create_cookie(plain, cookie_plain, c->secret_symmetric_key) != 0) return -1; memcpy(plain + COOKIE_LENGTH, request_plain + COOKIE_DATA_LENGTH, sizeof(uint64_t)); packet[0] = NET_PACKET_COOKIE_RESPONSE; new_nonce(packet + 1); int len = encrypt_data_symmetric(shared_key, packet + 1, plain, sizeof(plain), packet + 1 + crypto_box_NONCEBYTES); if (len != COOKIE_RESPONSE_LENGTH - (1 + crypto_box_NONCEBYTES)) return -1; return COOKIE_RESPONSE_LENGTH; } /* Handle the cookie request packet of length length. * Put what was in the request in request_plain (must be of size COOKIE_REQUEST_PLAIN_LENGTH) * Put the key used to decrypt the request into shared_key (of size crypto_box_BEFORENMBYTES) for use in the response. * * return -1 on failure. * return 0 on success. */ static int handle_cookie_request(const Net_Crypto *c, uint8_t *request_plain, uint8_t *shared_key, uint8_t *dht_public_key, const uint8_t *packet, uint16_t length) { if (length != COOKIE_REQUEST_LENGTH) return -1; memcpy(dht_public_key, packet + 1, crypto_box_PUBLICKEYBYTES); DHT_get_shared_key_sent(c->dht, shared_key, dht_public_key); int len = decrypt_data_symmetric(shared_key, packet + 1 + crypto_box_PUBLICKEYBYTES, packet + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES, COOKIE_REQUEST_PLAIN_LENGTH + crypto_box_MACBYTES, request_plain); if (len != COOKIE_REQUEST_PLAIN_LENGTH) return -1; return 0; } /* Handle the cookie request packet (for raw UDP) */ static int udp_handle_cookie_request(void *object, IP_Port source, const uint8_t *packet, uint16_t length) { Net_Crypto *c = object; uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; uint8_t shared_key[crypto_box_BEFORENMBYTES]; uint8_t dht_public_key[crypto_box_PUBLICKEYBYTES]; if (handle_cookie_request(c, request_plain, shared_key, dht_public_key, packet, length) != 0) return 1; uint8_t data[COOKIE_RESPONSE_LENGTH]; if (create_cookie_response(c, data, request_plain, shared_key, dht_public_key) != sizeof(data)) return 1; if ((uint32_t)sendpacket(c->dht->net, source, data, sizeof(data)) != sizeof(data)) return 1; return 0; } /* Handle the cookie request packet (for TCP) */ static int tcp_handle_cookie_request(Net_Crypto *c, int connections_number, const uint8_t *packet, uint16_t length) { uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; uint8_t shared_key[crypto_box_BEFORENMBYTES]; uint8_t dht_public_key[crypto_box_PUBLICKEYBYTES]; if (handle_cookie_request(c, request_plain, shared_key, dht_public_key, packet, length) != 0) return -1; uint8_t data[COOKIE_RESPONSE_LENGTH]; if (create_cookie_response(c, data, request_plain, shared_key, dht_public_key) != sizeof(data)) return -1; int ret = send_packet_tcp_connection(c->tcp_c, connections_number, data, sizeof(data)); return ret; } /* Handle the cookie request packet (for TCP oob packets) */ static int tcp_oob_handle_cookie_request(const Net_Crypto *c, unsigned int tcp_connections_number, const uint8_t *dht_public_key, const uint8_t *packet, uint16_t length) { uint8_t request_plain[COOKIE_REQUEST_PLAIN_LENGTH]; uint8_t shared_key[crypto_box_BEFORENMBYTES]; uint8_t dht_public_key_temp[crypto_box_PUBLICKEYBYTES]; if (handle_cookie_request(c, request_plain, shared_key, dht_public_key_temp, packet, length) != 0) return -1; if (public_key_cmp(dht_public_key, dht_public_key_temp) != 0) return -1; uint8_t data[COOKIE_RESPONSE_LENGTH]; if (create_cookie_response(c, data, request_plain, shared_key, dht_public_key) != sizeof(data)) return -1; int ret = tcp_send_oob_packet(c->tcp_c, tcp_connections_number, dht_public_key, data, sizeof(data)); return ret; } /* Handle a cookie response packet of length encrypted with shared_key. * put the cookie in the response in cookie * * cookie must be of length COOKIE_LENGTH. * * return -1 on failure. * return COOKIE_LENGTH on success. */ static int handle_cookie_response(uint8_t *cookie, uint64_t *number, const uint8_t *packet, uint16_t length, const uint8_t *shared_key) { if (length != COOKIE_RESPONSE_LENGTH) return -1; uint8_t plain[COOKIE_LENGTH + sizeof(uint64_t)]; int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + crypto_box_NONCEBYTES, length - (1 + crypto_box_NONCEBYTES), plain); if (len != sizeof(plain)) return -1; memcpy(cookie, plain, COOKIE_LENGTH); memcpy(number, plain + COOKIE_LENGTH, sizeof(uint64_t)); return COOKIE_LENGTH; } #define HANDSHAKE_PACKET_LENGTH (1 + COOKIE_LENGTH + crypto_box_NONCEBYTES + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + crypto_hash_sha512_BYTES + COOKIE_LENGTH + crypto_box_MACBYTES) /* Create a handshake packet and put it in packet. * cookie must be COOKIE_LENGTH bytes. * packet must be of size HANDSHAKE_PACKET_LENGTH or bigger. * * return -1 on failure. * return HANDSHAKE_PACKET_LENGTH on success. */ static int create_crypto_handshake(const Net_Crypto *c, uint8_t *packet, const uint8_t *cookie, const uint8_t *nonce, const uint8_t *session_pk, const uint8_t *peer_real_pk, const uint8_t *peer_dht_pubkey) { uint8_t plain[crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + crypto_hash_sha512_BYTES + COOKIE_LENGTH]; memcpy(plain, nonce, crypto_box_NONCEBYTES); memcpy(plain + crypto_box_NONCEBYTES, session_pk, crypto_box_PUBLICKEYBYTES); crypto_hash_sha512(plain + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES, cookie, COOKIE_LENGTH); uint8_t cookie_plain[COOKIE_DATA_LENGTH]; memcpy(cookie_plain, peer_real_pk, crypto_box_PUBLICKEYBYTES); memcpy(cookie_plain + crypto_box_PUBLICKEYBYTES, peer_dht_pubkey, crypto_box_PUBLICKEYBYTES); if (create_cookie(plain + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + crypto_hash_sha512_BYTES, cookie_plain, c->secret_symmetric_key) != 0) return -1; new_nonce(packet + 1 + COOKIE_LENGTH); int len = encrypt_data(peer_real_pk, c->self_secret_key, packet + 1 + COOKIE_LENGTH, plain, sizeof(plain), packet + 1 + COOKIE_LENGTH + crypto_box_NONCEBYTES); if (len != HANDSHAKE_PACKET_LENGTH - (1 + COOKIE_LENGTH + crypto_box_NONCEBYTES)) return -1; packet[0] = NET_PACKET_CRYPTO_HS; memcpy(packet + 1, cookie, COOKIE_LENGTH); return HANDSHAKE_PACKET_LENGTH; } /* Handle a crypto handshake packet of length. * put the nonce contained in the packet in nonce, * the session public key in session_pk * the real public key of the peer in peer_real_pk * the dht public key of the peer in dht_public_key and * the cookie inside the encrypted part of the packet in cookie. * * if expected_real_pk isn't NULL it denotes the real public key * the packet should be from. * * nonce must be at least crypto_box_NONCEBYTES * session_pk must be at least crypto_box_PUBLICKEYBYTES * peer_real_pk must be at least crypto_box_PUBLICKEYBYTES * cookie must be at least COOKIE_LENGTH * * return -1 on failure. * return 0 on success. */ static int handle_crypto_handshake(const Net_Crypto *c, uint8_t *nonce, uint8_t *session_pk, uint8_t *peer_real_pk, uint8_t *dht_public_key, uint8_t *cookie, const uint8_t *packet, uint16_t length, const uint8_t *expected_real_pk) { if (length != HANDSHAKE_PACKET_LENGTH) return -1; uint8_t cookie_plain[COOKIE_DATA_LENGTH]; if (open_cookie(cookie_plain, packet + 1, c->secret_symmetric_key) != 0) return -1; if (expected_real_pk) if (public_key_cmp(cookie_plain, expected_real_pk) != 0) return -1; uint8_t cookie_hash[crypto_hash_sha512_BYTES]; crypto_hash_sha512(cookie_hash, packet + 1, COOKIE_LENGTH); uint8_t plain[crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + crypto_hash_sha512_BYTES + COOKIE_LENGTH]; int len = decrypt_data(cookie_plain, c->self_secret_key, packet + 1 + COOKIE_LENGTH, packet + 1 + COOKIE_LENGTH + crypto_box_NONCEBYTES, HANDSHAKE_PACKET_LENGTH - (1 + COOKIE_LENGTH + crypto_box_NONCEBYTES), plain); if (len != sizeof(plain)) return -1; if (sodium_memcmp(cookie_hash, plain + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES, crypto_hash_sha512_BYTES) != 0) return -1; memcpy(nonce, plain, crypto_box_NONCEBYTES); memcpy(session_pk, plain + crypto_box_NONCEBYTES, crypto_box_PUBLICKEYBYTES); memcpy(cookie, plain + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + crypto_hash_sha512_BYTES, COOKIE_LENGTH); memcpy(peer_real_pk, cookie_plain, crypto_box_PUBLICKEYBYTES); memcpy(dht_public_key, cookie_plain + crypto_box_PUBLICKEYBYTES, crypto_box_PUBLICKEYBYTES); return 0; } static Crypto_Connection *get_crypto_connection(const Net_Crypto *c, int crypt_connection_id) { if (crypt_connection_id_not_valid(c, crypt_connection_id)) return 0; return &c->crypto_connections[crypt_connection_id]; } /* Associate an ip_port to a connection. * * return -1 on failure. * return 0 on success. */ static int add_ip_port_connection(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; if (ip_port.ip.family == AF_INET) { if (!ipport_equal(&ip_port, &conn->ip_portv4) && LAN_ip(conn->ip_portv4.ip) != 0) { if (!bs_list_add(&c->ip_port_list, (uint8_t *)&ip_port, crypt_connection_id)) return -1; bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv4, crypt_connection_id); conn->ip_portv4 = ip_port; return 0; } } else if (ip_port.ip.family == AF_INET6) { if (!ipport_equal(&ip_port, &conn->ip_portv6)) { if (!bs_list_add(&c->ip_port_list, (uint8_t *)&ip_port, crypt_connection_id)) return -1; bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv6, crypt_connection_id); conn->ip_portv6 = ip_port; return 0; } } return -1; } /* Return the IP_Port that should be used to send packets to the other peer. * * return IP_Port with family 0 on failure. * return IP_Port on success. */ IP_Port return_ip_port_connection(Net_Crypto *c, int crypt_connection_id) { IP_Port empty; empty.ip.family = 0; Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return empty; uint64_t current_time = unix_time(); _Bool v6 = 0, v4 = 0; if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev4) > current_time) { v4 = 1; } if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev6) > current_time) { v6 = 1; } if (v4 && LAN_ip(conn->ip_portv4.ip) == 0) { return conn->ip_portv4; } else if (v6 && conn->ip_portv6.ip.family == AF_INET6) { return conn->ip_portv6; } else if (conn->ip_portv4.ip.family == AF_INET) { return conn->ip_portv4; } else { return empty; } } /* Sends a packet to the peer using the fastest route. * * return -1 on failure. * return 0 on success. */ static int send_packet_to(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length) { //TODO TCP, etc... Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; int direct_send_attempt = 0; pthread_mutex_lock(&conn->mutex); IP_Port ip_port = return_ip_port_connection(c, crypt_connection_id); //TODO: on bad networks, direct connections might not last indefinitely. if (ip_port.ip.family != 0) { _Bool direct_connected = 0; crypto_connection_status(c, crypt_connection_id, &direct_connected, NULL); if (direct_connected) { if ((uint32_t)sendpacket(c->dht->net, ip_port, data, length) == length) { pthread_mutex_unlock(&conn->mutex); return 0; } else { pthread_mutex_unlock(&conn->mutex); return -1; } } //TODO: a better way of sending packets directly to confirm the others ip. uint64_t current_time = unix_time(); if ((((UDP_DIRECT_TIMEOUT / 2) + conn->direct_send_attempt_time) > current_time && length < 96) || data[0] == NET_PACKET_COOKIE_REQUEST || data[0] == NET_PACKET_CRYPTO_HS) { if ((uint32_t)sendpacket(c->dht->net, ip_port, data, length) == length) { direct_send_attempt = 1; conn->direct_send_attempt_time = unix_time(); } } } pthread_mutex_unlock(&conn->mutex); pthread_mutex_lock(&c->tcp_mutex); int ret = send_packet_tcp_connection(c->tcp_c, conn->connection_number_tcp, data, length); pthread_mutex_unlock(&c->tcp_mutex); pthread_mutex_lock(&conn->mutex); if (ret == 0) { conn->last_tcp_sent = current_time_monotonic(); } pthread_mutex_unlock(&conn->mutex); if (ret == 0 || direct_send_attempt) { return 0; } return -1; } /** START: Array Related functions **/ /* Return number of packets in array * Note that holes are counted too. */ static uint32_t num_packets_array(const Packets_Array *array) { return array->buffer_end - array->buffer_start; } /* Add data with packet number to array. * * return -1 on failure. * return 0 on success. */ static int add_data_to_buffer(Packets_Array *array, uint32_t number, const Packet_Data *data) { if (number - array->buffer_start > CRYPTO_PACKET_BUFFER_SIZE) return -1; uint32_t num = number % CRYPTO_PACKET_BUFFER_SIZE; if (array->buffer[num]) return -1; Packet_Data *new_d = malloc(sizeof(Packet_Data)); if (new_d == NULL) return -1; memcpy(new_d, data, sizeof(Packet_Data)); array->buffer[num] = new_d; if ((number - array->buffer_start) >= (array->buffer_end - array->buffer_start)) array->buffer_end = number + 1; return 0; } /* Get pointer of data with packet number. * * return -1 on failure. * return 0 if data at number is empty. * return 1 if data pointer was put in data. */ static int get_data_pointer(const Packets_Array *array, Packet_Data **data, uint32_t number) { uint32_t num_spots = array->buffer_end - array->buffer_start; if (array->buffer_end - number > num_spots || number - array->buffer_start >= num_spots) return -1; uint32_t num = number % CRYPTO_PACKET_BUFFER_SIZE; if (!array->buffer[num]) return 0; *data = array->buffer[num]; return 1; } /* Add data to end of array. * * return -1 on failure. * return packet number on success. */ static int64_t add_data_end_of_buffer(Packets_Array *array, const Packet_Data *data) { if (num_packets_array(array) >= CRYPTO_PACKET_BUFFER_SIZE) return -1; Packet_Data *new_d = malloc(sizeof(Packet_Data)); if (new_d == NULL) return -1; memcpy(new_d, data, sizeof(Packet_Data)); uint32_t id = array->buffer_end; array->buffer[id % CRYPTO_PACKET_BUFFER_SIZE] = new_d; ++array->buffer_end; return id; } /* Read data from begginning of array. * * return -1 on failure. * return packet number on success. */ static int64_t read_data_beg_buffer(Packets_Array *array, Packet_Data *data) { if (array->buffer_end == array->buffer_start) return -1; uint32_t num = array->buffer_start % CRYPTO_PACKET_BUFFER_SIZE; if (!array->buffer[num]) return -1; memcpy(data, array->buffer[num], sizeof(Packet_Data)); uint32_t id = array->buffer_start; ++array->buffer_start; free(array->buffer[num]); array->buffer[num] = NULL; return id; } /* Delete all packets in array before number (but not number) * * return -1 on failure. * return 0 on success */ static int clear_buffer_until(Packets_Array *array, uint32_t number) { uint32_t num_spots = array->buffer_end - array->buffer_start; if (array->buffer_end - number >= num_spots || number - array->buffer_start > num_spots) return -1; uint32_t i; for (i = array->buffer_start; i != number; ++i) { uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; if (array->buffer[num]) { free(array->buffer[num]); array->buffer[num] = NULL; } } array->buffer_start = i; return 0; } static int clear_buffer(Packets_Array *array) { uint32_t i; for (i = array->buffer_start; i != array->buffer_end; ++i) { uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; if (array->buffer[num]) { free(array->buffer[num]); array->buffer[num] = NULL; } } array->buffer_start = i; return 0; } /* Set array buffer end to number. * * return -1 on failure. * return 0 on success. */ static int set_buffer_end(Packets_Array *array, uint32_t number) { if ((number - array->buffer_start) > CRYPTO_PACKET_BUFFER_SIZE) return -1; if ((number - array->buffer_end) > CRYPTO_PACKET_BUFFER_SIZE) return -1; array->buffer_end = number; return 0; } /* Create a packet request packet from recv_array and send_buffer_end into * data of length. * * return -1 on failure. * return length of packet on success. */ static int generate_request_packet(uint8_t *data, uint16_t length, const Packets_Array *recv_array) { if (length == 0) return -1; data[0] = PACKET_ID_REQUEST; uint16_t cur_len = 1; if (recv_array->buffer_start == recv_array->buffer_end) return cur_len; if (length <= cur_len) return cur_len; uint32_t i, n = 1; for (i = recv_array->buffer_start; i != recv_array->buffer_end; ++i) { uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; if (!recv_array->buffer[num]) { data[cur_len] = n; n = 0; ++cur_len; if (length <= cur_len) return cur_len; } else if (n == 255) { data[cur_len] = 0; n = 0; ++cur_len; if (length <= cur_len) return cur_len; } ++n; } return cur_len; } /* Handle a request data packet. * Remove all the packets the other received from the array. * * return -1 on failure. * return number of requested packets on success. */ static int handle_request_packet(Packets_Array *send_array, const uint8_t *data, uint16_t length, uint64_t *latest_send_time, uint64_t rtt_time) { if (length < 1) return -1; if (data[0] != PACKET_ID_REQUEST) return -1; if (length == 1) return 0; ++data; --length; uint32_t i, n = 1; uint32_t requested = 0; uint64_t temp_time = current_time_monotonic(); uint64_t l_sent_time = ~0; for (i = send_array->buffer_start; i != send_array->buffer_end; ++i) { if (length == 0) break; uint32_t num = i % CRYPTO_PACKET_BUFFER_SIZE; if (n == data[0]) { if (send_array->buffer[num]) { uint64_t sent_time = send_array->buffer[num]->sent_time; if ((sent_time + rtt_time) < temp_time) { send_array->buffer[num]->sent_time = 0; } } ++data; --length; n = 0; ++requested; } else { if (send_array->buffer[num]) { uint64_t sent_time = send_array->buffer[num]->sent_time; if (l_sent_time < sent_time) l_sent_time = sent_time; free(send_array->buffer[num]); send_array->buffer[num] = NULL; } } if (n == 255) { n = 1; if (data[0] != 0) return -1; ++data; --length; } else { ++n; } } if (*latest_send_time < l_sent_time) *latest_send_time = l_sent_time; return requested; } /** END: Array Related functions **/ #define MAX_DATA_DATA_PACKET_SIZE (MAX_CRYPTO_PACKET_SIZE - (1 + sizeof(uint16_t) + crypto_box_MACBYTES)) /* Creates and sends a data packet to the peer using the fastest route. * * return -1 on failure. * return 0 on success. */ static int send_data_packet(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length) { if (length == 0 || length + (1 + sizeof(uint16_t) + crypto_box_MACBYTES) > MAX_CRYPTO_PACKET_SIZE) return -1; Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; pthread_mutex_lock(&conn->mutex); uint8_t packet[1 + sizeof(uint16_t) + length + crypto_box_MACBYTES]; packet[0] = NET_PACKET_CRYPTO_DATA; memcpy(packet + 1, conn->sent_nonce + (crypto_box_NONCEBYTES - sizeof(uint16_t)), sizeof(uint16_t)); int len = encrypt_data_symmetric(conn->shared_key, conn->sent_nonce, data, length, packet + 1 + sizeof(uint16_t)); if (len + 1 + sizeof(uint16_t) != sizeof(packet)) { pthread_mutex_unlock(&conn->mutex); return -1; } increment_nonce(conn->sent_nonce); pthread_mutex_unlock(&conn->mutex); return send_packet_to(c, crypt_connection_id, packet, sizeof(packet)); } /* Creates and sends a data packet with buffer_start and num to the peer using the fastest route. * * return -1 on failure. * return 0 on success. */ static int send_data_packet_helper(Net_Crypto *c, int crypt_connection_id, uint32_t buffer_start, uint32_t num, const uint8_t *data, uint16_t length) { if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) return -1; num = htonl(num); buffer_start = htonl(buffer_start); uint16_t padding_length = (MAX_CRYPTO_DATA_SIZE - length) % CRYPTO_MAX_PADDING; uint8_t packet[sizeof(uint32_t) + sizeof(uint32_t) + padding_length + length]; memcpy(packet, &buffer_start, sizeof(uint32_t)); memcpy(packet + sizeof(uint32_t), &num, sizeof(uint32_t)); memset(packet + (sizeof(uint32_t) * 2), PACKET_ID_PADDING, padding_length); memcpy(packet + (sizeof(uint32_t) * 2) + padding_length, data, length); return send_data_packet(c, crypt_connection_id, packet, sizeof(packet)); } static int reset_max_speed_reached(Net_Crypto *c, int crypt_connection_id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; /* If last packet send failed, try to send packet again. If sending it fails we won't be able to send the new packet. */ if (conn->maximum_speed_reached) { Packet_Data *dt = NULL; uint32_t packet_num = conn->send_array.buffer_end - 1; int ret = get_data_pointer(&conn->send_array, &dt, packet_num); uint8_t send_failed = 0; if (ret == 1) { if (!dt->sent_time) { if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, dt->data, dt->length) != 0) { send_failed = 1; } else { dt->sent_time = current_time_monotonic(); } } } if (!send_failed) { conn->maximum_speed_reached = 0; } else { return -1; } } return 0; } /* return -1 if data could not be put in packet queue. * return positive packet number if data was put into the queue. */ static int64_t send_lossless_packet(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length, uint8_t congestion_control) { if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) return -1; Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; /* If last packet send failed, try to send packet again. If sending it fails we won't be able to send the new packet. */ reset_max_speed_reached(c, crypt_connection_id); if (conn->maximum_speed_reached && congestion_control) { return -1; } Packet_Data dt; dt.sent_time = 0; dt.length = length; memcpy(dt.data, data, length); pthread_mutex_lock(&conn->mutex); int64_t packet_num = add_data_end_of_buffer(&conn->send_array, &dt); pthread_mutex_unlock(&conn->mutex); if (packet_num == -1) return -1; if (!congestion_control && conn->maximum_speed_reached) { return packet_num; } if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, data, length) == 0) { Packet_Data *dt1 = NULL; if (get_data_pointer(&conn->send_array, &dt1, packet_num) == 1) dt1->sent_time = current_time_monotonic(); } else { conn->maximum_speed_reached = 1; LOGGER_ERROR("send_data_packet failed\n"); } return packet_num; } /* Get the lowest 2 bytes from the nonce and convert * them to host byte format before returning them. */ static uint16_t get_nonce_uint16(const uint8_t *nonce) { uint16_t num; memcpy(&num, nonce + (crypto_box_NONCEBYTES - sizeof(uint16_t)), sizeof(uint16_t)); return ntohs(num); } #define DATA_NUM_THRESHOLD 21845 /* Handle a data packet. * Decrypt packet of length and put it into data. * data must be at least MAX_DATA_DATA_PACKET_SIZE big. * * return -1 on failure. * return length of data on success. */ static int handle_data_packet(const Net_Crypto *c, int crypt_connection_id, uint8_t *data, const uint8_t *packet, uint16_t length) { if (length <= (1 + sizeof(uint16_t) + crypto_box_MACBYTES) || length > MAX_CRYPTO_PACKET_SIZE) return -1; Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; uint8_t nonce[crypto_box_NONCEBYTES]; memcpy(nonce, conn->recv_nonce, crypto_box_NONCEBYTES); uint16_t num_cur_nonce = get_nonce_uint16(nonce); uint16_t num; memcpy(&num, packet + 1, sizeof(uint16_t)); num = ntohs(num); uint16_t diff = num - num_cur_nonce; increment_nonce_number(nonce, diff); int len = decrypt_data_symmetric(conn->shared_key, nonce, packet + 1 + sizeof(uint16_t), length - (1 + sizeof(uint16_t)), data); if ((unsigned int)len != length - (1 + sizeof(uint16_t) + crypto_box_MACBYTES)) return -1; if (diff > DATA_NUM_THRESHOLD * 2) { increment_nonce_number(conn->recv_nonce, DATA_NUM_THRESHOLD); } return len; } /* Send a request packet. * * return -1 on failure. * return 0 on success. */ static int send_request_packet(Net_Crypto *c, int crypt_connection_id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; uint8_t data[MAX_CRYPTO_DATA_SIZE]; int len = generate_request_packet(data, sizeof(data), &conn->recv_array); if (len == -1) return -1; return send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, conn->send_array.buffer_end, data, len); } /* Send up to max num previously requested data packets. * * return -1 on failure. * return number of packets sent on success. */ static int send_requested_packets(Net_Crypto *c, int crypt_connection_id, uint32_t max_num) { if (max_num == 0) return -1; Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; uint64_t temp_time = current_time_monotonic(); uint32_t i, num_sent = 0, array_size = num_packets_array(&conn->send_array); for (i = 0; i < array_size; ++i) { Packet_Data *dt; uint32_t packet_num = (i + conn->send_array.buffer_start); int ret = get_data_pointer(&conn->send_array, &dt, packet_num); if (ret == -1) { return -1; } else if (ret == 0) { continue; } if (dt->sent_time) { continue; } if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, dt->data, dt->length) == 0) { dt->sent_time = temp_time; ++num_sent; } if (num_sent >= max_num) break; } return num_sent; } /* Add a new temp packet to send repeatedly. * * return -1 on failure. * return 0 on success. */ static int new_temp_packet(const Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length) { if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) return -1; Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; uint8_t *temp_packet = malloc(length); if (temp_packet == 0) return -1; if (conn->temp_packet) free(conn->temp_packet); memcpy(temp_packet, packet, length); conn->temp_packet = temp_packet; conn->temp_packet_length = length; conn->temp_packet_sent_time = 0; conn->temp_packet_num_sent = 0; return 0; } /* Clear the temp packet. * * return -1 on failure. * return 0 on success. */ static int clear_temp_packet(const Net_Crypto *c, int crypt_connection_id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; if (conn->temp_packet) free(conn->temp_packet); conn->temp_packet = 0; conn->temp_packet_length = 0; conn->temp_packet_sent_time = 0; conn->temp_packet_num_sent = 0; return 0; } /* Send the temp packet. * * return -1 on failure. * return 0 on success. */ static int send_temp_packet(Net_Crypto *c, int crypt_connection_id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; if (!conn->temp_packet) return -1; if (send_packet_to(c, crypt_connection_id, conn->temp_packet, conn->temp_packet_length) != 0) return -1; conn->temp_packet_sent_time = current_time_monotonic(); ++conn->temp_packet_num_sent; return 0; } /* Create a handshake packet and set it as a temp packet. * cookie must be COOKIE_LENGTH. * * return -1 on failure. * return 0 on success. */ static int create_send_handshake(Net_Crypto *c, int crypt_connection_id, const uint8_t *cookie, const uint8_t *dht_public_key) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; uint8_t handshake_packet[HANDSHAKE_PACKET_LENGTH]; if (create_crypto_handshake(c, handshake_packet, cookie, conn->sent_nonce, conn->sessionpublic_key, conn->public_key, dht_public_key) != sizeof(handshake_packet)) return -1; if (new_temp_packet(c, crypt_connection_id, handshake_packet, sizeof(handshake_packet)) != 0) return -1; send_temp_packet(c, crypt_connection_id); return 0; } /* Send a kill packet. * * return -1 on failure. * return 0 on success. */ static int send_kill_packet(Net_Crypto *c, int crypt_connection_id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; uint8_t kill_packet = PACKET_ID_KILL; return send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, conn->send_array.buffer_end, &kill_packet, sizeof(kill_packet)); } static void connection_kill(Net_Crypto *c, int crypt_connection_id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return; if (conn->connection_status_callback) { conn->connection_status_callback(conn->connection_status_callback_object, conn->connection_status_callback_id, 0); } crypto_kill(c, crypt_connection_id); } /* Handle a received data packet. * * return -1 on failure. * return 0 on success. */ static int handle_data_packet_helper(Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length, _Bool udp) { if (length > MAX_CRYPTO_PACKET_SIZE || length <= CRYPTO_DATA_PACKET_MIN_SIZE) return -1; Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; uint8_t data[MAX_DATA_DATA_PACKET_SIZE]; int len = handle_data_packet(c, crypt_connection_id, data, packet, length); if (len <= (int)(sizeof(uint32_t) * 2)) return -1; uint32_t buffer_start, num; memcpy(&buffer_start, data, sizeof(uint32_t)); memcpy(&num, data + sizeof(uint32_t), sizeof(uint32_t)); buffer_start = ntohl(buffer_start); num = ntohl(num); uint64_t rtt_calc_time = 0; if (buffer_start != conn->send_array.buffer_start) { Packet_Data *packet_time; if (get_data_pointer(&conn->send_array, &packet_time, conn->send_array.buffer_start) == 1) { rtt_calc_time = packet_time->sent_time; } if (clear_buffer_until(&conn->send_array, buffer_start) != 0) { return -1; } } uint8_t *real_data = data + (sizeof(uint32_t) * 2); uint16_t real_length = len - (sizeof(uint32_t) * 2); while (real_data[0] == PACKET_ID_PADDING) { /* Remove Padding */ ++real_data; --real_length; if (real_length == 0) return -1; } if (real_data[0] == PACKET_ID_KILL) { connection_kill(c, crypt_connection_id); return 0; } if (conn->status == CRYPTO_CONN_NOT_CONFIRMED) { clear_temp_packet(c, crypt_connection_id); conn->status = CRYPTO_CONN_ESTABLISHED; if (conn->connection_status_callback) conn->connection_status_callback(conn->connection_status_callback_object, conn->connection_status_callback_id, 1); } if (real_data[0] == PACKET_ID_REQUEST) { uint64_t rtt_time; if (udp) { rtt_time = conn->rtt_time; } else { rtt_time = DEFAULT_TCP_PING_CONNECTION; } int requested = handle_request_packet(&conn->send_array, real_data, real_length, &rtt_calc_time, rtt_time); if (requested == -1) { return -1; } else { //TODO? } set_buffer_end(&conn->recv_array, num); } else if (real_data[0] >= CRYPTO_RESERVED_PACKETS && real_data[0] < PACKET_ID_LOSSY_RANGE_START) { Packet_Data dt; dt.length = real_length; memcpy(dt.data, real_data, real_length); if (add_data_to_buffer(&conn->recv_array, num, &dt) != 0) return -1; while (1) { pthread_mutex_lock(&conn->mutex); int ret = read_data_beg_buffer(&conn->recv_array, &dt); pthread_mutex_unlock(&conn->mutex); if (ret == -1) break; if (conn->connection_data_callback) conn->connection_data_callback(conn->connection_data_callback_object, conn->connection_data_callback_id, dt.data, dt.length); /* conn might get killed in callback. */ conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; } /* Packet counter. */ ++conn->packet_counter; } else if (real_data[0] >= PACKET_ID_LOSSY_RANGE_START && real_data[0] < (PACKET_ID_LOSSY_RANGE_START + PACKET_ID_LOSSY_RANGE_SIZE)) { set_buffer_end(&conn->recv_array, num); if (conn->connection_lossy_data_callback) conn->connection_lossy_data_callback(conn->connection_lossy_data_callback_object, conn->connection_lossy_data_callback_id, real_data, real_length); } else { return -1; } if (rtt_calc_time != 0) { uint64_t rtt_time = current_time_monotonic() - rtt_calc_time; if (rtt_time < conn->rtt_time) conn->rtt_time = rtt_time; } return 0; } /* Handle a packet that was received for the connection. * * return -1 on failure. * return 0 on success. */ static int handle_packet_connection(Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, uint16_t length, _Bool udp) { if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) return -1; Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; switch (packet[0]) { case NET_PACKET_COOKIE_RESPONSE: { if (conn->status != CRYPTO_CONN_COOKIE_REQUESTING) return -1; uint8_t cookie[COOKIE_LENGTH]; uint64_t number; if (handle_cookie_response(cookie, &number, packet, length, conn->shared_key) != sizeof(cookie)) return -1; if (number != conn->cookie_request_number) return -1; if (create_send_handshake(c, crypt_connection_id, cookie, conn->dht_public_key) != 0) return -1; conn->status = CRYPTO_CONN_HANDSHAKE_SENT; return 0; } case NET_PACKET_CRYPTO_HS: { if (conn->status == CRYPTO_CONN_COOKIE_REQUESTING || conn->status == CRYPTO_CONN_HANDSHAKE_SENT || conn->status == CRYPTO_CONN_NOT_CONFIRMED) { uint8_t peer_real_pk[crypto_box_PUBLICKEYBYTES]; uint8_t dht_public_key[crypto_box_PUBLICKEYBYTES]; uint8_t cookie[COOKIE_LENGTH]; if (handle_crypto_handshake(c, conn->recv_nonce, conn->peersessionpublic_key, peer_real_pk, dht_public_key, cookie, packet, length, conn->public_key) != 0) return -1; if (public_key_cmp(dht_public_key, conn->dht_public_key) == 0) { encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); if (conn->status == CRYPTO_CONN_COOKIE_REQUESTING) { if (create_send_handshake(c, crypt_connection_id, cookie, dht_public_key) != 0) return -1; } conn->status = CRYPTO_CONN_NOT_CONFIRMED; } else { if (conn->dht_pk_callback) conn->dht_pk_callback(conn->dht_pk_callback_object, conn->dht_pk_callback_number, dht_public_key); } } else { return -1; } return 0; } case NET_PACKET_CRYPTO_DATA: { if (conn->status == CRYPTO_CONN_NOT_CONFIRMED || conn->status == CRYPTO_CONN_ESTABLISHED) { return handle_data_packet_helper(c, crypt_connection_id, packet, length, udp); } else { return -1; } return 0; } default: { return -1; } } return 0; } /* Set the size of the friend list to numfriends. * * return -1 if realloc fails. * return 0 if it succeeds. */ static int realloc_cryptoconnection(Net_Crypto *c, uint32_t num) { if (num == 0) { free(c->crypto_connections); c->crypto_connections = NULL; return 0; } Crypto_Connection *newcrypto_connections = realloc(c->crypto_connections, num * sizeof(Crypto_Connection)); if (newcrypto_connections == NULL) return -1; c->crypto_connections = newcrypto_connections; return 0; } /* Create a new empty crypto connection. * * return -1 on failure. * return connection id on success. */ static int create_crypto_connection(Net_Crypto *c) { uint32_t i; for (i = 0; i < c->crypto_connections_length; ++i) { if (c->crypto_connections[i].status == CRYPTO_CONN_NO_CONNECTION) return i; } while (1) { /* TODO: is this really the best way to do this? */ pthread_mutex_lock(&c->connections_mutex); if (!c->connection_use_counter) { break; } pthread_mutex_unlock(&c->connections_mutex); } int id = -1; if (realloc_cryptoconnection(c, c->crypto_connections_length + 1) == 0) { id = c->crypto_connections_length; ++c->crypto_connections_length; memset(&(c->crypto_connections[id]), 0, sizeof(Crypto_Connection)); if (pthread_mutex_init(&c->crypto_connections[id].mutex, NULL) != 0) { pthread_mutex_unlock(&c->connections_mutex); return -1; } } pthread_mutex_unlock(&c->connections_mutex); return id; } /* Wipe a crypto connection. * * return -1 on failure. * return 0 on success. */ static int wipe_crypto_connection(Net_Crypto *c, int crypt_connection_id) { if (crypt_connection_id_not_valid(c, crypt_connection_id)) return -1; uint32_t i; /* Keep mutex, only destroy it when connection is realloced out. */ pthread_mutex_t mutex = c->crypto_connections[crypt_connection_id].mutex; sodium_memzero(&(c->crypto_connections[crypt_connection_id]), sizeof(Crypto_Connection)); c->crypto_connections[crypt_connection_id].mutex = mutex; for (i = c->crypto_connections_length; i != 0; --i) { if (c->crypto_connections[i - 1].status == CRYPTO_CONN_NO_CONNECTION) { pthread_mutex_destroy(&c->crypto_connections[i - 1].mutex); } else { break; } } if (c->crypto_connections_length != i) { c->crypto_connections_length = i; realloc_cryptoconnection(c, c->crypto_connections_length); } return 0; } /* Get crypto connection id from public key of peer. * * return -1 if there are no connections like we are looking for. * return id if it found it. */ static int getcryptconnection_id(const Net_Crypto *c, const uint8_t *public_key) { uint32_t i; for (i = 0; i < c->crypto_connections_length; ++i) { if (c->crypto_connections[i].status != CRYPTO_CONN_NO_CONNECTION) if (public_key_cmp(public_key, c->crypto_connections[i].public_key) == 0) return i; } return -1; } /* Add a source to the crypto connection. * This is to be used only when we have received a packet from that source. * * return -1 on failure. * return positive number on success. * 0 if source was a direct UDP connection. */ static int crypto_connection_add_source(Net_Crypto *c, int crypt_connection_id, IP_Port source) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; if (source.ip.family == AF_INET || source.ip.family == AF_INET6) { if (add_ip_port_connection(c, crypt_connection_id, source) != 0) return -1; if (source.ip.family == AF_INET) { conn->direct_lastrecv_timev4 = unix_time(); } else { conn->direct_lastrecv_timev6 = unix_time(); } return 0; } else if (source.ip.family == TCP_FAMILY) { if (add_tcp_number_relay_connection(c->tcp_c, conn->connection_number_tcp, source.ip.ip6.uint32[0]) == 0) return 1; } return -1; } /* Set function to be called when someone requests a new connection to us. * * The set function should return -1 on failure and 0 on success. * * n_c is only valid for the duration of the function call. */ void new_connection_handler(Net_Crypto *c, int (*new_connection_callback)(void *object, New_Connection *n_c), void *object) { c->new_connection_callback = new_connection_callback; c->new_connection_callback_object = object; } /* Handle a handshake packet by someone who wants to initiate a new connection with us. * This calls the callback set by new_connection_handler() if the handshake is ok. * * return -1 on failure. * return 0 on success. */ static int handle_new_connection_handshake(Net_Crypto *c, IP_Port source, const uint8_t *data, uint16_t length) { New_Connection n_c; n_c.cookie = malloc(COOKIE_LENGTH); if (n_c.cookie == NULL) return -1; n_c.source = source; n_c.cookie_length = COOKIE_LENGTH; if (handle_crypto_handshake(c, n_c.recv_nonce, n_c.peersessionpublic_key, n_c.public_key, n_c.dht_public_key, n_c.cookie, data, length, 0) != 0) { free(n_c.cookie); return -1; } int crypt_connection_id = getcryptconnection_id(c, n_c.public_key); if (crypt_connection_id != -1) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (public_key_cmp(n_c.dht_public_key, conn->dht_public_key) != 0) { connection_kill(c, crypt_connection_id); } else { int ret = -1; if (conn && (conn->status == CRYPTO_CONN_COOKIE_REQUESTING || conn->status == CRYPTO_CONN_HANDSHAKE_SENT)) { memcpy(conn->recv_nonce, n_c.recv_nonce, crypto_box_NONCEBYTES); memcpy(conn->peersessionpublic_key, n_c.peersessionpublic_key, crypto_box_PUBLICKEYBYTES); encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); crypto_connection_add_source(c, crypt_connection_id, source); if (create_send_handshake(c, crypt_connection_id, n_c.cookie, n_c.dht_public_key) == 0) { conn->status = CRYPTO_CONN_NOT_CONFIRMED; ret = 0; } } free(n_c.cookie); return ret; } } int ret = c->new_connection_callback(c->new_connection_callback_object, &n_c); free(n_c.cookie); return ret; } /* Accept a crypto connection. * * return -1 on failure. * return connection id on success. */ int accept_crypto_connection(Net_Crypto *c, New_Connection *n_c) { if (getcryptconnection_id(c, n_c->public_key) != -1) return -1; int crypt_connection_id = create_crypto_connection(c); if (crypt_connection_id == -1) return -1; Crypto_Connection *conn = &c->crypto_connections[crypt_connection_id]; if (n_c->cookie_length != COOKIE_LENGTH) return -1; pthread_mutex_lock(&c->tcp_mutex); int connection_number_tcp = new_tcp_connection_to(c->tcp_c, n_c->dht_public_key, crypt_connection_id); pthread_mutex_unlock(&c->tcp_mutex); if (connection_number_tcp == -1) return -1; conn->connection_number_tcp = connection_number_tcp; memcpy(conn->public_key, n_c->public_key, crypto_box_PUBLICKEYBYTES); memcpy(conn->recv_nonce, n_c->recv_nonce, crypto_box_NONCEBYTES); memcpy(conn->peersessionpublic_key, n_c->peersessionpublic_key, crypto_box_PUBLICKEYBYTES); random_nonce(conn->sent_nonce); crypto_box_keypair(conn->sessionpublic_key, conn->sessionsecret_key); encrypt_precompute(conn->peersessionpublic_key, conn->sessionsecret_key, conn->shared_key); conn->status = CRYPTO_CONN_NOT_CONFIRMED; if (create_send_handshake(c, crypt_connection_id, n_c->cookie, n_c->dht_public_key) != 0) { pthread_mutex_lock(&c->tcp_mutex); kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); pthread_mutex_unlock(&c->tcp_mutex); conn->status = CRYPTO_CONN_NO_CONNECTION; return -1; } memcpy(conn->dht_public_key, n_c->dht_public_key, crypto_box_PUBLICKEYBYTES); conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; conn->packet_send_rate_requested = CRYPTO_PACKET_MIN_RATE; conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; conn->rtt_time = DEFAULT_PING_CONNECTION; crypto_connection_add_source(c, crypt_connection_id, n_c->source); return crypt_connection_id; } /* Create a crypto connection. * If one to that real public key already exists, return it. * * return -1 on failure. * return connection id on success. */ int new_crypto_connection(Net_Crypto *c, const uint8_t *real_public_key, const uint8_t *dht_public_key) { int crypt_connection_id = getcryptconnection_id(c, real_public_key); if (crypt_connection_id != -1) return crypt_connection_id; crypt_connection_id = create_crypto_connection(c); if (crypt_connection_id == -1) return -1; Crypto_Connection *conn = &c->crypto_connections[crypt_connection_id]; if (conn == 0) return -1; pthread_mutex_lock(&c->tcp_mutex); int connection_number_tcp = new_tcp_connection_to(c->tcp_c, dht_public_key, crypt_connection_id); pthread_mutex_unlock(&c->tcp_mutex); if (connection_number_tcp == -1) return -1; conn->connection_number_tcp = connection_number_tcp; memcpy(conn->public_key, real_public_key, crypto_box_PUBLICKEYBYTES); random_nonce(conn->sent_nonce); crypto_box_keypair(conn->sessionpublic_key, conn->sessionsecret_key); conn->status = CRYPTO_CONN_COOKIE_REQUESTING; conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; conn->packet_send_rate_requested = CRYPTO_PACKET_MIN_RATE; conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; conn->rtt_time = DEFAULT_PING_CONNECTION; memcpy(conn->dht_public_key, dht_public_key, crypto_box_PUBLICKEYBYTES); conn->cookie_request_number = random_64b(); uint8_t cookie_request[COOKIE_REQUEST_LENGTH]; if (create_cookie_request(c, cookie_request, conn->dht_public_key, conn->cookie_request_number, conn->shared_key) != sizeof(cookie_request) || new_temp_packet(c, crypt_connection_id, cookie_request, sizeof(cookie_request)) != 0) { pthread_mutex_lock(&c->tcp_mutex); kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); pthread_mutex_unlock(&c->tcp_mutex); conn->status = CRYPTO_CONN_NO_CONNECTION; return -1; } return crypt_connection_id; } /* Set the direct ip of the crypto connection. * * Connected is 0 if we are not sure we are connected to that person, 1 if we are sure. * * return -1 on failure. * return 0 on success. */ int set_direct_ip_port(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port, _Bool connected) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; if (add_ip_port_connection(c, crypt_connection_id, ip_port) == 0) { if (connected) { if (ip_port.ip.family == AF_INET) { conn->direct_lastrecv_timev4 = unix_time(); } else { conn->direct_lastrecv_timev6 = unix_time(); } } else { if (ip_port.ip.family == AF_INET) { conn->direct_lastrecv_timev4 = 0; } else { conn->direct_lastrecv_timev6 = 0; } } return 0; } return -1; } static int tcp_data_callback(void *object, int id, const uint8_t *data, uint16_t length) { if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) return -1; Net_Crypto *c = object; Crypto_Connection *conn = get_crypto_connection(c, id); if (conn == 0) return -1; if (data[0] == NET_PACKET_COOKIE_REQUEST) { return tcp_handle_cookie_request(c, conn->connection_number_tcp, data, length); } pthread_mutex_unlock(&c->tcp_mutex); int ret = handle_packet_connection(c, id, data, length, 0); pthread_mutex_lock(&c->tcp_mutex); if (ret != 0) return -1; //TODO detect and kill bad TCP connections. return 0; } static int tcp_oob_callback(void *object, const uint8_t *public_key, unsigned int tcp_connections_number, const uint8_t *data, uint16_t length) { if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) return -1; Net_Crypto *c = object; if (data[0] == NET_PACKET_COOKIE_REQUEST) { return tcp_oob_handle_cookie_request(c, tcp_connections_number, public_key, data, length); } else if (data[0] == NET_PACKET_CRYPTO_HS) { IP_Port source; source.port = 0; source.ip.family = TCP_FAMILY; source.ip.ip6.uint32[0] = tcp_connections_number; if (handle_new_connection_handshake(c, source, data, length) != 0) return -1; return 0; } else { return -1; } } /* Add a tcp relay, associating it to a crypt_connection_id. * * return 0 if it was added. * return -1 if it wasn't. */ int add_tcp_relay_peer(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port, const uint8_t *public_key) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; pthread_mutex_lock(&c->tcp_mutex); int ret = add_tcp_relay_connection(c->tcp_c, conn->connection_number_tcp, ip_port, public_key); pthread_mutex_unlock(&c->tcp_mutex); return ret; } /* Add a tcp relay to the array. * * return 0 if it was added. * return -1 if it wasn't. */ int add_tcp_relay(Net_Crypto *c, IP_Port ip_port, const uint8_t *public_key) { pthread_mutex_lock(&c->tcp_mutex); int ret = add_tcp_relay_global(c->tcp_c, ip_port, public_key); pthread_mutex_unlock(&c->tcp_mutex); return ret; } /* 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_con_number(Net_Crypto *c) { pthread_mutex_lock(&c->tcp_mutex); int ret = get_random_tcp_onion_conn_number(c->tcp_c); pthread_mutex_unlock(&c->tcp_mutex); return ret; } /* Send an onion packet via the TCP relay corresponding to tcp_connections_number. * * return 0 on success. * return -1 on failure. */ int send_tcp_onion_request(Net_Crypto *c, unsigned int tcp_connections_number, const uint8_t *data, uint16_t length) { pthread_mutex_lock(&c->tcp_mutex); int ret = tcp_send_onion_request(c->tcp_c, tcp_connections_number, data, length); pthread_mutex_unlock(&c->tcp_mutex); return ret; } /* Copy a maximum of 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 copy_connected_tcp_relays(Net_Crypto *c, Node_format *tcp_relays, uint16_t num) { if (num == 0) return 0; pthread_mutex_lock(&c->tcp_mutex); unsigned int ret = tcp_copy_connected_relays(c->tcp_c, tcp_relays, num); pthread_mutex_unlock(&c->tcp_mutex); return ret; } static void do_tcp(Net_Crypto *c) { pthread_mutex_lock(&c->tcp_mutex); do_tcp_connections(c->tcp_c); pthread_mutex_unlock(&c->tcp_mutex); uint32_t i; for (i = 0; i < c->crypto_connections_length; ++i) { Crypto_Connection *conn = get_crypto_connection(c, i); if (conn == 0) return; if (conn->status == CRYPTO_CONN_ESTABLISHED) { _Bool direct_connected = 0; crypto_connection_status(c, i, &direct_connected, NULL); if (direct_connected) { pthread_mutex_lock(&c->tcp_mutex); set_tcp_connection_to_status(c->tcp_c, conn->connection_number_tcp, 0); pthread_mutex_unlock(&c->tcp_mutex); } else { pthread_mutex_lock(&c->tcp_mutex); set_tcp_connection_to_status(c->tcp_c, conn->connection_number_tcp, 1); pthread_mutex_unlock(&c->tcp_mutex); } } } } /* Set function to be called when connection with crypt_connection_id goes connects/disconnects. * * The set function should return -1 on failure and 0 on success. * Note that if this function is set, the connection will clear itself on disconnect. * Object and id will be passed to this function untouched. * status is 1 if the connection is going online, 0 if it is going offline. * * return -1 on failure. * return 0 on success. */ int connection_status_handler(const Net_Crypto *c, int crypt_connection_id, int (*connection_status_callback)(void *object, int id, uint8_t status), void *object, int id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; conn->connection_status_callback = connection_status_callback; conn->connection_status_callback_object = object; conn->connection_status_callback_id = id; return 0; } /* Set function to be called when connection with crypt_connection_id receives a data packet of length. * * The set function should return -1 on failure and 0 on success. * Object and id will be passed to this function untouched. * * return -1 on failure. * return 0 on success. */ int connection_data_handler(const Net_Crypto *c, int crypt_connection_id, int (*connection_data_callback)(void *object, int id, uint8_t *data, uint16_t length), void *object, int id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; conn->connection_data_callback = connection_data_callback; conn->connection_data_callback_object = object; conn->connection_data_callback_id = id; return 0; } /* Set function to be called when connection with crypt_connection_id receives a lossy data packet of length. * * The set function should return -1 on failure and 0 on success. * Object and id will be passed to this function untouched. * * return -1 on failure. * return 0 on success. */ int connection_lossy_data_handler(Net_Crypto *c, int crypt_connection_id, int (*connection_lossy_data_callback)(void *object, int id, const uint8_t *data, uint16_t length), void *object, int id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; conn->connection_lossy_data_callback = connection_lossy_data_callback; conn->connection_lossy_data_callback_object = object; conn->connection_lossy_data_callback_id = id; return 0; } /* Set the function for this friend that will be callbacked with object and number if * the friend sends us a different dht public key than we have associated to him. * * If this function is called, the connection should be recreated with the new public key. * * object and number will be passed as argument to this function. * * return -1 on failure. * return 0 on success. */ int nc_dht_pk_callback(Net_Crypto *c, int crypt_connection_id, void (*function)(void *data, int32_t number, const uint8_t *dht_public_key), void *object, uint32_t number) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; conn->dht_pk_callback = function; conn->dht_pk_callback_object = object; conn->dht_pk_callback_number = number; return 0; } /* Get the crypto connection id from the ip_port. * * return -1 on failure. * return connection id on success. */ static int crypto_id_ip_port(const Net_Crypto *c, IP_Port ip_port) { return bs_list_find(&c->ip_port_list, (uint8_t *)&ip_port); } #define CRYPTO_MIN_PACKET_SIZE (1 + sizeof(uint16_t) + crypto_box_MACBYTES) /* Handle raw UDP packets coming directly from the socket. * * Handles: * Cookie response packets. * Crypto handshake packets. * Crypto data packets. * */ static int udp_handle_packet(void *object, IP_Port source, const uint8_t *packet, uint16_t length) { if (length <= CRYPTO_MIN_PACKET_SIZE || length > MAX_CRYPTO_PACKET_SIZE) return 1; Net_Crypto *c = object; int crypt_connection_id = crypto_id_ip_port(c, source); if (crypt_connection_id == -1) { if (packet[0] != NET_PACKET_CRYPTO_HS) return 1; if (handle_new_connection_handshake(c, source, packet, length) != 0) return 1; return 0; } if (handle_packet_connection(c, crypt_connection_id, packet, length, 1) != 0) return 1; Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; pthread_mutex_lock(&conn->mutex); if (source.ip.family == AF_INET) { conn->direct_lastrecv_timev4 = unix_time(); } else { conn->direct_lastrecv_timev6 = unix_time(); } pthread_mutex_unlock(&conn->mutex); return 0; } /* The dT for the average packet receiving rate calculations. Also used as the */ #define PACKET_COUNTER_AVERAGE_INTERVAL 50 /* Ratio of recv queue size / recv packet rate (in seconds) times * the number of ms between request packets to send at that ratio */ #define REQUEST_PACKETS_COMPARE_CONSTANT (0.125 * 100.0) /* Timeout for increasing speed after congestion event (in ms). */ #define CONGESTION_EVENT_TIMEOUT 1000 /* If the send queue is SEND_QUEUE_RATIO times larger than the * calculated link speed the packet send speed will be reduced * by a value depending on this number. */ #define SEND_QUEUE_RATIO 2.0 static void send_crypto_packets(Net_Crypto *c) { uint32_t i; uint64_t temp_time = current_time_monotonic(); double total_send_rate = 0; uint32_t peak_request_packet_interval = ~0; for (i = 0; i < c->crypto_connections_length; ++i) { Crypto_Connection *conn = get_crypto_connection(c, i); if (conn == 0) return; if (CRYPTO_SEND_PACKET_INTERVAL + conn->temp_packet_sent_time < temp_time) { send_temp_packet(c, i); } if ((conn->status == CRYPTO_CONN_NOT_CONFIRMED || conn->status == CRYPTO_CONN_ESTABLISHED) && ((CRYPTO_SEND_PACKET_INTERVAL) + conn->last_request_packet_sent) < temp_time) { if (send_request_packet(c, i) == 0) { conn->last_request_packet_sent = temp_time; } } if (conn->status == CRYPTO_CONN_ESTABLISHED) { if (conn->packet_recv_rate > CRYPTO_PACKET_MIN_RATE) { double request_packet_interval = (REQUEST_PACKETS_COMPARE_CONSTANT / (((double)num_packets_array( &conn->recv_array) + 1.0) / (conn->packet_recv_rate + 1.0))); double request_packet_interval2 = ((CRYPTO_PACKET_MIN_RATE / conn->packet_recv_rate) * (double)CRYPTO_SEND_PACKET_INTERVAL) + (double)PACKET_COUNTER_AVERAGE_INTERVAL; if (request_packet_interval2 < request_packet_interval) request_packet_interval = request_packet_interval2; if (request_packet_interval < PACKET_COUNTER_AVERAGE_INTERVAL) request_packet_interval = PACKET_COUNTER_AVERAGE_INTERVAL; if (request_packet_interval > CRYPTO_SEND_PACKET_INTERVAL) request_packet_interval = CRYPTO_SEND_PACKET_INTERVAL; if (temp_time - conn->last_request_packet_sent > (uint64_t)request_packet_interval) { if (send_request_packet(c, i) == 0) { conn->last_request_packet_sent = temp_time; } } if (request_packet_interval < peak_request_packet_interval) { peak_request_packet_interval = request_packet_interval; } } if ((PACKET_COUNTER_AVERAGE_INTERVAL + conn->packet_counter_set) < temp_time) { double dt = temp_time - conn->packet_counter_set; conn->packet_recv_rate = (double)conn->packet_counter / (dt / 1000.0); conn->packet_counter = 0; conn->packet_counter_set = temp_time; uint32_t packets_sent = conn->packets_sent; conn->packets_sent = 0; uint32_t packets_resent = conn->packets_resent; conn->packets_resent = 0; /* conjestion control calculate a new value of conn->packet_send_rate based on some data */ unsigned int pos = conn->last_sendqueue_counter % CONGESTION_QUEUE_ARRAY_SIZE; conn->last_sendqueue_size[pos] = num_packets_array(&conn->send_array); ++conn->last_sendqueue_counter; unsigned int j; long signed int sum = 0; sum = (long signed int)conn->last_sendqueue_size[(pos) % CONGESTION_QUEUE_ARRAY_SIZE] - (long signed int)conn->last_sendqueue_size[(pos - (CONGESTION_QUEUE_ARRAY_SIZE - 1)) % CONGESTION_QUEUE_ARRAY_SIZE]; unsigned int n_p_pos = conn->last_sendqueue_counter % CONGESTION_LAST_SENT_ARRAY_SIZE; conn->last_num_packets_sent[n_p_pos] = packets_sent; conn->last_num_packets_resent[n_p_pos] = packets_resent; _Bool direct_connected = 0; crypto_connection_status(c, i, &direct_connected, NULL); if (direct_connected && conn->last_tcp_sent + CONGESTION_EVENT_TIMEOUT > temp_time) { /* When switching from TCP to UDP, don't change the packet send rate for CONGESTION_EVENT_TIMEOUT ms. */ } else { long signed int total_sent = 0, total_resent = 0; //TODO use real delay unsigned int delay = (unsigned int)((conn->rtt_time / PACKET_COUNTER_AVERAGE_INTERVAL) + 0.5); unsigned int packets_set_rem_array = (CONGESTION_LAST_SENT_ARRAY_SIZE - CONGESTION_QUEUE_ARRAY_SIZE); if (delay > packets_set_rem_array) { delay = packets_set_rem_array; } for (j = 0; j < CONGESTION_QUEUE_ARRAY_SIZE; ++j) { unsigned int ind = (j + (packets_set_rem_array - delay) + n_p_pos) % CONGESTION_LAST_SENT_ARRAY_SIZE; total_sent += conn->last_num_packets_sent[ind]; total_resent += conn->last_num_packets_resent[ind]; } if (sum > 0) { total_sent -= sum; } else { if (total_resent > -sum) total_resent = -sum; } /* if queue is too big only allow resending packets. */ uint32_t npackets = num_packets_array(&conn->send_array); double min_speed = 1000.0 * (((double)(total_sent)) / ((double)(CONGESTION_QUEUE_ARRAY_SIZE) * PACKET_COUNTER_AVERAGE_INTERVAL)); double min_speed_request = 1000.0 * (((double)(total_sent + total_resent)) / ((double)( CONGESTION_QUEUE_ARRAY_SIZE) * PACKET_COUNTER_AVERAGE_INTERVAL)); if (min_speed < CRYPTO_PACKET_MIN_RATE) min_speed = CRYPTO_PACKET_MIN_RATE; double send_array_ratio = (((double)npackets) / min_speed); //TODO: Improve formula? if (send_array_ratio > SEND_QUEUE_RATIO && CRYPTO_MIN_QUEUE_LENGTH < npackets) { conn->packet_send_rate = min_speed * (1.0 / (send_array_ratio / SEND_QUEUE_RATIO)); } else if (conn->last_congestion_event + CONGESTION_EVENT_TIMEOUT < temp_time) { conn->packet_send_rate = min_speed * 1.2; } else { conn->packet_send_rate = min_speed * 0.9; } conn->packet_send_rate_requested = min_speed_request * 1.2; if (conn->packet_send_rate < CRYPTO_PACKET_MIN_RATE) { conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; } if (conn->packet_send_rate_requested < conn->packet_send_rate) { conn->packet_send_rate_requested = conn->packet_send_rate; } } } if (conn->last_packets_left_set == 0 || conn->last_packets_left_requested_set == 0) { conn->last_packets_left_requested_set = conn->last_packets_left_set = temp_time; conn->packets_left_requested = conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; } else { if (((uint64_t)((1000.0 / conn->packet_send_rate) + 0.5) + conn->last_packets_left_set) <= temp_time) { double n_packets = conn->packet_send_rate * (((double)(temp_time - conn->last_packets_left_set)) / 1000.0); n_packets += conn->last_packets_left_rem; uint32_t num_packets = n_packets; double rem = n_packets - (double)num_packets; if (conn->packets_left > num_packets * 4 + CRYPTO_MIN_QUEUE_LENGTH) { conn->packets_left = num_packets * 4 + CRYPTO_MIN_QUEUE_LENGTH; } else { conn->packets_left += num_packets; } conn->last_packets_left_set = temp_time; conn->last_packets_left_rem = rem; } if (((uint64_t)((1000.0 / conn->packet_send_rate_requested) + 0.5) + conn->last_packets_left_requested_set) <= temp_time) { double n_packets = conn->packet_send_rate_requested * (((double)(temp_time - conn->last_packets_left_requested_set)) / 1000.0); n_packets += conn->last_packets_left_requested_rem; uint32_t num_packets = n_packets; double rem = n_packets - (double)num_packets; conn->packets_left_requested = num_packets; conn->last_packets_left_requested_set = temp_time; conn->last_packets_left_requested_rem = rem; } if (conn->packets_left > conn->packets_left_requested) conn->packets_left_requested = conn->packets_left; } int ret = send_requested_packets(c, i, conn->packets_left_requested); if (ret != -1) { conn->packets_left_requested -= ret; conn->packets_resent += ret; if ((unsigned int)ret < conn->packets_left) { conn->packets_left -= ret; } else { conn->last_congestion_event = temp_time; conn->packets_left = 0; } } if (conn->packet_send_rate > CRYPTO_PACKET_MIN_RATE * 1.5) { total_send_rate += conn->packet_send_rate; } } } c->current_sleep_time = ~0; uint32_t sleep_time = peak_request_packet_interval; if (c->current_sleep_time > sleep_time) { c->current_sleep_time = sleep_time; } if (total_send_rate > CRYPTO_PACKET_MIN_RATE) { sleep_time = (1000.0 / total_send_rate); if (c->current_sleep_time > sleep_time) { c->current_sleep_time = sleep_time + 1; } } sleep_time = CRYPTO_SEND_PACKET_INTERVAL; if (c->current_sleep_time > sleep_time) { c->current_sleep_time = sleep_time; } } /* Return 1 if max speed was reached for this connection (no more data can be physically through the pipe). * Return 0 if it wasn't reached. */ _Bool max_speed_reached(Net_Crypto *c, int crypt_connection_id) { return reset_max_speed_reached(c, crypt_connection_id) != 0; } /* returns the number of packet slots left in the sendbuffer. * return 0 if failure. */ uint32_t crypto_num_free_sendqueue_slots(const Net_Crypto *c, int crypt_connection_id) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return 0; uint32_t max_packets = CRYPTO_PACKET_BUFFER_SIZE - num_packets_array(&conn->send_array); if (conn->packets_left < max_packets) { return conn->packets_left; } else { return max_packets; } } /* Sends a lossless cryptopacket. * * return -1 if data could not be put in packet queue. * return positive packet number if data was put into the queue. * * congestion_control: should congestion control apply to this packet? */ int64_t write_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length, uint8_t congestion_control) { if (length == 0) return -1; if (data[0] < CRYPTO_RESERVED_PACKETS) return -1; if (data[0] >= PACKET_ID_LOSSY_RANGE_START) return -1; Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; if (conn->status != CRYPTO_CONN_ESTABLISHED) return -1; if (congestion_control && conn->packets_left == 0) return -1; int64_t ret = send_lossless_packet(c, crypt_connection_id, data, length, congestion_control); if (ret == -1) return -1; if (congestion_control) { --conn->packets_left; --conn->packets_left_requested; conn->packets_sent++; } return ret; } /* Check if packet_number was received by the other side. * * packet_number must be a valid packet number of a packet sent on this connection. * * return -1 on failure. * return 0 on success. */ int cryptpacket_received(Net_Crypto *c, int crypt_connection_id, uint32_t packet_number) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return -1; uint32_t num = conn->send_array.buffer_end - conn->send_array.buffer_start; uint32_t num1 = packet_number - conn->send_array.buffer_start; if (num < num1) { return 0; } else { return -1; } } /* return -1 on failure. * return 0 on success. * * Sends a lossy cryptopacket. (first byte must in the PACKET_ID_LOSSY_RANGE_*) */ int send_lossy_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length) { if (length == 0 || length > MAX_CRYPTO_DATA_SIZE) return -1; if (data[0] < PACKET_ID_LOSSY_RANGE_START) return -1; if (data[0] >= (PACKET_ID_LOSSY_RANGE_START + PACKET_ID_LOSSY_RANGE_SIZE)) return -1; pthread_mutex_lock(&c->connections_mutex); ++c->connection_use_counter; pthread_mutex_unlock(&c->connections_mutex); Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); int ret = -1; if (conn) { pthread_mutex_lock(&conn->mutex); uint32_t buffer_start = conn->recv_array.buffer_start; uint32_t buffer_end = conn->send_array.buffer_end; pthread_mutex_unlock(&conn->mutex); ret = send_data_packet_helper(c, crypt_connection_id, buffer_start, buffer_end, data, length); } pthread_mutex_lock(&c->connections_mutex); --c->connection_use_counter; pthread_mutex_unlock(&c->connections_mutex); return ret; } /* Kill a crypto connection. * * return -1 on failure. * return 0 on success. */ int crypto_kill(Net_Crypto *c, int crypt_connection_id) { while (1) { /* TODO: is this really the best way to do this? */ pthread_mutex_lock(&c->connections_mutex); if (!c->connection_use_counter) { break; } pthread_mutex_unlock(&c->connections_mutex); } Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); int ret = -1; if (conn) { if (conn->status == CRYPTO_CONN_ESTABLISHED) send_kill_packet(c, crypt_connection_id); pthread_mutex_lock(&c->tcp_mutex); kill_tcp_connection_to(c->tcp_c, conn->connection_number_tcp); pthread_mutex_unlock(&c->tcp_mutex); bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv4, crypt_connection_id); bs_list_remove(&c->ip_port_list, (uint8_t *)&conn->ip_portv6, crypt_connection_id); clear_temp_packet(c, crypt_connection_id); clear_buffer(&conn->send_array); clear_buffer(&conn->recv_array); ret = wipe_crypto_connection(c, crypt_connection_id); } pthread_mutex_unlock(&c->connections_mutex); return ret; } /* return one of CRYPTO_CONN_* values indicating the state of the connection. * * sets direct_connected to 1 if connection connects directly to other, 0 if it isn't. * sets online_tcp_relays to the number of connected tcp relays this connection has. */ unsigned int crypto_connection_status(const Net_Crypto *c, int crypt_connection_id, _Bool *direct_connected, unsigned int *online_tcp_relays) { Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); if (conn == 0) return CRYPTO_CONN_NO_CONNECTION; if (direct_connected) { *direct_connected = 0; uint64_t current_time = unix_time(); if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev4) > current_time) *direct_connected = 1; if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_timev6) > current_time) *direct_connected = 1; } if (online_tcp_relays) { *online_tcp_relays = tcp_connection_to_online_tcp_relays(c->tcp_c, conn->connection_number_tcp); } return conn->status; } void new_keys(Net_Crypto *c) { crypto_box_keypair(c->self_public_key, c->self_secret_key); } /* Save the public and private keys to the keys array. * Length must be crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES. * * TODO: Save only secret key. */ void save_keys(const Net_Crypto *c, uint8_t *keys) { memcpy(keys, c->self_public_key, crypto_box_PUBLICKEYBYTES); memcpy(keys + crypto_box_PUBLICKEYBYTES, c->self_secret_key, crypto_box_SECRETKEYBYTES); } /* Load the secret key. * Length must be crypto_box_SECRETKEYBYTES. */ void load_secret_key(Net_Crypto *c, const uint8_t *sk) { memcpy(c->self_secret_key, sk, crypto_box_SECRETKEYBYTES); crypto_scalarmult_curve25519_base(c->self_public_key, c->self_secret_key); } /* Run this to (re)initialize net_crypto. * Sets all the global connection variables to their default values. */ Net_Crypto *new_net_crypto(DHT *dht, TCP_Proxy_Info *proxy_info) { unix_time_update(); if (dht == NULL) return NULL; Net_Crypto *temp = calloc(1, sizeof(Net_Crypto)); if (temp == NULL) return NULL; temp->tcp_c = new_tcp_connections(dht->self_secret_key, proxy_info); if (temp->tcp_c == NULL) { free(temp); return NULL; } set_packet_tcp_connection_callback(temp->tcp_c, &tcp_data_callback, temp); set_oob_packet_tcp_connection_callback(temp->tcp_c, &tcp_oob_callback, temp); if (create_recursive_mutex(&temp->tcp_mutex) != 0 || pthread_mutex_init(&temp->connections_mutex, NULL) != 0) { kill_tcp_connections(temp->tcp_c); free(temp); return NULL; } temp->dht = dht; new_keys(temp); new_symmetric_key(temp->secret_symmetric_key); temp->current_sleep_time = CRYPTO_SEND_PACKET_INTERVAL; networking_registerhandler(dht->net, NET_PACKET_COOKIE_REQUEST, &udp_handle_cookie_request, temp); networking_registerhandler(dht->net, NET_PACKET_COOKIE_RESPONSE, &udp_handle_packet, temp); networking_registerhandler(dht->net, NET_PACKET_CRYPTO_HS, &udp_handle_packet, temp); networking_registerhandler(dht->net, NET_PACKET_CRYPTO_DATA, &udp_handle_packet, temp); bs_list_init(&temp->ip_port_list, sizeof(IP_Port), 8); return temp; } static void kill_timedout(Net_Crypto *c) { uint32_t i; //uint64_t temp_time = current_time_monotonic(); for (i = 0; i < c->crypto_connections_length; ++i) { Crypto_Connection *conn = get_crypto_connection(c, i); if (conn == 0) return; if (conn->status == CRYPTO_CONN_NO_CONNECTION) continue; if (conn->status == CRYPTO_CONN_COOKIE_REQUESTING || conn->status == CRYPTO_CONN_HANDSHAKE_SENT || conn->status == CRYPTO_CONN_NOT_CONFIRMED) { if (conn->temp_packet_num_sent < MAX_NUM_SENDPACKET_TRIES) continue; connection_kill(c, i); } if (conn->status == CRYPTO_CONN_ESTABLISHED) { //TODO: add a timeout here? } } } /* return the optimal interval in ms for running do_net_crypto. */ uint32_t crypto_run_interval(const Net_Crypto *c) { return c->current_sleep_time; } /* Main loop. */ void do_net_crypto(Net_Crypto *c) { unix_time_update(); kill_timedout(c); do_tcp(c); send_crypto_packets(c); } void kill_net_crypto(Net_Crypto *c) { uint32_t i; for (i = 0; i < c->crypto_connections_length; ++i) { crypto_kill(c, i); } pthread_mutex_destroy(&c->tcp_mutex); pthread_mutex_destroy(&c->connections_mutex); kill_tcp_connections(c->tcp_c); bs_list_free(&c->ip_port_list); networking_registerhandler(c->dht->net, NET_PACKET_COOKIE_REQUEST, NULL, NULL); networking_registerhandler(c->dht->net, NET_PACKET_COOKIE_RESPONSE, NULL, NULL); networking_registerhandler(c->dht->net, NET_PACKET_CRYPTO_HS, NULL, NULL); networking_registerhandler(c->dht->net, NET_PACKET_CRYPTO_DATA, NULL, NULL); sodium_memzero(c, sizeof(Net_Crypto)); free(c); } ================================================ FILE: toxcore/net_crypto.h ================================================ /* net_crypto.h * * Functions for the core network crypto. * * 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 NET_CRYPTO_H #define NET_CRYPTO_H #include "DHT.h" #include "LAN_discovery.h" #include "TCP_connection.h" #include #define CRYPTO_CONN_NO_CONNECTION 0 #define CRYPTO_CONN_COOKIE_REQUESTING 1 //send cookie request packets #define CRYPTO_CONN_HANDSHAKE_SENT 2 //send handshake packets #define CRYPTO_CONN_NOT_CONFIRMED 3 //send handshake packets, we have received one from the other #define CRYPTO_CONN_ESTABLISHED 4 /* Maximum size of receiving and sending packet buffers. */ #define CRYPTO_PACKET_BUFFER_SIZE 32768 /* Must be a power of 2 */ /* Minimum packet rate per second. */ #define CRYPTO_PACKET_MIN_RATE 4.0 /* Minimum packet queue max length. */ #define CRYPTO_MIN_QUEUE_LENGTH 64 /* Maximum total size of packets that net_crypto sends. */ #define MAX_CRYPTO_PACKET_SIZE 1400 #define CRYPTO_DATA_PACKET_MIN_SIZE (1 + sizeof(uint16_t) + (sizeof(uint32_t) + sizeof(uint32_t)) + crypto_box_MACBYTES) /* Max size of data in packets */ #define MAX_CRYPTO_DATA_SIZE (MAX_CRYPTO_PACKET_SIZE - CRYPTO_DATA_PACKET_MIN_SIZE) /* Interval in ms between sending cookie request/handshake packets. */ #define CRYPTO_SEND_PACKET_INTERVAL 1000 /* The maximum number of times we try to send the cookie request and handshake before giving up. */ #define MAX_NUM_SENDPACKET_TRIES 8 /* The timeout of no received UDP packets before the direct UDP connection is considered dead. */ #define UDP_DIRECT_TIMEOUT ((MAX_NUM_SENDPACKET_TRIES * CRYPTO_SEND_PACKET_INTERVAL) / 1000) #define PACKET_ID_PADDING 0 /* Denotes padding */ #define PACKET_ID_REQUEST 1 /* Used to request unreceived packets */ #define PACKET_ID_KILL 2 /* Used to kill connection */ /* Packet ids 0 to CRYPTO_RESERVED_PACKETS - 1 are reserved for use by net_crypto. */ #define CRYPTO_RESERVED_PACKETS 16 #define MAX_TCP_CONNECTIONS 64 #define MAX_TCP_RELAYS_PEER 4 /* All packets starting with a byte in this range are considered lossy packets. */ #define PACKET_ID_LOSSY_RANGE_START 192 #define PACKET_ID_LOSSY_RANGE_SIZE 63 #define CRYPTO_MAX_PADDING 8 /* All packets will be padded a number of bytes based on this number. */ /* Base current transfer speed on last CONGESTION_QUEUE_ARRAY_SIZE number of points taken at the dT defined in net_crypto.c */ #define CONGESTION_QUEUE_ARRAY_SIZE 12 #define CONGESTION_LAST_SENT_ARRAY_SIZE (CONGESTION_QUEUE_ARRAY_SIZE * 2) /* Default connection ping in ms. */ #define DEFAULT_PING_CONNECTION 1000 #define DEFAULT_TCP_PING_CONNECTION 500 typedef struct { uint64_t sent_time; uint16_t length; uint8_t data[MAX_CRYPTO_DATA_SIZE]; } Packet_Data; typedef struct { Packet_Data *buffer[CRYPTO_PACKET_BUFFER_SIZE]; uint32_t buffer_start; uint32_t buffer_end; /* packet numbers in array: {buffer_start, buffer_end) */ } Packets_Array; typedef struct { uint8_t public_key[crypto_box_PUBLICKEYBYTES]; /* The real public key of the peer. */ uint8_t recv_nonce[crypto_box_NONCEBYTES]; /* Nonce of received packets. */ uint8_t sent_nonce[crypto_box_NONCEBYTES]; /* Nonce of sent packets. */ uint8_t sessionpublic_key[crypto_box_PUBLICKEYBYTES]; /* Our public key for this session. */ uint8_t sessionsecret_key[crypto_box_SECRETKEYBYTES]; /* Our private key for this session. */ uint8_t peersessionpublic_key[crypto_box_PUBLICKEYBYTES]; /* The public key of the peer. */ uint8_t shared_key[crypto_box_BEFORENMBYTES]; /* The precomputed shared key from encrypt_precompute. */ uint8_t status; /* 0 if no connection, 1 we are sending cookie request packets, * 2 if we are sending handshake packets * 3 if connection is not confirmed yet (we have received a handshake but no data packets yet), * 4 if the connection is established. */ uint64_t cookie_request_number; /* number used in the cookie request packets for this connection */ uint8_t dht_public_key[crypto_box_PUBLICKEYBYTES]; /* The dht public key of the peer */ uint8_t *temp_packet; /* Where the cookie request/handshake packet is stored while it is being sent. */ uint16_t temp_packet_length; uint64_t temp_packet_sent_time; /* The time at which the last temp_packet was sent in ms. */ uint32_t temp_packet_num_sent; IP_Port ip_portv4; /* The ip and port to contact this guy directly.*/ IP_Port ip_portv6; uint64_t direct_lastrecv_timev4; /* The Time at which we last received a direct packet in ms. */ uint64_t direct_lastrecv_timev6; uint64_t last_tcp_sent; /* Time the last TCP packet was sent. */ Packets_Array send_array; Packets_Array recv_array; int (*connection_status_callback)(void *object, int id, uint8_t status); void *connection_status_callback_object; int connection_status_callback_id; int (*connection_data_callback)(void *object, int id, uint8_t *data, uint16_t length); void *connection_data_callback_object; int connection_data_callback_id; int (*connection_lossy_data_callback)(void *object, int id, const uint8_t *data, uint16_t length); void *connection_lossy_data_callback_object; int connection_lossy_data_callback_id; uint64_t last_request_packet_sent; uint64_t direct_send_attempt_time; uint32_t packet_counter; double packet_recv_rate; uint64_t packet_counter_set; double packet_send_rate; uint32_t packets_left; uint64_t last_packets_left_set; double last_packets_left_rem; double packet_send_rate_requested; uint32_t packets_left_requested; uint64_t last_packets_left_requested_set; double last_packets_left_requested_rem; uint32_t last_sendqueue_size[CONGESTION_QUEUE_ARRAY_SIZE], last_sendqueue_counter; long signed int last_num_packets_sent[CONGESTION_LAST_SENT_ARRAY_SIZE], last_num_packets_resent[CONGESTION_LAST_SENT_ARRAY_SIZE]; uint32_t packets_sent, packets_resent; uint64_t last_congestion_event; uint64_t rtt_time; /* TCP_connection connection_number */ unsigned int connection_number_tcp; uint8_t maximum_speed_reached; pthread_mutex_t mutex; void (*dht_pk_callback)(void *data, int32_t number, const uint8_t *dht_public_key); void *dht_pk_callback_object; uint32_t dht_pk_callback_number; } Crypto_Connection; typedef struct { IP_Port source; uint8_t public_key[crypto_box_PUBLICKEYBYTES]; /* The real public key of the peer. */ uint8_t dht_public_key[crypto_box_PUBLICKEYBYTES]; /* The dht public key of the peer. */ uint8_t recv_nonce[crypto_box_NONCEBYTES]; /* Nonce of received packets. */ uint8_t peersessionpublic_key[crypto_box_PUBLICKEYBYTES]; /* The public key of the peer. */ uint8_t *cookie; uint8_t cookie_length; } New_Connection; typedef struct { DHT *dht; TCP_Connections *tcp_c; Crypto_Connection *crypto_connections; pthread_mutex_t tcp_mutex; pthread_mutex_t connections_mutex; unsigned int connection_use_counter; uint32_t crypto_connections_length; /* Length of connections array. */ /* Our public and secret keys. */ uint8_t self_public_key[crypto_box_PUBLICKEYBYTES]; uint8_t self_secret_key[crypto_box_SECRETKEYBYTES]; /* The secret key used for cookies */ uint8_t secret_symmetric_key[crypto_box_KEYBYTES]; int (*new_connection_callback)(void *object, New_Connection *n_c); void *new_connection_callback_object; /* The current optimal sleep time */ uint32_t current_sleep_time; BS_LIST ip_port_list; } Net_Crypto; /* Set function to be called when someone requests a new connection to us. * * The set function should return -1 on failure and 0 on success. * * n_c is only valid for the duration of the function call. */ void new_connection_handler(Net_Crypto *c, int (*new_connection_callback)(void *object, New_Connection *n_c), void *object); /* Accept a crypto connection. * * return -1 on failure. * return connection id on success. */ int accept_crypto_connection(Net_Crypto *c, New_Connection *n_c); /* Create a crypto connection. * If one to that real public key already exists, return it. * * return -1 on failure. * return connection id on success. */ int new_crypto_connection(Net_Crypto *c, const uint8_t *real_public_key, const uint8_t *dht_public_key); /* Set the direct ip of the crypto connection. * * Connected is 0 if we are not sure we are connected to that person, 1 if we are sure. * * return -1 on failure. * return 0 on success. */ int set_direct_ip_port(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port, _Bool connected); /* Set function to be called when connection with crypt_connection_id goes connects/disconnects. * * The set function should return -1 on failure and 0 on success. * Note that if this function is set, the connection will clear itself on disconnect. * Object and id will be passed to this function untouched. * status is 1 if the connection is going online, 0 if it is going offline. * * return -1 on failure. * return 0 on success. */ int connection_status_handler(const Net_Crypto *c, int crypt_connection_id, int (*connection_status_callback)(void *object, int id, uint8_t status), void *object, int id); /* Set function to be called when connection with crypt_connection_id receives a lossless data packet of length. * * The set function should return -1 on failure and 0 on success. * Object and id will be passed to this function untouched. * * return -1 on failure. * return 0 on success. */ int connection_data_handler(const Net_Crypto *c, int crypt_connection_id, int (*connection_data_callback)(void *object, int id, uint8_t *data, uint16_t length), void *object, int id); /* Set function to be called when connection with crypt_connection_id receives a lossy data packet of length. * * The set function should return -1 on failure and 0 on success. * Object and id will be passed to this function untouched. * * return -1 on failure. * return 0 on success. */ int connection_lossy_data_handler(Net_Crypto *c, int crypt_connection_id, int (*connection_lossy_data_callback)(void *object, int id, const uint8_t *data, uint16_t length), void *object, int id); /* Set the function for this friend that will be callbacked with object and number if * the friend sends us a different dht public key than we have associated to him. * * If this function is called, the connection should be recreated with the new public key. * * object and number will be passed as argument to this function. * * return -1 on failure. * return 0 on success. */ int nc_dht_pk_callback(Net_Crypto *c, int crypt_connection_id, void (*function)(void *data, int32_t number, const uint8_t *dht_public_key), void *object, uint32_t number); /* returns the number of packet slots left in the sendbuffer. * return 0 if failure. */ uint32_t crypto_num_free_sendqueue_slots(const Net_Crypto *c, int crypt_connection_id); /* Return 1 if max speed was reached for this connection (no more data can be physically through the pipe). * Return 0 if it wasn't reached. */ _Bool max_speed_reached(Net_Crypto *c, int crypt_connection_id); /* Sends a lossless cryptopacket. * * return -1 if data could not be put in packet queue. * return positive packet number if data was put into the queue. * * The first byte of data must be in the CRYPTO_RESERVED_PACKETS to PACKET_ID_LOSSY_RANGE_START range. * * congestion_control: should congestion control apply to this packet? */ int64_t write_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length, uint8_t congestion_control); /* Check if packet_number was received by the other side. * * packet_number must be a valid packet number of a packet sent on this connection. * * return -1 on failure. * return 0 on success. */ int cryptpacket_received(Net_Crypto *c, int crypt_connection_id, uint32_t packet_number); /* return -1 on failure. * return 0 on success. * * Sends a lossy cryptopacket. (first byte must in the PACKET_ID_LOSSY_RANGE_*) */ int send_lossy_cryptpacket(Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint16_t length); /* Add a tcp relay, associating it to a crypt_connection_id. * * return 0 if it was added. * return -1 if it wasn't. */ int add_tcp_relay_peer(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port, const uint8_t *public_key); /* Add a tcp relay to the array. * * return 0 if it was added. * return -1 if it wasn't. */ int add_tcp_relay(Net_Crypto *c, IP_Port ip_port, const uint8_t *public_key); /* Return a random TCP connection number for use in send_tcp_onion_request. * * return TCP connection number on success. * return -1 on failure. */ int get_random_tcp_con_number(Net_Crypto *c); /* Send an onion packet via the TCP relay corresponding to TCP_conn_number. * * return 0 on success. * return -1 on failure. */ int send_tcp_onion_request(Net_Crypto *c, unsigned int TCP_conn_number, const uint8_t *data, uint16_t length); /* Copy a maximum of 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 copy_connected_tcp_relays(Net_Crypto *c, Node_format *tcp_relays, uint16_t num); /* Kill a crypto connection. * * return -1 on failure. * return 0 on success. */ int crypto_kill(Net_Crypto *c, int crypt_connection_id); /* return one of CRYPTO_CONN_* values indicating the state of the connection. * * sets direct_connected to 1 if connection connects directly to other, 0 if it isn't. * sets online_tcp_relays to the number of connected tcp relays this connection has. */ unsigned int crypto_connection_status(const Net_Crypto *c, int crypt_connection_id, _Bool *direct_connected, unsigned int *online_tcp_relays); /* Generate our public and private keys. * Only call this function the first time the program starts. */ void new_keys(Net_Crypto *c); /* Save the public and private keys to the keys array. * Length must be crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES. */ void save_keys(const Net_Crypto *c, uint8_t *keys); /* Load the secret key. * Length must be crypto_box_SECRETKEYBYTES. */ void load_secret_key(Net_Crypto *c, const uint8_t *sk); /* Create new instance of Net_Crypto. * Sets all the global connection variables to their default values. */ Net_Crypto *new_net_crypto(DHT *dht, TCP_Proxy_Info *proxy_info); /* return the optimal interval in ms for running do_net_crypto. */ uint32_t crypto_run_interval(const Net_Crypto *c); /* Main loop. */ void do_net_crypto(Net_Crypto *c); void kill_net_crypto(Net_Crypto *c); #endif ================================================ FILE: toxcore/network.c ================================================ /* network.c * * Functions for the core networking. * * 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 . * */ #if (_WIN32_WINNT >= _WIN32_WINNT_WINXP) #define _WIN32_WINNT 0x501 #endif #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "logger.h" #if !defined(_WIN32) && !defined(__WIN32__) && !defined (WIN32) #include #endif #ifdef __APPLE__ #include #include #endif #include "network.h" #include "util.h" #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) static const char *inet_ntop(sa_family_t family, void *addr, char *buf, size_t bufsize) { if (family == AF_INET) { struct sockaddr_in saddr; memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_addr = *(struct in_addr *)addr; DWORD len = bufsize; if (WSAAddressToString((LPSOCKADDR)&saddr, sizeof(saddr), NULL, buf, &len)) return NULL; return buf; } else if (family == AF_INET6) { struct sockaddr_in6 saddr; memset(&saddr, 0, sizeof(saddr)); saddr.sin6_family = AF_INET6; saddr.sin6_addr = *(struct in6_addr *)addr; DWORD len = bufsize; if (WSAAddressToString((LPSOCKADDR)&saddr, sizeof(saddr), NULL, buf, &len)) return NULL; return buf; } return NULL; } static int inet_pton(sa_family_t family, const char *addrString, void *addrbuf) { if (family == AF_INET) { struct sockaddr_in saddr; memset(&saddr, 0, sizeof(saddr)); INT len = sizeof(saddr); if (WSAStringToAddress((LPTSTR)addrString, AF_INET, NULL, (LPSOCKADDR)&saddr, &len)) return 0; *(struct in_addr *)addrbuf = saddr.sin_addr; return 1; } else if (family == AF_INET6) { struct sockaddr_in6 saddr; memset(&saddr, 0, sizeof(saddr)); INT len = sizeof(saddr); if (WSAStringToAddress((LPTSTR)addrString, AF_INET6, NULL, (LPSOCKADDR)&saddr, &len)) return 0; *(struct in6_addr *)addrbuf = saddr.sin6_addr; return 1; } return 0; } #endif /* Check if socket is valid. * * return 1 if valid * return 0 if not valid */ int sock_valid(sock_t sock) { #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) if (sock == INVALID_SOCKET) { #else if (sock < 0) { #endif return 0; } return 1; } /* Close the socket. */ void kill_sock(sock_t sock) { #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) closesocket(sock); #else close(sock); #endif } /* Set socket as nonblocking * * return 1 on success * return 0 on failure */ int set_socket_nonblock(sock_t sock) { #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) u_long mode = 1; return (ioctlsocket(sock, FIONBIO, &mode) == 0); #else return (fcntl(sock, F_SETFL, O_NONBLOCK, 1) == 0); #endif } /* Set socket to not emit SIGPIPE * * return 1 on success * return 0 on failure */ int set_socket_nosigpipe(sock_t sock) { #if defined(__MACH__) int set = 1; return (setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int)) == 0); #else return 1; #endif } /* Enable SO_REUSEADDR on socket. * * return 1 on success * return 0 on failure */ int set_socket_reuseaddr(sock_t sock) { int set = 1; return (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&set, sizeof(set)) == 0); } /* Set socket to dual (IPv4 + IPv6 socket) * * return 1 on success * return 0 on failure */ int set_socket_dualstack(sock_t sock) { int ipv6only = 0; socklen_t optsize = sizeof(ipv6only); int res = getsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&ipv6only, &optsize); if ((res == 0) && (ipv6only == 0)) return 1; ipv6only = 0; return (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&ipv6only, sizeof(ipv6only)) == 0); } /* return current UNIX time in microseconds (us). */ static uint64_t current_time_actual(void) { uint64_t time; #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) /* This probably works fine */ FILETIME ft; GetSystemTimeAsFileTime(&ft); time = ft.dwHighDateTime; time <<= 32; time |= ft.dwLowDateTime; time -= 116444736000000000ULL; return time / 10; #else struct timeval a; gettimeofday(&a, NULL); time = 1000000ULL * a.tv_sec + a.tv_usec; return time; #endif } #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) static uint64_t last_monotime; static uint64_t add_monotime; #endif /* return current monotonic time in milliseconds (ms). */ uint64_t current_time_monotonic(void) { uint64_t time; #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) time = (uint64_t)GetTickCount() + add_monotime; if (time < last_monotime) { /* Prevent time from ever decreasing because of 32 bit wrap. */ uint32_t add = ~0; add_monotime += add; time += add; } last_monotime = time; #else struct timespec monotime; #if defined(__linux__) && defined(CLOCK_MONOTONIC_RAW) clock_gettime(CLOCK_MONOTONIC_RAW, &monotime); #elif defined(__APPLE__) clock_serv_t muhclock; mach_timespec_t machtime; host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &muhclock); clock_get_time(muhclock, &machtime); mach_port_deallocate(mach_task_self(), muhclock); monotime.tv_sec = machtime.tv_sec; monotime.tv_nsec = machtime.tv_nsec; #else clock_gettime(CLOCK_MONOTONIC, &monotime); #endif time = 1000ULL * monotime.tv_sec + (monotime.tv_nsec / 1000000ULL); #endif return time; } /* In case no logging */ #ifndef TOX_LOGGER #define loglogdata(__message__, __buffer__, __buflen__, __ip_port__, __res__) #else #define data_0(__buflen__, __buffer__) __buflen__ > 4 ? ntohl(*(uint32_t *)&__buffer__[1]) : 0 #define data_1(__buflen__, __buffer__) __buflen__ > 7 ? ntohl(*(uint32_t *)&__buffer__[5]) : 0 #define loglogdata(__message__, __buffer__, __buflen__, __ip_port__, __res__) \ (__ip_port__) .ip; \ if (__res__ < 0) /* Windows doesn't necessarily know %zu */ \ LOGGER_TRACE("[%2u] %s %3hu%c %s:%hu (%u: %s) | %04x%04x", \ __buffer__[0], __message__, (__buflen__ < 999 ? (uint16_t)__buflen__ : 999), 'E', \ ip_ntoa(&((__ip_port__).ip)), ntohs((__ip_port__).port), errno, strerror(errno), data_0(__buflen__, __buffer__), data_1(__buflen__, __buffer__)); \ else if ((__res__ > 0) && ((size_t)__res__ <= __buflen__)) \ LOGGER_TRACE("[%2u] %s %3zu%c %s:%hu (%u: %s) | %04x%04x", \ __buffer__[0], __message__, (__res__ < 999 ? (size_t)__res__ : 999), ((size_t)__res__ < __buflen__ ? '<' : '='), \ ip_ntoa(&((__ip_port__).ip)), ntohs((__ip_port__).port), 0, "OK", data_0(__buflen__, __buffer__), data_1(__buflen__, __buffer__)); \ else /* empty or overwrite */ \ LOGGER_TRACE("[%2u] %s %zu%c%zu %s:%hu (%u: %s) | %04x%04x", \ __buffer__[0], __message__, (size_t)__res__, (!__res__ ? '!' : '>'), __buflen__, \ ip_ntoa(&((__ip_port__).ip)), ntohs((__ip_port__).port), 0, "OK", data_0(__buflen__, __buffer__), data_1(__buflen__, __buffer__)); #endif /* TOX_LOGGER */ /* Basic network functions: * Function to send packet(data) of length length to ip_port. */ int sendpacket(Networking_Core *net, IP_Port ip_port, const uint8_t *data, uint16_t length) { if (net->family == 0) /* Socket not initialized */ return -1; /* socket AF_INET, but target IP NOT: can't send */ if ((net->family == AF_INET) && (ip_port.ip.family != AF_INET)) return -1; struct sockaddr_storage addr; size_t addrsize = 0; if (ip_port.ip.family == AF_INET) { if (net->family == AF_INET6) { /* must convert to IPV4-in-IPV6 address */ struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; addrsize = sizeof(struct sockaddr_in6); addr6->sin6_family = AF_INET6; addr6->sin6_port = ip_port.port; /* there should be a macro for this in a standards compliant * environment, not found */ IP6 ip6; ip6.uint32[0] = 0; ip6.uint32[1] = 0; ip6.uint32[2] = htonl(0xFFFF); ip6.uint32[3] = ip_port.ip.ip4.uint32; addr6->sin6_addr = ip6.in6_addr; addr6->sin6_flowinfo = 0; addr6->sin6_scope_id = 0; } else { 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_port = ip_port.port; addr6->sin6_addr = ip_port.ip.ip6.in6_addr; addr6->sin6_flowinfo = 0; addr6->sin6_scope_id = 0; } else { /* unknown address type*/ return -1; } int res = sendto(net->sock, (char *) data, length, 0, (struct sockaddr *)&addr, addrsize); loglogdata("O=>", data, length, ip_port, res); return res; } /* Function to receive data * ip and port of sender is put into ip_port. * Packet data is put into data. * Packet length is put into length. */ static int receivepacket(sock_t sock, IP_Port *ip_port, uint8_t *data, uint32_t *length) { memset(ip_port, 0, sizeof(IP_Port)); struct sockaddr_storage addr; #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) int addrlen = sizeof(addr); #else socklen_t addrlen = sizeof(addr); #endif *length = 0; int fail_or_len = recvfrom(sock, (char *) data, MAX_UDP_PACKET_SIZE, 0, (struct sockaddr *)&addr, &addrlen); if (fail_or_len < 0) { LOGGER_SCOPE( if ((fail_or_len < 0) && (errno != EWOULDBLOCK)) LOGGER_ERROR("Unexpected error reading from socket: %u, %s\n", errno, strerror(errno)); ); return -1; /* Nothing received. */ } *length = (uint32_t)fail_or_len; if (addr.ss_family == AF_INET) { struct sockaddr_in *addr_in = (struct sockaddr_in *)&addr; ip_port->ip.family = addr_in->sin_family; ip_port->ip.ip4.in_addr = addr_in->sin_addr; ip_port->port = addr_in->sin_port; } else if (addr.ss_family == AF_INET6) { struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&addr; ip_port->ip.family = addr_in6->sin6_family; ip_port->ip.ip6.in6_addr = addr_in6->sin6_addr; ip_port->port = addr_in6->sin6_port; if (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]; } } else return -1; loglogdata("=>O", data, MAX_UDP_PACKET_SIZE, *ip_port, *length); return 0; } void networking_registerhandler(Networking_Core *net, uint8_t byte, packet_handler_callback cb, void *object) { net->packethandlers[byte].function = cb; net->packethandlers[byte].object = object; } void networking_poll(Networking_Core *net) { if (net->family == 0) /* Socket not initialized */ return; unix_time_update(); IP_Port ip_port; uint8_t data[MAX_UDP_PACKET_SIZE]; uint32_t length; while (receivepacket(net->sock, &ip_port, data, &length) != -1) { if (length < 1) continue; if (!(net->packethandlers[data[0]].function)) { LOGGER_WARNING("[%02u] -- Packet has no handler", data[0]); continue; } net->packethandlers[data[0]].function(net->packethandlers[data[0]].object, ip_port, data, length); } } #ifndef VANILLA_NACL /* Used for sodium_init() */ #include #endif uint8_t at_startup_ran = 0; int networking_at_startup(void) { if (at_startup_ran != 0) return 0; #ifndef VANILLA_NACL #ifdef USE_RANDOMBYTES_STIR randombytes_stir(); #else sodium_init(); #endif /*USE_RANDOMBYTES_STIR*/ #endif/*VANILLA_NACL*/ #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) return -1; #endif srand((uint32_t)current_time_actual()); at_startup_ran = 1; return 0; } /* TODO: Put this somewhere static void at_shutdown(void) { #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) WSACleanup(); #endif } */ /* Initialize networking. * Added for reverse compatibility with old new_networking calls. */ Networking_Core *new_networking(IP ip, uint16_t port) { return new_networking_ex(ip, port, port + (TOX_PORTRANGE_TO - TOX_PORTRANGE_FROM), 0); } /* Initialize networking. * Bind to ip and port. * ip must be in network order EX: 127.0.0.1 = (7F000001). * port is in host byte order (this means don't worry about it). * * return Networking_Core object if no problems * return NULL if there are problems. * * If error is non NULL it is set to 0 if no issues, 1 if socket related error, 2 if other. */ Networking_Core *new_networking_ex(IP ip, uint16_t port_from, uint16_t port_to, unsigned int *error) { /* If both from and to are 0, use default port range * If one is 0 and the other is non-0, use the non-0 value as only port * If from > to, swap */ if (port_from == 0 && port_to == 0) { port_from = TOX_PORTRANGE_FROM; port_to = TOX_PORTRANGE_TO; } else if (port_from == 0 && port_to != 0) { port_from = port_to; } else if (port_from != 0 && port_to == 0) { port_to = port_from; } else if (port_from > port_to) { uint16_t temp = port_from; port_from = port_to; port_to = temp; } if (error) *error = 2; /* maybe check for invalid IPs like 224+.x.y.z? if there is any IP set ever */ if (ip.family != AF_INET && ip.family != AF_INET6) { #ifdef DEBUG fprintf(stderr, "Invalid address family: %u\n", ip.family); #endif return NULL; } if (networking_at_startup() != 0) return NULL; Networking_Core *temp = calloc(1, sizeof(Networking_Core)); if (temp == NULL) return NULL; temp->family = ip.family; temp->port = 0; /* Initialize our socket. */ /* add log message what we're creating */ temp->sock = socket(temp->family, SOCK_DGRAM, IPPROTO_UDP); /* Check for socket error. */ if (!sock_valid(temp->sock)) { #ifdef DEBUG fprintf(stderr, "Failed to get a socket?! %u, %s\n", errno, strerror(errno)); #endif free(temp); if (error) *error = 1; return NULL; } /* Functions to increase the size of the send and receive UDP buffers. */ int n = 1024 * 1024 * 2; setsockopt(temp->sock, SOL_SOCKET, SO_RCVBUF, (char *)&n, sizeof(n)); setsockopt(temp->sock, SOL_SOCKET, SO_SNDBUF, (char *)&n, sizeof(n)); /* Enable broadcast on socket */ int broadcast = 1; setsockopt(temp->sock, SOL_SOCKET, SO_BROADCAST, (char *)&broadcast, sizeof(broadcast)); /* iOS UDP sockets are weird and apparently can SIGPIPE */ if (!set_socket_nosigpipe(temp->sock)) { kill_networking(temp); if (error) *error = 1; return NULL; } /* Set socket nonblocking. */ if (!set_socket_nonblock(temp->sock)) { kill_networking(temp); if (error) *error = 1; return NULL; } /* Bind our socket to port PORT and the given IP address (usually 0.0.0.0 or ::) */ uint16_t *portptr = NULL; struct sockaddr_storage addr; size_t addrsize; if (temp->family == AF_INET) { struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr; addrsize = sizeof(struct sockaddr_in); addr4->sin_family = AF_INET; addr4->sin_port = 0; addr4->sin_addr = ip.ip4.in_addr; portptr = &addr4->sin_port; } else if (temp->family == AF_INET6) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr; addrsize = sizeof(struct sockaddr_in6); addr6->sin6_family = AF_INET6; addr6->sin6_port = 0; addr6->sin6_addr = ip.ip6.in6_addr; addr6->sin6_flowinfo = 0; addr6->sin6_scope_id = 0; portptr = &addr6->sin6_port; } else { free(temp); return NULL; } if (ip.family == AF_INET6) { #ifdef TOX_LOGGER int is_dualstack = #endif /* TOX_LOGGER */ set_socket_dualstack(temp->sock); LOGGER_DEBUG( "Dual-stack socket: %s", is_dualstack ? "enabled" : "Failed to enable, won't be able to receive from/send to IPv4 addresses" ); /* multicast local nodes */ struct ipv6_mreq mreq; memset(&mreq, 0, sizeof(mreq)); mreq.ipv6mr_multiaddr.s6_addr[ 0] = 0xFF; mreq.ipv6mr_multiaddr.s6_addr[ 1] = 0x02; mreq.ipv6mr_multiaddr.s6_addr[15] = 0x01; mreq.ipv6mr_interface = 0; #ifdef TOX_LOGGER int res = #endif /* TOX_LOGGER */ setsockopt(temp->sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)); LOGGER_DEBUG(res < 0 ? "Failed to activate local multicast membership. (%u, %s)" : "Local multicast group FF02::1 joined successfully", errno, strerror(errno) ); } /* a hanging program or a different user might block the standard port; * as long as it isn't a parameter coming from the commandline, * try a few ports after it, to see if we can find a "free" one * * if we go on without binding, the first sendto() automatically binds to * a free port chosen by the system (i.e. anything from 1024 to 65535) * * returning NULL after bind fails has both advantages and disadvantages: * advantage: * we can rely on getting the port in the range 33445..33450, which * enables us to tell joe user to open their firewall to a small range * * disadvantage: * some clients might not test return of tox_new(), blindly assuming that * it worked ok (which it did previously without a successful bind) */ uint16_t port_to_try = port_from; *portptr = htons(port_to_try); int tries; for (tries = port_from; tries <= port_to; tries++) { int res = bind(temp->sock, (struct sockaddr *)&addr, addrsize); if (!res) { temp->port = *portptr; LOGGER_DEBUG("Bound successfully to %s:%u", ip_ntoa(&ip), ntohs(temp->port)); /* errno isn't reset on success, only set on failure, the failed * binds with parallel clients yield a -EPERM to the outside if * errno isn't cleared here */ if (tries > 0) errno = 0; if (error) *error = 0; return temp; } port_to_try++; if (port_to_try > port_to) port_to_try = port_from; *portptr = htons(port_to_try); } LOGGER_ERROR("Failed to bind socket: %u, %s IP: %s port_from: %u port_to: %u", errno, strerror(errno), ip_ntoa(&ip), port_from, port_to); kill_networking(temp); if (error) *error = 1; return NULL; } /* Function to cleanup networking stuff. */ void kill_networking(Networking_Core *net) { if (!net) return; if (net->family != 0) /* Socket not initialized */ kill_sock(net->sock); free(net); return; } /* ip_equal * compares two IPAny structures * unset means unequal * * returns 0 when not equal or when uninitialized */ int ip_equal(const IP *a, const IP *b) { if (!a || !b) return 0; /* same family */ if (a->family == b->family) { if (a->family == AF_INET) return (a->ip4.in_addr.s_addr == b->ip4.in_addr.s_addr); else if (a->family == AF_INET6) return a->ip6.uint64[0] == b->ip6.uint64[0] && a->ip6.uint64[1] == b->ip6.uint64[1]; else return 0; } /* different family: check on the IPv6 one if it is the IPv4 one embedded */ if ((a->family == AF_INET) && (b->family == AF_INET6)) { if (IPV6_IPV4_IN_V6(b->ip6)) return (a->ip4.in_addr.s_addr == b->ip6.uint32[3]); } else if ((a->family == AF_INET6) && (b->family == AF_INET)) { if (IPV6_IPV4_IN_V6(a->ip6)) return (a->ip6.uint32[3] == b->ip4.in_addr.s_addr); } return 0; } /* ipport_equal * compares two IPAny_Port structures * unset means unequal * * returns 0 when not equal or when uninitialized */ int ipport_equal(const IP_Port *a, const IP_Port *b) { if (!a || !b) return 0; if (!a->port || (a->port != b->port)) return 0; return ip_equal(&a->ip, &b->ip); } /* nulls out ip */ void ip_reset(IP *ip) { if (!ip) return; memset(ip, 0, sizeof(IP)); } /* nulls out ip, sets family according to flag */ void ip_init(IP *ip, uint8_t ipv6enabled) { if (!ip) return; memset(ip, 0, sizeof(IP)); ip->family = ipv6enabled ? AF_INET6 : AF_INET; } /* checks if ip is valid */ int ip_isset(const IP *ip) { if (!ip) return 0; return (ip->family != 0); } /* checks if ip is valid */ int ipport_isset(const IP_Port *ipport) { if (!ipport) return 0; if (!ipport->port) return 0; return ip_isset(&ipport->ip); } /* copies an ip structure (careful about direction!) */ void ip_copy(IP *target, const IP *source) { if (!source || !target) return; memcpy(target, source, sizeof(IP)); } /* copies an ip_port structure (careful about direction!) */ void ipport_copy(IP_Port *target, const IP_Port *source) { if (!source || !target) return; memcpy(target, source, sizeof(IP_Port)); } /* ip_ntoa * converts ip into a string * uses a static buffer, so mustn't used multiple times in the same output * * IPv6 addresses are enclosed into square brackets, i.e. "[IPv6]" * writes error message into the buffer on error */ /* there would be INET6_ADDRSTRLEN, but it might be too short for the error message */ static char addresstext[96]; // FIXME magic number. Why not INET6_ADDRSTRLEN ? const char *ip_ntoa(const IP *ip) { if (ip) { if (ip->family == AF_INET) { /* returns standard quad-dotted notation */ struct in_addr *addr = (struct in_addr *)&ip->ip4; addresstext[0] = 0; inet_ntop(ip->family, addr, addresstext, sizeof(addresstext)); } else if (ip->family == AF_INET6) { /* returns hex-groups enclosed into square brackets */ struct in6_addr *addr = (struct in6_addr *)&ip->ip6; addresstext[0] = '['; inet_ntop(ip->family, addr, &addresstext[1], sizeof(addresstext) - 3); size_t len = strlen(addresstext); addresstext[len] = ']'; addresstext[len + 1] = 0; } else snprintf(addresstext, sizeof(addresstext), "(IP invalid, family %u)", ip->family); } else snprintf(addresstext, sizeof(addresstext), "(IP invalid: NULL)"); /* brute force protection against lacking termination */ addresstext[sizeof(addresstext) - 1] = 0; return addresstext; } /* * ip_parse_addr * parses IP structure into an address string * * input * ip: ip of AF_INET or AF_INET6 families * length: length of the address buffer * Must be at least INET_ADDRSTRLEN for AF_INET * and INET6_ADDRSTRLEN for AF_INET6 * * output * address: dotted notation (IPv4: quad, IPv6: 16) or colon notation (IPv6) * * returns 1 on success, 0 on failure */ int ip_parse_addr(const IP *ip, char *address, size_t length) { if (!address || !ip) { return 0; } if (ip->family == AF_INET) { struct in_addr *addr = (struct in_addr *)&ip->ip4; return inet_ntop(ip->family, addr, address, length) != NULL; } else if (ip->family == AF_INET6) { struct in6_addr *addr = (struct in6_addr *)&ip->ip6; return inet_ntop(ip->family, addr, address, length) != NULL; } return 0; } /* * addr_parse_ip * directly parses the input into an IP structure * tries IPv4 first, then IPv6 * * input * address: dotted notation (IPv4: quad, IPv6: 16) or colon notation (IPv6) * * output * IP: family and the value is set on success * * returns 1 on success, 0 on failure */ int addr_parse_ip(const char *address, IP *to) { if (!address || !to) return 0; struct in_addr addr4; if (1 == inet_pton(AF_INET, address, &addr4)) { to->family = AF_INET; to->ip4.in_addr = addr4; return 1; } struct in6_addr addr6; if (1 == inet_pton(AF_INET6, address, &addr6)) { to->family = AF_INET6; to->ip6.in6_addr = addr6; return 1; } return 0; } /* * addr_resolve(): * uses getaddrinfo to resolve an address into an IP address * uses the first IPv4/IPv6 addresses returned by getaddrinfo * * input * address: a hostname (or something parseable to an IP address) * to: to.family MUST be initialized, either set to a specific IP version * (AF_INET/AF_INET6) or to the unspecified AF_UNSPEC (= 0), if both * IP versions are acceptable * extra can be NULL and is only set in special circumstances, see returns * * returns in *to a valid IPAny (v4/v6), * prefers v6 if ip.family was AF_UNSPEC and both available * returns in *extra an IPv4 address, if family was AF_UNSPEC and *to is AF_INET6 * returns 0 on failure, TOX_ADDR_RESOLVE_* on success. */ int addr_resolve(const char *address, IP *to, IP *extra) { if (!address || !to) return 0; sa_family_t family = to->family; struct addrinfo *server = NULL; struct addrinfo *walker = NULL; struct addrinfo hints; int rc; int result = 0; int done = 0; memset(&hints, 0, sizeof(hints)); hints.ai_family = family; hints.ai_socktype = SOCK_DGRAM; // type of socket Tox uses. if (networking_at_startup() != 0) return 0; rc = getaddrinfo(address, NULL, &hints, &server); // Lookup failed. if (rc != 0) { return 0; } IP ip4; ip_init(&ip4, 0); // ipv6enabled = 0 IP ip6; ip_init(&ip6, 1); // ipv6enabled = 1 for (walker = server; (walker != NULL) && !done; walker = walker->ai_next) { switch (walker->ai_family) { case AF_INET: if (walker->ai_family == family) { /* AF_INET requested, done */ struct sockaddr_in *addr = (struct sockaddr_in *)walker->ai_addr; to->ip4.in_addr = addr->sin_addr; result = TOX_ADDR_RESOLVE_INET; done = 1; } else if (!(result & TOX_ADDR_RESOLVE_INET)) { /* AF_UNSPEC requested, store away */ struct sockaddr_in *addr = (struct sockaddr_in *)walker->ai_addr; ip4.ip4.in_addr = addr->sin_addr; result |= TOX_ADDR_RESOLVE_INET; } break; /* switch */ case AF_INET6: if (walker->ai_family == family) { /* AF_INET6 requested, done */ if (walker->ai_addrlen == sizeof(struct sockaddr_in6)) { struct sockaddr_in6 *addr = (struct sockaddr_in6 *)walker->ai_addr; to->ip6.in6_addr = addr->sin6_addr; result = TOX_ADDR_RESOLVE_INET6; done = 1; } } else if (!(result & TOX_ADDR_RESOLVE_INET6)) { /* AF_UNSPEC requested, store away */ if (walker->ai_addrlen == sizeof(struct sockaddr_in6)) { struct sockaddr_in6 *addr = (struct sockaddr_in6 *)walker->ai_addr; ip6.ip6.in6_addr = addr->sin6_addr; result |= TOX_ADDR_RESOLVE_INET6; } } break; /* switch */ } } if (family == AF_UNSPEC) { if (result & TOX_ADDR_RESOLVE_INET6) { ip_copy(to, &ip6); if ((result & TOX_ADDR_RESOLVE_INET) && (extra != NULL)) { ip_copy(extra, &ip4); } } else if (result & TOX_ADDR_RESOLVE_INET) { ip_copy(to, &ip4); } else { result = 0; } } freeaddrinfo(server); return result; } /* * addr_resolve_or_parse_ip * resolves string into an IP address * * address: a hostname (or something parseable to an IP address) * to: to.family MUST be initialized, either set to a specific IP version * (AF_INET/AF_INET6) or to the unspecified AF_UNSPEC (= 0), if both * IP versions are acceptable * extra can be NULL and is only set in special circumstances, see returns * * returns in *tro a matching address (IPv6 or IPv4) * returns in *extra, if not NULL, an IPv4 address, if to->family was AF_UNSPEC * returns 1 on success * returns 0 on failure */ int addr_resolve_or_parse_ip(const char *address, IP *to, IP *extra) { if (!addr_resolve(address, to, extra)) if (!addr_parse_ip(address, to)) return 0; return 1; } ================================================ FILE: toxcore/network.h ================================================ /* network.h * * Datatypes, functions and includes for the core networking. * * 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 NETWORK_H #define NETWORK_H #ifdef PLAN9 #include //Plan 9 requires this is imported first #include #endif #include #include #include #include #include #if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) /* Put win32 includes here */ #ifndef WINVER //Windows XP #define WINVER 0x0501 #endif #include #include #include #ifndef IPV6_V6ONLY #define IPV6_V6ONLY 27 #endif typedef unsigned int sock_t; /* sa_family_t is the sockaddr_in / sockaddr_in6 family field */ typedef short sa_family_t; #ifndef EWOULDBLOCK #define EWOULDBLOCK WSAEWOULDBLOCK #endif #else // Linux includes #include #include #include #include #include #include #include #include #include typedef int sock_t; #endif #if defined(__AIX__) # define _XOPEN_SOURCE 1 #endif #if defined(__sun__) #define __EXTENSIONS__ 1 // SunOS! #if defined(__SunOS5_6__) || defined(__SunOS5_7__) || defined(__SunOS5_8__) || defined(__SunOS5_9__) || defined(__SunOS5_10__) //Nothing needed #else #define __MAKECONTEXT_V2_SOURCE 1 #endif #endif #ifndef IPV6_ADD_MEMBERSHIP #ifdef IPV6_JOIN_GROUP #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP #define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP #endif #endif #define MAX_UDP_PACKET_SIZE 2048 #define NET_PACKET_PING_REQUEST 0 /* Ping request packet ID. */ #define NET_PACKET_PING_RESPONSE 1 /* Ping response packet ID. */ #define NET_PACKET_GET_NODES 2 /* Get nodes request packet ID. */ #define NET_PACKET_SEND_NODES_IPV6 4 /* Send nodes response packet ID for other addresses. */ #define NET_PACKET_COOKIE_REQUEST 24 /* Cookie request packet */ #define NET_PACKET_COOKIE_RESPONSE 25 /* Cookie response packet */ #define NET_PACKET_CRYPTO_HS 26 /* Crypto handshake packet */ #define NET_PACKET_CRYPTO_DATA 27 /* Crypto data packet */ #define NET_PACKET_CRYPTO 32 /* Encrypted data packet ID. */ #define NET_PACKET_LAN_DISCOVERY 33 /* LAN discovery packet ID. */ /* See: docs/Prevent_Tracking.txt and onion.{c, h} */ #define NET_PACKET_ONION_SEND_INITIAL 128 #define NET_PACKET_ONION_SEND_1 129 #define NET_PACKET_ONION_SEND_2 130 #define NET_PACKET_ANNOUNCE_REQUEST 131 #define NET_PACKET_ANNOUNCE_RESPONSE 132 #define NET_PACKET_ONION_DATA_REQUEST 133 #define NET_PACKET_ONION_DATA_RESPONSE 134 #define NET_PACKET_ONION_RECV_3 140 #define NET_PACKET_ONION_RECV_2 141 #define NET_PACKET_ONION_RECV_1 142 /* Only used for bootstrap nodes */ #define BOOTSTRAP_INFO_PACKET_ID 240 #define TOX_PORTRANGE_FROM 33445 #define TOX_PORTRANGE_TO 33545 #define TOX_PORT_DEFAULT TOX_PORTRANGE_FROM /* TCP related */ #define TCP_ONION_FAMILY (AF_INET6 + 1) #define TCP_INET (AF_INET6 + 2) #define TCP_INET6 (AF_INET6 + 3) #define TCP_FAMILY (AF_INET6 + 4) typedef union { uint8_t uint8[4]; uint16_t uint16[2]; uint32_t uint32; struct in_addr in_addr; } IP4; typedef union { uint8_t uint8[16]; uint16_t uint16[8]; uint32_t uint32[4]; uint64_t uint64[2]; struct in6_addr in6_addr; } IP6; typedef struct { uint8_t family; union { IP4 ip4; IP6 ip6; }; } IP; typedef struct { IP ip; uint16_t port; } IP_Port; /* Does the IP6 struct a contain an IPv4 address in an IPv6 one? */ #define IPV6_IPV4_IN_V6(a) ((a.uint64[0] == 0) && (a.uint32[2] == htonl (0xffff))) #define SIZE_IP4 4 #define SIZE_IP6 16 #define SIZE_IP (1 + SIZE_IP6) #define SIZE_PORT 2 #define SIZE_IPPORT (SIZE_IP + SIZE_PORT) #define TOX_ENABLE_IPV6_DEFAULT 1 /* addr_resolve return values */ #define TOX_ADDR_RESOLVE_INET 1 #define TOX_ADDR_RESOLVE_INET6 2 /* ip_ntoa * converts ip into a string * uses a static buffer, so mustn't used multiple times in the same output * * IPv6 addresses are enclosed into square brackets, i.e. "[IPv6]" * writes error message into the buffer on error */ const char *ip_ntoa(const IP *ip); /* * ip_parse_addr * parses IP structure into an address string * * input * ip: ip of AF_INET or AF_INET6 families * length: length of the address buffer * Must be at least INET_ADDRSTRLEN for AF_INET * and INET6_ADDRSTRLEN for AF_INET6 * * output * address: dotted notation (IPv4: quad, IPv6: 16) or colon notation (IPv6) * * returns 1 on success, 0 on failure */ int ip_parse_addr(const IP *ip, char *address, size_t length); /* * addr_parse_ip * directly parses the input into an IP structure * tries IPv4 first, then IPv6 * * input * address: dotted notation (IPv4: quad, IPv6: 16) or colon notation (IPv6) * * output * IP: family and the value is set on success * * returns 1 on success, 0 on failure */ int addr_parse_ip(const char *address, IP *to); /* ip_equal * compares two IPAny structures * unset means unequal * * returns 0 when not equal or when uninitialized */ int ip_equal(const IP *a, const IP *b); /* ipport_equal * compares two IPAny_Port structures * unset means unequal * * returns 0 when not equal or when uninitialized */ int ipport_equal(const IP_Port *a, const IP_Port *b); /* nulls out ip */ void ip_reset(IP *ip); /* nulls out ip, sets family according to flag */ void ip_init(IP *ip, uint8_t ipv6enabled); /* checks if ip is valid */ int ip_isset(const IP *ip); /* checks if ip is valid */ int ipport_isset(const IP_Port *ipport); /* copies an ip structure */ void ip_copy(IP *target, const IP *source); /* copies an ip_port structure */ void ipport_copy(IP_Port *target, const IP_Port *source); /* * addr_resolve(): * uses getaddrinfo to resolve an address into an IP address * uses the first IPv4/IPv6 addresses returned by getaddrinfo * * input * address: a hostname (or something parseable to an IP address) * to: to.family MUST be initialized, either set to a specific IP version * (AF_INET/AF_INET6) or to the unspecified AF_UNSPEC (= 0), if both * IP versions are acceptable * extra can be NULL and is only set in special circumstances, see returns * * returns in *to a valid IPAny (v4/v6), * prefers v6 if ip.family was AF_UNSPEC and both available * returns in *extra an IPv4 address, if family was AF_UNSPEC and *to is AF_INET6 * returns 0 on failure */ int addr_resolve(const char *address, IP *to, IP *extra); /* * addr_resolve_or_parse_ip * resolves string into an IP address * * address: a hostname (or something parseable to an IP address) * to: to.family MUST be initialized, either set to a specific IP version * (AF_INET/AF_INET6) or to the unspecified AF_UNSPEC (= 0), if both * IP versions are acceptable * extra can be NULL and is only set in special circumstances, see returns * * returns in *tro a matching address (IPv6 or IPv4) * returns in *extra, if not NULL, an IPv4 address, if to->family was AF_UNSPEC * returns 1 on success * returns 0 on failure */ int addr_resolve_or_parse_ip(const char *address, IP *to, IP *extra); /* Function to receive data, ip and port of sender is put into ip_port. * Packet data is put into data. * Packet length is put into length. */ typedef int (*packet_handler_callback)(void *object, IP_Port ip_port, const uint8_t *data, uint16_t len); typedef struct { packet_handler_callback function; void *object; } Packet_Handles; typedef struct { Packet_Handles packethandlers[256]; sa_family_t family; uint16_t port; /* Our UDP socket. */ sock_t sock; } Networking_Core; /* Run this before creating sockets. * * return 0 on success * return -1 on failure */ int networking_at_startup(void); /* Check if socket is valid. * * return 1 if valid * return 0 if not valid */ int sock_valid(sock_t sock); /* Close the socket. */ void kill_sock(sock_t sock); /* Set socket as nonblocking * * return 1 on success * return 0 on failure */ int set_socket_nonblock(sock_t sock); /* Set socket to not emit SIGPIPE * * return 1 on success * return 0 on failure */ int set_socket_nosigpipe(sock_t sock); /* Enable SO_REUSEADDR on socket. * * return 1 on success * return 0 on failure */ int set_socket_reuseaddr(sock_t sock); /* Set socket to dual (IPv4 + IPv6 socket) * * return 1 on success * return 0 on failure */ int set_socket_dualstack(sock_t sock); /* return current monotonic time in milliseconds (ms). */ uint64_t current_time_monotonic(void); /* Basic network functions: */ /* Function to send packet(data) of length length to ip_port. */ int sendpacket(Networking_Core *net, IP_Port ip_port, const uint8_t *data, uint16_t length); /* Function to call when packet beginning with byte is received. */ void networking_registerhandler(Networking_Core *net, uint8_t byte, packet_handler_callback cb, void *object); /* Call this several times a second. */ void networking_poll(Networking_Core *net); /* Initialize networking. * bind to ip and port. * ip must be in network order EX: 127.0.0.1 = (7F000001). * port is in host byte order (this means don't worry about it). * * return Networking_Core object if no problems * return NULL if there are problems. * * If error is non NULL it is set to 0 if no issues, 1 if socket related error, 2 if other. */ Networking_Core *new_networking(IP ip, uint16_t port); Networking_Core *new_networking_ex(IP ip, uint16_t port_from, uint16_t port_to, unsigned int *error); /* Function to cleanup networking stuff (doesn't do much right now). */ void kill_networking(Networking_Core *net); #endif ================================================ FILE: toxcore/onion.c ================================================ /* * onion.c -- Implementation of the onion part of docs/Prevent_Tracking.txt * * 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 "onion.h" #include "util.h" #define RETURN_1 ONION_RETURN_1 #define RETURN_2 ONION_RETURN_2 #define RETURN_3 ONION_RETURN_3 #define SEND_BASE ONION_SEND_BASE #define SEND_3 ONION_SEND_3 #define SEND_2 ONION_SEND_2 #define SEND_1 ONION_SEND_1 /* Change symmetric keys every 2 hours to make paths expire eventually. */ #define KEY_REFRESH_INTERVAL (2 * 60 * 60) static void change_symmetric_key(Onion *onion) { if (is_timeout(onion->timestamp, KEY_REFRESH_INTERVAL)) { new_symmetric_key(onion->secret_symmetric_key); onion->timestamp = unix_time(); } } /* packing and unpacking functions */ static void ip_pack(uint8_t *data, IP source) { to_net_family(&source); data[0] = source.family; if (source.family == TOX_AF_INET || source.family == TOX_TCP_INET) { memset(data + 1, 0, SIZE_IP6); memcpy(data + 1, source.ip4.uint8, SIZE_IP4); } else { memcpy(data + 1, source.ip6.uint8, SIZE_IP6); } } /* return 0 on success, -1 on failure. */ static int ip_unpack(IP *target, const uint8_t *data, unsigned int data_size, _Bool disable_family_check) { if (data_size < (1 + SIZE_IP6)) return -1; target->family = data[0]; if (target->family == TOX_AF_INET || target->family == TOX_TCP_INET) { memcpy(target->ip4.uint8, data + 1, SIZE_IP4); } else { memcpy(target->ip6.uint8, data + 1, SIZE_IP6); } if (!disable_family_check) { return to_host_family(target); } else { to_host_family(target); return 0; } } static void ipport_pack(uint8_t *data, const IP_Port *source) { ip_pack(data, source->ip); memcpy(data + SIZE_IP, &source->port, SIZE_PORT); } /* return 0 on success, -1 on failure. */ static int ipport_unpack(IP_Port *target, const uint8_t *data, unsigned int data_size, _Bool disable_family_check) { if (data_size < (SIZE_IP + SIZE_PORT)) return -1; if (ip_unpack(&target->ip, data, data_size, disable_family_check) == -1) return -1; memcpy(&target->port, data + SIZE_IP, SIZE_PORT); return 0; } /* Create a new onion path. * * Create a new onion path out of nodes (nodes is a list of ONION_PATH_LENGTH nodes) * * new_path must be an empty memory location of atleast Onion_Path size. * * return -1 on failure. * return 0 on success. */ int create_onion_path(const DHT *dht, Onion_Path *new_path, const Node_format *nodes) { if (!new_path || !nodes) return -1; encrypt_precompute(nodes[0].public_key, dht->self_secret_key, new_path->shared_key1); memcpy(new_path->public_key1, dht->self_public_key, crypto_box_PUBLICKEYBYTES); uint8_t random_public_key[crypto_box_PUBLICKEYBYTES]; uint8_t random_secret_key[crypto_box_SECRETKEYBYTES]; crypto_box_keypair(random_public_key, random_secret_key); encrypt_precompute(nodes[1].public_key, random_secret_key, new_path->shared_key2); memcpy(new_path->public_key2, random_public_key, crypto_box_PUBLICKEYBYTES); crypto_box_keypair(random_public_key, random_secret_key); encrypt_precompute(nodes[2].public_key, random_secret_key, new_path->shared_key3); memcpy(new_path->public_key3, random_public_key, crypto_box_PUBLICKEYBYTES); new_path->ip_port1 = nodes[0].ip_port; new_path->ip_port2 = nodes[1].ip_port; new_path->ip_port3 = nodes[2].ip_port; memcpy(new_path->node_public_key1, nodes[0].public_key, crypto_box_PUBLICKEYBYTES); memcpy(new_path->node_public_key2, nodes[1].public_key, crypto_box_PUBLICKEYBYTES); memcpy(new_path->node_public_key3, nodes[2].public_key, crypto_box_PUBLICKEYBYTES); return 0; } /* Dump nodes in onion path to nodes of length num_nodes; * * return -1 on failure. * return 0 on success. */ int onion_path_to_nodes(Node_format *nodes, unsigned int num_nodes, const Onion_Path *path) { if (num_nodes < ONION_PATH_LENGTH) return -1; nodes[0].ip_port = path->ip_port1; nodes[1].ip_port = path->ip_port2; nodes[2].ip_port = path->ip_port3; memcpy(nodes[0].public_key, path->node_public_key1, crypto_box_PUBLICKEYBYTES); memcpy(nodes[1].public_key, path->node_public_key2, crypto_box_PUBLICKEYBYTES); memcpy(nodes[2].public_key, path->node_public_key3, crypto_box_PUBLICKEYBYTES); return 0; } /* Create a onion packet. * * Use Onion_Path path to create packet for data of length to dest. * Maximum length of data is ONION_MAX_DATA_SIZE. * packet should be at least ONION_MAX_PACKET_SIZE big. * * return -1 on failure. * return length of created packet on success. */ int create_onion_packet(uint8_t *packet, uint16_t max_packet_length, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length) { if (1 + length + SEND_1 > max_packet_length || length == 0) return -1; uint8_t step1[SIZE_IPPORT + length]; ipport_pack(step1, &dest); memcpy(step1 + SIZE_IPPORT, data, length); uint8_t nonce[crypto_box_NONCEBYTES]; random_nonce(nonce); uint8_t step2[SIZE_IPPORT + SEND_BASE + length]; ipport_pack(step2, &path->ip_port3); memcpy(step2 + SIZE_IPPORT, path->public_key3, crypto_box_PUBLICKEYBYTES); int len = encrypt_data_symmetric(path->shared_key3, nonce, step1, sizeof(step1), step2 + SIZE_IPPORT + crypto_box_PUBLICKEYBYTES); if (len != SIZE_IPPORT + length + crypto_box_MACBYTES) return -1; uint8_t step3[SIZE_IPPORT + SEND_BASE * 2 + length]; ipport_pack(step3, &path->ip_port2); memcpy(step3 + SIZE_IPPORT, path->public_key2, crypto_box_PUBLICKEYBYTES); len = encrypt_data_symmetric(path->shared_key2, nonce, step2, sizeof(step2), step3 + SIZE_IPPORT + crypto_box_PUBLICKEYBYTES); if (len != SIZE_IPPORT + SEND_BASE + length + crypto_box_MACBYTES) return -1; packet[0] = NET_PACKET_ONION_SEND_INITIAL; memcpy(packet + 1, nonce, crypto_box_NONCEBYTES); memcpy(packet + 1 + crypto_box_NONCEBYTES, path->public_key1, crypto_box_PUBLICKEYBYTES); len = encrypt_data_symmetric(path->shared_key1, nonce, step3, sizeof(step3), packet + 1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES); if (len != SIZE_IPPORT + SEND_BASE * 2 + length + crypto_box_MACBYTES) return -1; return 1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + len; } /* Create a onion packet to be sent over tcp. * * Use Onion_Path path to create packet for data of length to dest. * Maximum length of data is ONION_MAX_DATA_SIZE. * packet should be at least ONION_MAX_PACKET_SIZE big. * * return -1 on failure. * return length of created packet on success. */ int create_onion_packet_tcp(uint8_t *packet, uint16_t max_packet_length, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length) { if (crypto_box_NONCEBYTES + SIZE_IPPORT + SEND_BASE * 2 + length > max_packet_length || length == 0) return -1; uint8_t step1[SIZE_IPPORT + length]; ipport_pack(step1, &dest); memcpy(step1 + SIZE_IPPORT, data, length); uint8_t nonce[crypto_box_NONCEBYTES]; random_nonce(nonce); uint8_t step2[SIZE_IPPORT + SEND_BASE + length]; ipport_pack(step2, &path->ip_port3); memcpy(step2 + SIZE_IPPORT, path->public_key3, crypto_box_PUBLICKEYBYTES); int len = encrypt_data_symmetric(path->shared_key3, nonce, step1, sizeof(step1), step2 + SIZE_IPPORT + crypto_box_PUBLICKEYBYTES); if (len != SIZE_IPPORT + length + crypto_box_MACBYTES) return -1; ipport_pack(packet + crypto_box_NONCEBYTES, &path->ip_port2); memcpy(packet + crypto_box_NONCEBYTES + SIZE_IPPORT, path->public_key2, crypto_box_PUBLICKEYBYTES); len = encrypt_data_symmetric(path->shared_key2, nonce, step2, sizeof(step2), packet + crypto_box_NONCEBYTES + SIZE_IPPORT + crypto_box_PUBLICKEYBYTES); if (len != SIZE_IPPORT + SEND_BASE + length + crypto_box_MACBYTES) return -1; memcpy(packet, nonce, crypto_box_NONCEBYTES); return crypto_box_NONCEBYTES + SIZE_IPPORT + crypto_box_PUBLICKEYBYTES + len; } /* Create and send a onion packet. * * Use Onion_Path path to send data of length to dest. * Maximum length of data is ONION_MAX_DATA_SIZE. * * return -1 on failure. * return 0 on success. */ int send_onion_packet(Networking_Core *net, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length) { uint8_t packet[ONION_MAX_PACKET_SIZE]; int len = create_onion_packet(packet, sizeof(packet), path, dest, data, length); if (len == -1) return -1; if (sendpacket(net, path->ip_port1, packet, len) != len) return -1; return 0; } /* Create and send a onion response sent initially to dest with. * Maximum length of data is ONION_RESPONSE_MAX_DATA_SIZE. * * return -1 on failure. * return 0 on success. */ int send_onion_response(Networking_Core *net, IP_Port dest, const uint8_t *data, uint16_t length, const uint8_t *ret) { if (length > ONION_RESPONSE_MAX_DATA_SIZE || length == 0) return -1; uint8_t packet[1 + RETURN_3 + length]; packet[0] = NET_PACKET_ONION_RECV_3; memcpy(packet + 1, ret, RETURN_3); memcpy(packet + 1 + RETURN_3, data, length); if ((uint32_t)sendpacket(net, dest, packet, sizeof(packet)) != sizeof(packet)) return -1; return 0; } static int handle_send_initial(void *object, IP_Port source, const uint8_t *packet, uint16_t length) { Onion *onion = object; if (length > ONION_MAX_PACKET_SIZE) return 1; if (length <= 1 + SEND_1) return 1; change_symmetric_key(onion); uint8_t plain[ONION_MAX_PACKET_SIZE]; uint8_t shared_key[crypto_box_BEFORENMBYTES]; get_shared_key(&onion->shared_keys_1, shared_key, onion->dht->self_secret_key, packet + 1 + crypto_box_NONCEBYTES); int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES, length - (1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES), plain); if (len != length - (1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + crypto_box_MACBYTES)) return 1; return onion_send_1(onion, plain, len, source, packet + 1); } int onion_send_1(const Onion *onion, const uint8_t *plain, uint16_t len, IP_Port source, const uint8_t *nonce) { if (len > ONION_MAX_PACKET_SIZE + SIZE_IPPORT - (1 + crypto_box_NONCEBYTES + ONION_RETURN_1)) return 1; if (len <= SIZE_IPPORT + SEND_BASE * 2) return 1; IP_Port send_to; if (ipport_unpack(&send_to, plain, len, 0) == -1) return 1; uint8_t ip_port[SIZE_IPPORT]; ipport_pack(ip_port, &source); uint8_t data[ONION_MAX_PACKET_SIZE]; data[0] = NET_PACKET_ONION_SEND_1; memcpy(data + 1, nonce, crypto_box_NONCEBYTES); memcpy(data + 1 + crypto_box_NONCEBYTES, plain + SIZE_IPPORT, len - SIZE_IPPORT); uint16_t data_len = 1 + crypto_box_NONCEBYTES + (len - SIZE_IPPORT); uint8_t *ret_part = data + data_len; new_nonce(ret_part); len = encrypt_data_symmetric(onion->secret_symmetric_key, ret_part, ip_port, SIZE_IPPORT, ret_part + crypto_box_NONCEBYTES); if (len != SIZE_IPPORT + crypto_box_MACBYTES) return 1; data_len += crypto_box_NONCEBYTES + len; if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) return 1; return 0; } static int handle_send_1(void *object, IP_Port source, const uint8_t *packet, uint16_t length) { Onion *onion = object; if (length > ONION_MAX_PACKET_SIZE) return 1; if (length <= 1 + SEND_2) return 1; change_symmetric_key(onion); uint8_t plain[ONION_MAX_PACKET_SIZE]; uint8_t shared_key[crypto_box_BEFORENMBYTES]; get_shared_key(&onion->shared_keys_2, shared_key, onion->dht->self_secret_key, packet + 1 + crypto_box_NONCEBYTES); int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES, length - (1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + RETURN_1), plain); if (len != length - (1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + RETURN_1 + crypto_box_MACBYTES)) return 1; IP_Port send_to; if (ipport_unpack(&send_to, plain, len, 0) == -1) return 1; uint8_t data[ONION_MAX_PACKET_SIZE]; data[0] = NET_PACKET_ONION_SEND_2; memcpy(data + 1, packet + 1, crypto_box_NONCEBYTES); memcpy(data + 1 + crypto_box_NONCEBYTES, plain + SIZE_IPPORT, len - SIZE_IPPORT); uint16_t data_len = 1 + crypto_box_NONCEBYTES + (len - SIZE_IPPORT); uint8_t *ret_part = data + data_len; new_nonce(ret_part); uint8_t ret_data[RETURN_1 + SIZE_IPPORT]; ipport_pack(ret_data, &source); memcpy(ret_data + SIZE_IPPORT, packet + (length - RETURN_1), RETURN_1); len = encrypt_data_symmetric(onion->secret_symmetric_key, ret_part, ret_data, sizeof(ret_data), ret_part + crypto_box_NONCEBYTES); if (len != RETURN_2 - crypto_box_NONCEBYTES) return 1; data_len += crypto_box_NONCEBYTES + len; if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) return 1; return 0; } static int handle_send_2(void *object, IP_Port source, const uint8_t *packet, uint16_t length) { Onion *onion = object; if (length > ONION_MAX_PACKET_SIZE) return 1; if (length <= 1 + SEND_3) return 1; change_symmetric_key(onion); uint8_t plain[ONION_MAX_PACKET_SIZE]; uint8_t shared_key[crypto_box_BEFORENMBYTES]; get_shared_key(&onion->shared_keys_3, shared_key, onion->dht->self_secret_key, packet + 1 + crypto_box_NONCEBYTES); int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES, length - (1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + RETURN_2), plain); if (len != length - (1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + RETURN_2 + crypto_box_MACBYTES)) return 1; if (len <= SIZE_IPPORT) { return 1; } if (plain[SIZE_IPPORT] != NET_PACKET_ANNOUNCE_REQUEST && plain[SIZE_IPPORT] != NET_PACKET_ONION_DATA_REQUEST) { return 1; } IP_Port send_to; if (ipport_unpack(&send_to, plain, len, 0) == -1) return 1; uint8_t data[ONION_MAX_PACKET_SIZE]; memcpy(data, plain + SIZE_IPPORT, len - SIZE_IPPORT); uint16_t data_len = (len - SIZE_IPPORT); uint8_t *ret_part = data + (len - SIZE_IPPORT); new_nonce(ret_part); uint8_t ret_data[RETURN_2 + SIZE_IPPORT]; ipport_pack(ret_data, &source); memcpy(ret_data + SIZE_IPPORT, packet + (length - RETURN_2), RETURN_2); len = encrypt_data_symmetric(onion->secret_symmetric_key, ret_part, ret_data, sizeof(ret_data), ret_part + crypto_box_NONCEBYTES); if (len != RETURN_3 - crypto_box_NONCEBYTES) return 1; data_len += RETURN_3; if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) return 1; return 0; } static int handle_recv_3(void *object, IP_Port source, const uint8_t *packet, uint16_t length) { Onion *onion = object; if (length > ONION_MAX_PACKET_SIZE) return 1; if (length <= 1 + RETURN_3) return 1; if (packet[1 + RETURN_3] != NET_PACKET_ANNOUNCE_RESPONSE && packet[1 + RETURN_3] != NET_PACKET_ONION_DATA_RESPONSE) { return 1; } change_symmetric_key(onion); uint8_t plain[SIZE_IPPORT + RETURN_2]; int len = decrypt_data_symmetric(onion->secret_symmetric_key, packet + 1, packet + 1 + crypto_box_NONCEBYTES, SIZE_IPPORT + RETURN_2 + crypto_box_MACBYTES, plain); if ((uint32_t)len != sizeof(plain)) return 1; IP_Port send_to; if (ipport_unpack(&send_to, plain, len, 0) == -1) return 1; uint8_t data[ONION_MAX_PACKET_SIZE]; data[0] = NET_PACKET_ONION_RECV_2; memcpy(data + 1, plain + SIZE_IPPORT, RETURN_2); memcpy(data + 1 + RETURN_2, packet + 1 + RETURN_3, length - (1 + RETURN_3)); uint16_t data_len = 1 + RETURN_2 + (length - (1 + RETURN_3)); if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) return 1; return 0; } static int handle_recv_2(void *object, IP_Port source, const uint8_t *packet, uint16_t length) { Onion *onion = object; if (length > ONION_MAX_PACKET_SIZE) return 1; if (length <= 1 + RETURN_2) return 1; if (packet[1 + RETURN_2] != NET_PACKET_ANNOUNCE_RESPONSE && packet[1 + RETURN_2] != NET_PACKET_ONION_DATA_RESPONSE) { return 1; } change_symmetric_key(onion); uint8_t plain[SIZE_IPPORT + RETURN_1]; int len = decrypt_data_symmetric(onion->secret_symmetric_key, packet + 1, packet + 1 + crypto_box_NONCEBYTES, SIZE_IPPORT + RETURN_1 + crypto_box_MACBYTES, plain); if ((uint32_t)len != sizeof(plain)) return 1; IP_Port send_to; if (ipport_unpack(&send_to, plain, len, 0) == -1) return 1; uint8_t data[ONION_MAX_PACKET_SIZE]; data[0] = NET_PACKET_ONION_RECV_1; memcpy(data + 1, plain + SIZE_IPPORT, RETURN_1); memcpy(data + 1 + RETURN_1, packet + 1 + RETURN_2, length - (1 + RETURN_2)); uint16_t data_len = 1 + RETURN_1 + (length - (1 + RETURN_2)); if ((uint32_t)sendpacket(onion->net, send_to, data, data_len) != data_len) return 1; return 0; } static int handle_recv_1(void *object, IP_Port source, const uint8_t *packet, uint16_t length) { Onion *onion = object; if (length > ONION_MAX_PACKET_SIZE) return 1; if (length <= 1 + RETURN_1) return 1; if (packet[1 + RETURN_1] != NET_PACKET_ANNOUNCE_RESPONSE && packet[1 + RETURN_1] != NET_PACKET_ONION_DATA_RESPONSE) { return 1; } change_symmetric_key(onion); uint8_t plain[SIZE_IPPORT]; int len = decrypt_data_symmetric(onion->secret_symmetric_key, packet + 1, packet + 1 + crypto_box_NONCEBYTES, SIZE_IPPORT + crypto_box_MACBYTES, plain); if ((uint32_t)len != SIZE_IPPORT) return 1; IP_Port send_to; if (ipport_unpack(&send_to, plain, len, 1) == -1) return 1; uint16_t data_len = length - (1 + RETURN_1); if (onion->recv_1_function && send_to.ip.family != AF_INET && send_to.ip.family != AF_INET6) return onion->recv_1_function(onion->callback_object, send_to, packet + (1 + RETURN_1), data_len); if ((uint32_t)sendpacket(onion->net, send_to, packet + (1 + RETURN_1), data_len) != data_len) return 1; return 0; } void set_callback_handle_recv_1(Onion *onion, int (*function)(void *, IP_Port, const uint8_t *, uint16_t), void *object) { onion->recv_1_function = function; onion->callback_object = object; } Onion *new_onion(DHT *dht) { if (dht == NULL) return NULL; Onion *onion = calloc(1, sizeof(Onion)); if (onion == NULL) return NULL; onion->dht = dht; onion->net = dht->net; new_symmetric_key(onion->secret_symmetric_key); onion->timestamp = unix_time(); networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_INITIAL, &handle_send_initial, onion); networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_1, &handle_send_1, onion); networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_2, &handle_send_2, onion); networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_3, &handle_recv_3, onion); networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_2, &handle_recv_2, onion); networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_1, &handle_recv_1, onion); return onion; } void kill_onion(Onion *onion) { if (onion == NULL) return; networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_INITIAL, NULL, NULL); networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_1, NULL, NULL); networking_registerhandler(onion->net, NET_PACKET_ONION_SEND_2, NULL, NULL); networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_3, NULL, NULL); networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_2, NULL, NULL); networking_registerhandler(onion->net, NET_PACKET_ONION_RECV_1, NULL, NULL); free(onion); } ================================================ FILE: toxcore/onion.h ================================================ /* * onion.h -- Implementation of the onion part of docs/Prevent_Tracking.txt * * 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 ONION_H #define ONION_H #include "DHT.h" typedef struct { DHT *dht; Networking_Core *net; uint8_t secret_symmetric_key[crypto_box_KEYBYTES]; uint64_t timestamp; Shared_Keys shared_keys_1; Shared_Keys shared_keys_2; Shared_Keys shared_keys_3; int (*recv_1_function)(void *, IP_Port, const uint8_t *, uint16_t); void *callback_object; } Onion; #define ONION_MAX_PACKET_SIZE 1400 #define ONION_RETURN_1 (crypto_box_NONCEBYTES + SIZE_IPPORT + crypto_box_MACBYTES) #define ONION_RETURN_2 (crypto_box_NONCEBYTES + SIZE_IPPORT + crypto_box_MACBYTES + ONION_RETURN_1) #define ONION_RETURN_3 (crypto_box_NONCEBYTES + SIZE_IPPORT + crypto_box_MACBYTES + ONION_RETURN_2) #define ONION_SEND_BASE (crypto_box_PUBLICKEYBYTES + SIZE_IPPORT + crypto_box_MACBYTES) #define ONION_SEND_3 (crypto_box_NONCEBYTES + ONION_SEND_BASE + ONION_RETURN_2) #define ONION_SEND_2 (crypto_box_NONCEBYTES + ONION_SEND_BASE*2 + ONION_RETURN_1) #define ONION_SEND_1 (crypto_box_NONCEBYTES + ONION_SEND_BASE*3) #define ONION_MAX_DATA_SIZE (ONION_MAX_PACKET_SIZE - (ONION_SEND_1 + 1)) #define ONION_RESPONSE_MAX_DATA_SIZE (ONION_MAX_PACKET_SIZE - (1 + ONION_RETURN_3)) #define ONION_PATH_LENGTH 3 typedef struct { uint8_t shared_key1[crypto_box_BEFORENMBYTES]; uint8_t shared_key2[crypto_box_BEFORENMBYTES]; uint8_t shared_key3[crypto_box_BEFORENMBYTES]; uint8_t public_key1[crypto_box_PUBLICKEYBYTES]; uint8_t public_key2[crypto_box_PUBLICKEYBYTES]; uint8_t public_key3[crypto_box_PUBLICKEYBYTES]; IP_Port ip_port1; uint8_t node_public_key1[crypto_box_PUBLICKEYBYTES]; IP_Port ip_port2; uint8_t node_public_key2[crypto_box_PUBLICKEYBYTES]; IP_Port ip_port3; uint8_t node_public_key3[crypto_box_PUBLICKEYBYTES]; uint32_t path_num; } Onion_Path; /* Create a new onion path. * * Create a new onion path out of nodes (nodes is a list of ONION_PATH_LENGTH nodes) * * new_path must be an empty memory location of atleast Onion_Path size. * * return -1 on failure. * return 0 on success. */ int create_onion_path(const DHT *dht, Onion_Path *new_path, const Node_format *nodes); /* Dump nodes in onion path to nodes of length num_nodes; * * return -1 on failure. * return 0 on success. */ int onion_path_to_nodes(Node_format *nodes, unsigned int num_nodes, const Onion_Path *path); /* Create a onion packet. * * Use Onion_Path path to create packet for data of length to dest. * Maximum length of data is ONION_MAX_DATA_SIZE. * packet should be at least ONION_MAX_PACKET_SIZE big. * * return -1 on failure. * return length of created packet on success. */ int create_onion_packet(uint8_t *packet, uint16_t max_packet_length, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length); /* Create a onion packet to be sent over tcp. * * Use Onion_Path path to create packet for data of length to dest. * Maximum length of data is ONION_MAX_DATA_SIZE. * packet should be at least ONION_MAX_PACKET_SIZE big. * * return -1 on failure. * return length of created packet on success. */ int create_onion_packet_tcp(uint8_t *packet, uint16_t max_packet_length, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length); /* Create and send a onion packet. * * Use Onion_Path path to send data of length to dest. * Maximum length of data is ONION_MAX_DATA_SIZE. * * return -1 on failure. * return 0 on success. */ int send_onion_packet(Networking_Core *net, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length); /* Create and send a onion response sent initially to dest with. * Maximum length of data is ONION_RESPONSE_MAX_DATA_SIZE. * * return -1 on failure. * return 0 on success. */ int send_onion_response(Networking_Core *net, IP_Port dest, const uint8_t *data, uint16_t length, const uint8_t *ret); /* Function to handle/send received decrypted versions of the packet sent with send_onion_packet. * * return 0 on success. * return 1 on failure. * * Used to handle these packets that are received in a non traditional way (by TCP for example). * * Source family must be set to something else than AF_INET6 or AF_INET so that the callback gets called * when the response is received. */ int onion_send_1(const Onion *onion, const uint8_t *plain, uint16_t len, IP_Port source, const uint8_t *nonce); /* Set the callback to be called when the dest ip_port doesn't have AF_INET6 or AF_INET as the family. * * Format: function(void *object, IP_Port dest, uint8_t *data, uint16_t length) */ void set_callback_handle_recv_1(Onion *onion, int (*function)(void *, IP_Port, const uint8_t *, uint16_t), void *object); Onion *new_onion(DHT *dht); void kill_onion(Onion *onion); #endif ================================================ FILE: toxcore/onion_announce.c ================================================ /* * onion_announce.c -- Implementation of the announce part of docs/Prevent_Tracking.txt * * 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 "onion_announce.h" #include "LAN_discovery.h" #include "util.h" #define PING_ID_TIMEOUT 20 #define ANNOUNCE_REQUEST_SIZE_RECV (ONION_ANNOUNCE_REQUEST_SIZE + ONION_RETURN_3) #define DATA_REQUEST_MIN_SIZE ONION_DATA_REQUEST_MIN_SIZE #define DATA_REQUEST_MIN_SIZE_RECV (DATA_REQUEST_MIN_SIZE + ONION_RETURN_3) /* Create an onion announce request packet in packet of max_packet_length (recommended size ONION_ANNOUNCE_REQUEST_SIZE). * * dest_client_id is the public key of the node the packet will be sent to. * public_key and secret_key is the kepair which will be used to encrypt the request. * ping_id is the ping id that will be sent in the request. * client_id is the client id of the node we are searching for. * data_public_key is the public key we want others to encrypt their data packets with. * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to * receive back in the response. * * return -1 on failure. * return packet length on success. */ int create_announce_request(uint8_t *packet, uint16_t max_packet_length, const uint8_t *dest_client_id, const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id, const uint8_t *data_public_key, uint64_t sendback_data) { if (max_packet_length < ONION_ANNOUNCE_REQUEST_SIZE) return -1; uint8_t plain[ONION_PING_ID_SIZE + crypto_box_PUBLICKEYBYTES + crypto_box_PUBLICKEYBYTES + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH]; memcpy(plain, ping_id, ONION_PING_ID_SIZE); memcpy(plain + ONION_PING_ID_SIZE, client_id, crypto_box_PUBLICKEYBYTES); memcpy(plain + ONION_PING_ID_SIZE + crypto_box_PUBLICKEYBYTES, data_public_key, crypto_box_PUBLICKEYBYTES); memcpy(plain + ONION_PING_ID_SIZE + crypto_box_PUBLICKEYBYTES + crypto_box_PUBLICKEYBYTES, &sendback_data, sizeof(sendback_data)); packet[0] = NET_PACKET_ANNOUNCE_REQUEST; random_nonce(packet + 1); int len = encrypt_data(dest_client_id, secret_key, packet + 1, plain, sizeof(plain), packet + 1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES); if ((uint32_t)len + 1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES != ONION_ANNOUNCE_REQUEST_SIZE) return -1; memcpy(packet + 1 + crypto_box_NONCEBYTES, public_key, crypto_box_PUBLICKEYBYTES); return ONION_ANNOUNCE_REQUEST_SIZE; } /* Create an onion data request packet in packet of max_packet_length (recommended size ONION_MAX_PACKET_SIZE). * * public_key is the real public key of the node which we want to send the data of length length to. * encrypt_public_key is the public key used to encrypt the data packet. * * nonce is the nonce to encrypt this packet with * * return -1 on failure. * return 0 on success. */ int create_data_request(uint8_t *packet, uint16_t max_packet_length, const uint8_t *public_key, const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length) { if (DATA_REQUEST_MIN_SIZE + length > max_packet_length) return -1; if ((unsigned int)DATA_REQUEST_MIN_SIZE + length > ONION_MAX_DATA_SIZE) return -1; packet[0] = NET_PACKET_ONION_DATA_REQUEST; memcpy(packet + 1, public_key, crypto_box_PUBLICKEYBYTES); memcpy(packet + 1 + crypto_box_PUBLICKEYBYTES, nonce, crypto_box_NONCEBYTES); uint8_t random_public_key[crypto_box_PUBLICKEYBYTES]; uint8_t random_secret_key[crypto_box_SECRETKEYBYTES]; crypto_box_keypair(random_public_key, random_secret_key); memcpy(packet + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES, random_public_key, crypto_box_PUBLICKEYBYTES); int len = encrypt_data(encrypt_public_key, random_secret_key, packet + 1 + crypto_box_PUBLICKEYBYTES, data, length, packet + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES); if (1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + len != DATA_REQUEST_MIN_SIZE + length) return -1; return DATA_REQUEST_MIN_SIZE + length; } /* Create and send an onion announce request packet. * * path is the path the request will take before it is sent to dest. * * public_key and secret_key is the kepair which will be used to encrypt the request. * ping_id is the ping id that will be sent in the request. * client_id is the client id of the node we are searching for. * data_public_key is the public key we want others to encrypt their data packets with. * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to * receive back in the response. * * return -1 on failure. * return 0 on success. */ int send_announce_request(Networking_Core *net, const Onion_Path *path, Node_format dest, const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id, const uint8_t *data_public_key, uint64_t sendback_data) { uint8_t request[ONION_ANNOUNCE_REQUEST_SIZE]; int len = create_announce_request(request, sizeof(request), dest.public_key, public_key, secret_key, ping_id, client_id, data_public_key, sendback_data); if (len != sizeof(request)) return -1; uint8_t packet[ONION_MAX_PACKET_SIZE]; len = create_onion_packet(packet, sizeof(packet), path, dest.ip_port, request, sizeof(request)); if (len == -1) return -1; if (sendpacket(net, path->ip_port1, packet, len) != len) return -1; return 0; } /* Create and send an onion data request packet. * * path is the path the request will take before it is sent to dest. * (if dest knows the person with the public_key they should * send the packet to that person in the form of a response) * * public_key is the real public key of the node which we want to send the data of length length to. * encrypt_public_key is the public key used to encrypt the data packet. * * nonce is the nonce to encrypt this packet with * * return -1 on failure. * return 0 on success. */ int send_data_request(Networking_Core *net, const Onion_Path *path, IP_Port dest, const uint8_t *public_key, const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length) { uint8_t request[ONION_MAX_DATA_SIZE]; int len = create_data_request(request, sizeof(request), public_key, encrypt_public_key, nonce, data, length); if (len == -1) return -1; uint8_t packet[ONION_MAX_PACKET_SIZE]; len = create_onion_packet(packet, sizeof(packet), path, dest, request, len); if (len == -1) return -1; if (sendpacket(net, path->ip_port1, packet, len) != len) return -1; return 0; } /* Generate a ping_id and put it in ping_id */ static void generate_ping_id(const Onion_Announce *onion_a, uint64_t time, const uint8_t *public_key, IP_Port ret_ip_port, uint8_t *ping_id) { time /= PING_ID_TIMEOUT; uint8_t data[crypto_box_KEYBYTES + sizeof(time) + crypto_box_PUBLICKEYBYTES + sizeof(ret_ip_port)]; memcpy(data, onion_a->secret_bytes, crypto_box_KEYBYTES); memcpy(data + crypto_box_KEYBYTES, &time, sizeof(time)); memcpy(data + crypto_box_KEYBYTES + sizeof(time), public_key, crypto_box_PUBLICKEYBYTES); memcpy(data + crypto_box_KEYBYTES + sizeof(time) + crypto_box_PUBLICKEYBYTES, &ret_ip_port, sizeof(ret_ip_port)); crypto_hash_sha256(ping_id, data, sizeof(data)); } /* check if public key is in entries list * * return -1 if no * return position in list if yes */ static int in_entries(const Onion_Announce *onion_a, const uint8_t *public_key) { unsigned int i; for (i = 0; i < ONION_ANNOUNCE_MAX_ENTRIES; ++i) { if (!is_timeout(onion_a->entries[i].time, ONION_ANNOUNCE_TIMEOUT) && public_key_cmp(onion_a->entries[i].public_key, public_key) == 0) return i; } return -1; } static uint8_t cmp_public_key[crypto_box_PUBLICKEYBYTES]; static int cmp_entry(const void *a, const void *b) { Onion_Announce_Entry entry1, entry2; memcpy(&entry1, a, sizeof(Onion_Announce_Entry)); memcpy(&entry2, b, sizeof(Onion_Announce_Entry)); int t1 = is_timeout(entry1.time, ONION_ANNOUNCE_TIMEOUT); int t2 = is_timeout(entry2.time, ONION_ANNOUNCE_TIMEOUT); if (t1 && t2) return 0; 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; } /* add entry to entries list * * return -1 if failure * return position if added */ static int add_to_entries(Onion_Announce *onion_a, IP_Port ret_ip_port, const uint8_t *public_key, const uint8_t *data_public_key, const uint8_t *ret) { int pos = in_entries(onion_a, public_key); unsigned int i; if (pos == -1) { for (i = 0; i < ONION_ANNOUNCE_MAX_ENTRIES; ++i) { if (is_timeout(onion_a->entries[i].time, ONION_ANNOUNCE_TIMEOUT)) pos = i; } } if (pos == -1) { if (id_closest(onion_a->dht->self_public_key, public_key, onion_a->entries[0].public_key) == 1) pos = 0; } if (pos == -1) return -1; memcpy(onion_a->entries[pos].public_key, public_key, crypto_box_PUBLICKEYBYTES); onion_a->entries[pos].ret_ip_port = ret_ip_port; memcpy(onion_a->entries[pos].ret, ret, ONION_RETURN_3); memcpy(onion_a->entries[pos].data_public_key, data_public_key, crypto_box_PUBLICKEYBYTES); onion_a->entries[pos].time = unix_time(); memcpy(cmp_public_key, onion_a->dht->self_public_key, crypto_box_PUBLICKEYBYTES); qsort(onion_a->entries, ONION_ANNOUNCE_MAX_ENTRIES, sizeof(Onion_Announce_Entry), cmp_entry); return in_entries(onion_a, public_key); } static int handle_announce_request(void *object, IP_Port source, const uint8_t *packet, uint16_t length) { Onion_Announce *onion_a = object; if (length != ANNOUNCE_REQUEST_SIZE_RECV) return 1; const uint8_t *packet_public_key = packet + 1 + crypto_box_NONCEBYTES; uint8_t shared_key[crypto_box_BEFORENMBYTES]; get_shared_key(&onion_a->shared_keys_recv, shared_key, onion_a->dht->self_secret_key, packet_public_key); uint8_t plain[ONION_PING_ID_SIZE + crypto_box_PUBLICKEYBYTES + crypto_box_PUBLICKEYBYTES + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH]; int len = decrypt_data_symmetric(shared_key, packet + 1, packet + 1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES, ONION_PING_ID_SIZE + crypto_box_PUBLICKEYBYTES + crypto_box_PUBLICKEYBYTES + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + crypto_box_MACBYTES, plain); if ((uint32_t)len != sizeof(plain)) return 1; uint8_t ping_id1[ONION_PING_ID_SIZE]; generate_ping_id(onion_a, unix_time(), packet_public_key, source, ping_id1); uint8_t ping_id2[ONION_PING_ID_SIZE]; generate_ping_id(onion_a, unix_time() + PING_ID_TIMEOUT, packet_public_key, source, ping_id2); int index = -1; uint8_t *data_public_key = plain + ONION_PING_ID_SIZE + crypto_box_PUBLICKEYBYTES; if (sodium_memcmp(ping_id1, plain, ONION_PING_ID_SIZE) == 0 || sodium_memcmp(ping_id2, plain, ONION_PING_ID_SIZE) == 0) { index = add_to_entries(onion_a, source, packet_public_key, data_public_key, packet + (ANNOUNCE_REQUEST_SIZE_RECV - ONION_RETURN_3)); } else { index = in_entries(onion_a, plain + ONION_PING_ID_SIZE); } /*Respond with a announce response packet*/ Node_format nodes_list[MAX_SENT_NODES]; unsigned int num_nodes = get_close_nodes(onion_a->dht, plain + ONION_PING_ID_SIZE, nodes_list, 0, LAN_ip(source.ip) == 0, 1); uint8_t nonce[crypto_box_NONCEBYTES]; random_nonce(nonce); uint8_t pl[1 + ONION_PING_ID_SIZE + sizeof(nodes_list)]; if (index == -1) { pl[0] = 0; memcpy(pl + 1, ping_id2, ONION_PING_ID_SIZE); } else { if (public_key_cmp(onion_a->entries[index].public_key, packet_public_key) == 0) { if (public_key_cmp(onion_a->entries[index].data_public_key, data_public_key) != 0) { pl[0] = 0; memcpy(pl + 1, ping_id2, ONION_PING_ID_SIZE); } else { pl[0] = 2; memcpy(pl + 1, ping_id2, ONION_PING_ID_SIZE); } } else { pl[0] = 1; memcpy(pl + 1, onion_a->entries[index].data_public_key, crypto_box_PUBLICKEYBYTES); } } int nodes_length = 0; if (num_nodes != 0) { nodes_length = pack_nodes(pl + 1 + ONION_PING_ID_SIZE, sizeof(nodes_list), nodes_list, num_nodes); if (nodes_length <= 0) return 1; } uint8_t data[ONION_ANNOUNCE_RESPONSE_MAX_SIZE]; len = encrypt_data_symmetric(shared_key, nonce, pl, 1 + ONION_PING_ID_SIZE + nodes_length, data + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + crypto_box_NONCEBYTES); if (len != 1 + ONION_PING_ID_SIZE + nodes_length + crypto_box_MACBYTES) return 1; data[0] = NET_PACKET_ANNOUNCE_RESPONSE; memcpy(data + 1, plain + ONION_PING_ID_SIZE + crypto_box_PUBLICKEYBYTES + crypto_box_PUBLICKEYBYTES, ONION_ANNOUNCE_SENDBACK_DATA_LENGTH); memcpy(data + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, nonce, crypto_box_NONCEBYTES); if (send_onion_response(onion_a->net, source, data, 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + crypto_box_NONCEBYTES + len, packet + (ANNOUNCE_REQUEST_SIZE_RECV - ONION_RETURN_3)) == -1) return 1; return 0; } static int handle_data_request(void *object, IP_Port source, const uint8_t *packet, uint16_t length) { Onion_Announce *onion_a = object; if (length <= DATA_REQUEST_MIN_SIZE_RECV) return 1; if (length > ONION_MAX_PACKET_SIZE) return 1; int index = in_entries(onion_a, packet + 1); if (index == -1) return 1; uint8_t data[length - (crypto_box_PUBLICKEYBYTES + ONION_RETURN_3)]; data[0] = NET_PACKET_ONION_DATA_RESPONSE; memcpy(data + 1, packet + 1 + crypto_box_PUBLICKEYBYTES, length - (1 + crypto_box_PUBLICKEYBYTES + ONION_RETURN_3)); if (send_onion_response(onion_a->net, onion_a->entries[index].ret_ip_port, data, sizeof(data), onion_a->entries[index].ret) == -1) return 1; return 0; } Onion_Announce *new_onion_announce(DHT *dht) { if (dht == NULL) return NULL; Onion_Announce *onion_a = calloc(1, sizeof(Onion_Announce)); if (onion_a == NULL) return NULL; onion_a->dht = dht; onion_a->net = dht->net; new_symmetric_key(onion_a->secret_bytes); networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST, &handle_announce_request, onion_a); networking_registerhandler(onion_a->net, NET_PACKET_ONION_DATA_REQUEST, &handle_data_request, onion_a); return onion_a; } void kill_onion_announce(Onion_Announce *onion_a) { if (onion_a == NULL) return; networking_registerhandler(onion_a->net, NET_PACKET_ANNOUNCE_REQUEST, NULL, NULL); networking_registerhandler(onion_a->net, NET_PACKET_ONION_DATA_REQUEST, NULL, NULL); free(onion_a); } ================================================ FILE: toxcore/onion_announce.h ================================================ /* * onion_announce.h -- Implementation of the announce part of docs/Prevent_Tracking.txt * * 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 ONION_ANNOUNCE_H #define ONION_ANNOUNCE_H #include "onion.h" #define ONION_ANNOUNCE_MAX_ENTRIES 160 #define ONION_ANNOUNCE_TIMEOUT 300 #define ONION_PING_ID_SIZE crypto_hash_sha256_BYTES #define ONION_ANNOUNCE_SENDBACK_DATA_LENGTH (sizeof(uint64_t)) #define ONION_ANNOUNCE_REQUEST_SIZE (1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + ONION_PING_ID_SIZE + crypto_box_PUBLICKEYBYTES + crypto_box_PUBLICKEYBYTES + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + crypto_box_MACBYTES) #define ONION_ANNOUNCE_RESPONSE_MIN_SIZE (1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + crypto_box_NONCEBYTES + 1 + ONION_PING_ID_SIZE + crypto_box_MACBYTES) #define ONION_ANNOUNCE_RESPONSE_MAX_SIZE (ONION_ANNOUNCE_RESPONSE_MIN_SIZE + sizeof(Node_format)*MAX_SENT_NODES) #define ONION_DATA_RESPONSE_MIN_SIZE (1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + crypto_box_MACBYTES) #if ONION_PING_ID_SIZE != crypto_box_PUBLICKEYBYTES #error announce response packets assume that ONION_PING_ID_SIZE is equal to crypto_box_PUBLICKEYBYTES #endif #define ONION_DATA_REQUEST_MIN_SIZE (1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + crypto_box_MACBYTES) #define MAX_DATA_REQUEST_SIZE (ONION_MAX_DATA_SIZE - ONION_DATA_REQUEST_MIN_SIZE) typedef struct { uint8_t public_key[crypto_box_PUBLICKEYBYTES]; IP_Port ret_ip_port; uint8_t ret[ONION_RETURN_3]; uint8_t data_public_key[crypto_box_PUBLICKEYBYTES]; uint64_t time; } Onion_Announce_Entry; typedef struct { DHT *dht; Networking_Core *net; Onion_Announce_Entry entries[ONION_ANNOUNCE_MAX_ENTRIES]; /* This is crypto_box_KEYBYTES long just so we can use new_symmetric_key() to fill it */ uint8_t secret_bytes[crypto_box_KEYBYTES]; Shared_Keys shared_keys_recv; } Onion_Announce; /* Create an onion announce request packet in packet of max_packet_length (recommended size ONION_ANNOUNCE_REQUEST_SIZE). * * dest_client_id is the public key of the node the packet will be sent to. * public_key and secret_key is the kepair which will be used to encrypt the request. * ping_id is the ping id that will be sent in the request. * client_id is the client id of the node we are searching for. * data_public_key is the public key we want others to encrypt their data packets with. * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to * receive back in the response. * * return -1 on failure. * return packet length on success. */ int create_announce_request(uint8_t *packet, uint16_t max_packet_length, const uint8_t *dest_client_id, const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id, const uint8_t *data_public_key, uint64_t sendback_data); /* Create an onion data request packet in packet of max_packet_length (recommended size ONION_MAX_PACKET_SIZE). * * public_key is the real public key of the node which we want to send the data of length length to. * encrypt_public_key is the public key used to encrypt the data packet. * * nonce is the nonce to encrypt this packet with * * return -1 on failure. * return 0 on success. */ int create_data_request(uint8_t *packet, uint16_t max_packet_length, const uint8_t *public_key, const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length); /* Create and send an onion announce request packet. * * path is the path the request will take before it is sent to dest. * * public_key and secret_key is the kepair which will be used to encrypt the request. * ping_id is the ping id that will be sent in the request. * client_id is the client id of the node we are searching for. * data_public_key is the public key we want others to encrypt their data packets with. * sendback_data is the data of ONION_ANNOUNCE_SENDBACK_DATA_LENGTH length that we expect to * receive back in the response. * * return -1 on failure. * return 0 on success. */ int send_announce_request(Networking_Core *net, const Onion_Path *path, Node_format dest, const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *ping_id, const uint8_t *client_id, const uint8_t *data_public_key, uint64_t sendback_data); /* Create and send an onion data request packet. * * path is the path the request will take before it is sent to dest. * (if dest knows the person with the public_key they should * send the packet to that person in the form of a response) * * public_key is the real public key of the node which we want to send the data of length length to. * encrypt_public_key is the public key used to encrypt the data packet. * * nonce is the nonce to encrypt this packet with * * The maximum length of data is MAX_DATA_REQUEST_SIZE. * * return -1 on failure. * return 0 on success. */ int send_data_request(Networking_Core *net, const Onion_Path *path, IP_Port dest, const uint8_t *public_key, const uint8_t *encrypt_public_key, const uint8_t *nonce, const uint8_t *data, uint16_t length); Onion_Announce *new_onion_announce(DHT *dht); void kill_onion_announce(Onion_Announce *onion_a); #endif ================================================ FILE: toxcore/onion_client.c ================================================ /* * onion_client.c -- Implementation of the client part of docs/Prevent_Tracking.txt * (The part that uses the onion stuff to connect to the friend) * * 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 "onion_client.h" #include "util.h" #include "LAN_discovery.h" /* defines for the array size and timeout for onion announce packets. */ #define ANNOUNCE_ARRAY_SIZE 256 #define ANNOUNCE_TIMEOUT 10 /* Add a node to the path_nodes bootstrap array. * * return -1 on failure * return 0 on success */ int onion_add_bs_path_node(Onion_Client *onion_c, IP_Port ip_port, const uint8_t *public_key) { if (ip_port.ip.family != AF_INET && ip_port.ip.family != AF_INET6) return -1; unsigned int i; for (i = 0; i < MAX_PATH_NODES; ++i) { if (public_key_cmp(public_key, onion_c->path_nodes_bs[i].public_key) == 0) return -1; } onion_c->path_nodes_bs[onion_c->path_nodes_index_bs % MAX_PATH_NODES].ip_port = ip_port; memcpy(onion_c->path_nodes_bs[onion_c->path_nodes_index_bs % MAX_PATH_NODES].public_key, public_key, crypto_box_PUBLICKEYBYTES); uint16_t last = onion_c->path_nodes_index_bs; ++onion_c->path_nodes_index_bs; if (onion_c->path_nodes_index_bs < last) onion_c->path_nodes_index_bs = MAX_PATH_NODES + 1; return 0; } /* Add a node to the path_nodes array. * * return -1 on failure * return 0 on success */ static int onion_add_path_node(Onion_Client *onion_c, IP_Port ip_port, const uint8_t *public_key) { if (ip_port.ip.family != AF_INET && ip_port.ip.family != AF_INET6) return -1; unsigned int i; for (i = 0; i < MAX_PATH_NODES; ++i) { if (public_key_cmp(public_key, onion_c->path_nodes[i].public_key) == 0) return -1; } onion_c->path_nodes[onion_c->path_nodes_index % MAX_PATH_NODES].ip_port = ip_port; memcpy(onion_c->path_nodes[onion_c->path_nodes_index % MAX_PATH_NODES].public_key, public_key, crypto_box_PUBLICKEYBYTES); uint16_t last = onion_c->path_nodes_index; ++onion_c->path_nodes_index; if (onion_c->path_nodes_index < last) onion_c->path_nodes_index = MAX_PATH_NODES + 1; return 0; } /* Put up to max_num nodes in nodes. * * return the number of nodes. */ uint16_t onion_backup_nodes(const Onion_Client *onion_c, Node_format *nodes, uint16_t max_num) { unsigned int i; if (!max_num) return 0; unsigned int num_nodes = (onion_c->path_nodes_index < MAX_PATH_NODES) ? onion_c->path_nodes_index : MAX_PATH_NODES; if (num_nodes == 0) return 0; if (num_nodes < max_num) max_num = num_nodes; for (i = 0; i < max_num; ++i) { nodes[i] = onion_c->path_nodes[(onion_c->path_nodes_index - (1 + i)) % num_nodes]; } return max_num; } /* Put up to max_num random nodes in nodes. * * return the number of nodes. */ static uint16_t random_nodes_path_onion(const Onion_Client *onion_c, Node_format *nodes, uint16_t max_num) { unsigned int i; if (!max_num) return 0; unsigned int num_nodes = (onion_c->path_nodes_index < MAX_PATH_NODES) ? onion_c->path_nodes_index : MAX_PATH_NODES; //if (DHT_non_lan_connected(onion_c->dht)) { if (DHT_isconnected(onion_c->dht)) { if (num_nodes == 0) return 0; for (i = 0; i < max_num; ++i) { nodes[i] = onion_c->path_nodes[rand() % num_nodes]; } } else { int random_tcp = get_random_tcp_con_number(onion_c->c); if (random_tcp == -1) { return 0; } if (num_nodes >= 2) { nodes[0].ip_port.ip.family = TCP_FAMILY; nodes[0].ip_port.ip.ip4.uint32 = random_tcp; for (i = 1; i < max_num; ++i) { nodes[i] = onion_c->path_nodes[rand() % num_nodes]; } } else { unsigned int num_nodes_bs = (onion_c->path_nodes_index_bs < MAX_PATH_NODES) ? onion_c->path_nodes_index_bs : MAX_PATH_NODES; if (num_nodes_bs == 0) return 0; nodes[0].ip_port.ip.family = TCP_FAMILY; nodes[0].ip_port.ip.ip4.uint32 = random_tcp; for (i = 1; i < max_num; ++i) { nodes[i] = onion_c->path_nodes_bs[rand() % num_nodes_bs]; } } } return max_num; } /* * return -1 if nodes are suitable for creating a new path. * return path number of already existing similar path if one already exists. */ static int is_path_used(const Onion_Client_Paths *onion_paths, const Node_format *nodes) { unsigned int i; for (i = 0; i < NUMBER_ONION_PATHS; ++i) { if (is_timeout(onion_paths->last_path_success[i], ONION_PATH_TIMEOUT)) { continue; } if (is_timeout(onion_paths->path_creation_time[i], ONION_PATH_MAX_LIFETIME)) { continue; } // TODO: do we really have to check it with the last node? if (ipport_equal(&onion_paths->paths[i].ip_port1, &nodes[ONION_PATH_LENGTH - 1].ip_port)) { return i; } } return -1; } /* is path timed out */ static _Bool path_timed_out(Onion_Client_Paths *onion_paths, uint32_t pathnum) { pathnum = pathnum % NUMBER_ONION_PATHS; return ((onion_paths->last_path_success[pathnum] + ONION_PATH_TIMEOUT < onion_paths->last_path_used[pathnum] && onion_paths->last_path_used_times[pathnum] >= ONION_PATH_MAX_NO_RESPONSE_USES) || is_timeout(onion_paths->path_creation_time[pathnum], ONION_PATH_MAX_LIFETIME)); } /* Create a new path or use an old suitable one (if pathnum is valid) * or a random one from onion_paths. * * return -1 on failure * return 0 on success * * TODO: Make this function better, it currently probably is vulnerable to some attacks that * could de anonimize us. */ static int random_path(const Onion_Client *onion_c, Onion_Client_Paths *onion_paths, uint32_t pathnum, Onion_Path *path) { if (pathnum == UINT32_MAX) { pathnum = rand() % NUMBER_ONION_PATHS; } else { pathnum = pathnum % NUMBER_ONION_PATHS; } if (path_timed_out(onion_paths, pathnum)) { Node_format nodes[ONION_PATH_LENGTH]; if (random_nodes_path_onion(onion_c, nodes, ONION_PATH_LENGTH) != ONION_PATH_LENGTH) return -1; int n = is_path_used(onion_paths, nodes); if (n == -1) { if (create_onion_path(onion_c->dht, &onion_paths->paths[pathnum], nodes) == -1) return -1; onion_paths->last_path_success[pathnum] = unix_time() + ONION_PATH_FIRST_TIMEOUT - ONION_PATH_TIMEOUT; onion_paths->path_creation_time[pathnum] = unix_time(); onion_paths->last_path_used_times[pathnum] = ONION_PATH_MAX_NO_RESPONSE_USES / 2; uint32_t path_num = rand(); path_num /= NUMBER_ONION_PATHS; path_num *= NUMBER_ONION_PATHS; path_num += pathnum; onion_paths->paths[pathnum].path_num = path_num; } else { pathnum = n; } } ++onion_paths->last_path_used_times[pathnum]; onion_paths->last_path_used[pathnum] = unix_time(); memcpy(path, &onion_paths->paths[pathnum], sizeof(Onion_Path)); return 0; } /* Does path with path_num exist. */ static _Bool path_exists(Onion_Client_Paths *onion_paths, uint32_t path_num) { if (path_timed_out(onion_paths, path_num)) return 0; return onion_paths->paths[path_num % NUMBER_ONION_PATHS].path_num == path_num; } /* Set path timeouts, return the path number. * */ static uint32_t set_path_timeouts(Onion_Client *onion_c, uint32_t num, uint32_t path_num) { if (num > onion_c->num_friends) return -1; Onion_Client_Paths *onion_paths; if (num == 0) { onion_paths = &onion_c->onion_paths_self; } else { onion_paths = &onion_c->onion_paths_friends; } if (onion_paths->paths[path_num % NUMBER_ONION_PATHS].path_num == path_num) { onion_paths->last_path_success[path_num % NUMBER_ONION_PATHS] = unix_time(); onion_paths->last_path_used_times[path_num % NUMBER_ONION_PATHS] = 0; Node_format nodes[ONION_PATH_LENGTH]; if (onion_path_to_nodes(nodes, ONION_PATH_LENGTH, &onion_paths->paths[path_num % NUMBER_ONION_PATHS]) == 0) { unsigned int i; for (i = 0; i < ONION_PATH_LENGTH; ++i) { onion_add_path_node(onion_c, nodes[i].ip_port, nodes[i].public_key); } } return path_num; } return ~0; } /* Function to send onion packet via TCP and UDP. * * return -1 on failure. * return 0 on success. */ static int send_onion_packet_tcp_udp(const Onion_Client *onion_c, const Onion_Path *path, IP_Port dest, const uint8_t *data, uint16_t length) { if (path->ip_port1.ip.family == AF_INET || path->ip_port1.ip.family == AF_INET6) { uint8_t packet[ONION_MAX_PACKET_SIZE]; int len = create_onion_packet(packet, sizeof(packet), path, dest, data, length); if (len == -1) return -1; if (sendpacket(onion_c->net, path->ip_port1, packet, len) != len) return -1; return 0; } else if (path->ip_port1.ip.family == TCP_FAMILY) { uint8_t packet[ONION_MAX_PACKET_SIZE]; int len = create_onion_packet_tcp(packet, sizeof(packet), path, dest, data, length); if (len == -1) return -1; return send_tcp_onion_request(onion_c->c, path->ip_port1.ip.ip4.uint32, packet, len); } else { return -1; } } /* Creates a sendback for use in an announce request. * * num is 0 if we used our secret public key for the announce * num is 1 + friendnum if we use a temporary one. * * Public key is the key we will be sending it to. * ip_port is the ip_port of the node we will be sending * it to. * * sendback must be at least ONION_ANNOUNCE_SENDBACK_DATA_LENGTH big * * return -1 on failure * return 0 on success * */ static int new_sendback(Onion_Client *onion_c, uint32_t num, const uint8_t *public_key, IP_Port ip_port, uint32_t path_num, uint64_t *sendback) { uint8_t data[sizeof(uint32_t) + crypto_box_PUBLICKEYBYTES + sizeof(IP_Port) + sizeof(uint32_t)]; memcpy(data, &num, sizeof(uint32_t)); memcpy(data + sizeof(uint32_t), public_key, crypto_box_PUBLICKEYBYTES); memcpy(data + sizeof(uint32_t) + crypto_box_PUBLICKEYBYTES, &ip_port, sizeof(IP_Port)); memcpy(data + sizeof(uint32_t) + crypto_box_PUBLICKEYBYTES + sizeof(IP_Port), &path_num, sizeof(uint32_t)); *sendback = ping_array_add(&onion_c->announce_ping_array, data, sizeof(data)); if (*sendback == 0) return -1; return 0; } /* Checks if the sendback is valid and returns the public key contained in it in ret_pubkey and the * ip contained in it in ret_ip_port * * sendback is the sendback ONION_ANNOUNCE_SENDBACK_DATA_LENGTH big * ret_pubkey must be at least crypto_box_PUBLICKEYBYTES big * ret_ip_port must be at least 1 big * * return ~0 on failure * return num (see new_sendback(...)) on success */ static uint32_t check_sendback(Onion_Client *onion_c, const uint8_t *sendback, uint8_t *ret_pubkey, IP_Port *ret_ip_port, uint32_t *path_num) { uint64_t sback; memcpy(&sback, sendback, sizeof(uint64_t)); uint8_t data[sizeof(uint32_t) + crypto_box_PUBLICKEYBYTES + sizeof(IP_Port) + sizeof(uint32_t)]; if (ping_array_check(data, sizeof(data), &onion_c->announce_ping_array, sback) != sizeof(data)) return ~0; memcpy(ret_pubkey, data + sizeof(uint32_t), crypto_box_PUBLICKEYBYTES); memcpy(ret_ip_port, data + sizeof(uint32_t) + crypto_box_PUBLICKEYBYTES, sizeof(IP_Port)); memcpy(path_num, data + sizeof(uint32_t) + crypto_box_PUBLICKEYBYTES + sizeof(IP_Port), sizeof(uint32_t)); uint32_t num; memcpy(&num, data, sizeof(uint32_t)); return num; } static int client_send_announce_request(Onion_Client *onion_c, uint32_t num, IP_Port dest, const uint8_t *dest_pubkey, const uint8_t *ping_id, uint32_t pathnum) { if (num > onion_c->num_friends) return -1; uint64_t sendback; Onion_Path path; if (num == 0) { if (random_path(onion_c, &onion_c->onion_paths_self, pathnum, &path) == -1) return -1; } else { if (random_path(onion_c, &onion_c->onion_paths_friends, pathnum, &path) == -1) return -1; } if (new_sendback(onion_c, num, dest_pubkey, dest, path.path_num, &sendback) == -1) return -1; uint8_t zero_ping_id[ONION_PING_ID_SIZE] = {0}; if (ping_id == NULL) ping_id = zero_ping_id; uint8_t request[ONION_ANNOUNCE_REQUEST_SIZE]; int len; if (num == 0) { len = create_announce_request(request, sizeof(request), dest_pubkey, onion_c->c->self_public_key, onion_c->c->self_secret_key, ping_id, onion_c->c->self_public_key, onion_c->temp_public_key, sendback); } else { len = create_announce_request(request, sizeof(request), dest_pubkey, onion_c->friends_list[num - 1].temp_public_key, onion_c->friends_list[num - 1].temp_secret_key, ping_id, onion_c->friends_list[num - 1].real_public_key, zero_ping_id, sendback); } if (len == -1) { return -1; } return send_onion_packet_tcp_udp(onion_c, &path, dest, request, len); } static uint8_t cmp_public_key[crypto_box_PUBLICKEYBYTES]; static int cmp_entry(const void *a, const void *b) { Onion_Node entry1, entry2; memcpy(&entry1, a, sizeof(Onion_Node)); memcpy(&entry2, b, sizeof(Onion_Node)); int t1 = is_timeout(entry1.timestamp, ONION_NODE_TIMEOUT); int t2 = is_timeout(entry2.timestamp, ONION_NODE_TIMEOUT); if (t1 && t2) return 0; 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; } static int client_add_to_list(Onion_Client *onion_c, uint32_t num, const uint8_t *public_key, IP_Port ip_port, uint8_t is_stored, const uint8_t *pingid_or_key, uint32_t path_num) { if (num > onion_c->num_friends) return -1; Onion_Node *list_nodes = NULL; uint8_t *reference_id = NULL; unsigned int list_length; if (num == 0) { list_nodes = onion_c->clients_announce_list; reference_id = onion_c->c->self_public_key; list_length = MAX_ONION_CLIENTS_ANNOUNCE; if (is_stored == 1 && public_key_cmp(pingid_or_key, onion_c->temp_public_key) != 0) { is_stored = 0; } } else { if (is_stored >= 2) return -1; list_nodes = onion_c->friends_list[num - 1].clients_list; reference_id = onion_c->friends_list[num - 1].real_public_key; list_length = MAX_ONION_CLIENTS; } memcpy(cmp_public_key, reference_id, crypto_box_PUBLICKEYBYTES); qsort(list_nodes, list_length, sizeof(Onion_Node), cmp_entry); int index = -1, stored = 0; unsigned int i; if (is_timeout(list_nodes[0].timestamp, ONION_NODE_TIMEOUT) || id_closest(reference_id, list_nodes[0].public_key, public_key) == 2) { index = 0; } for (i = 0; i < list_length; ++i) { if (public_key_cmp(list_nodes[i].public_key, public_key) == 0) { index = i; stored = 1; break; } } if (index == -1) return 0; memcpy(list_nodes[index].public_key, public_key, crypto_box_PUBLICKEYBYTES); list_nodes[index].ip_port = ip_port; //TODO: remove this and find a better source of nodes to use for paths. onion_add_path_node(onion_c, ip_port, public_key); if (is_stored == 1) { memcpy(list_nodes[index].data_public_key, pingid_or_key, crypto_box_PUBLICKEYBYTES); } else { memcpy(list_nodes[index].ping_id, pingid_or_key, ONION_PING_ID_SIZE); } list_nodes[index].is_stored = is_stored; list_nodes[index].timestamp = unix_time(); if (!stored) list_nodes[index].last_pinged = 0; list_nodes[index].path_used = set_path_timeouts(onion_c, num, path_num); return 0; } static int good_to_ping(Last_Pinged *last_pinged, uint8_t *last_pinged_index, const uint8_t *public_key) { unsigned int i; for (i = 0; i < MAX_STORED_PINGED_NODES; ++i) { if (!is_timeout(last_pinged[i].timestamp, MIN_NODE_PING_TIME)) if (public_key_cmp(last_pinged[i].public_key, public_key) == 0) return 0; } memcpy(last_pinged[*last_pinged_index % MAX_STORED_PINGED_NODES].public_key, public_key, crypto_box_PUBLICKEYBYTES); last_pinged[*last_pinged_index % MAX_STORED_PINGED_NODES].timestamp = unix_time(); ++*last_pinged_index; return 1; } static int client_ping_nodes(Onion_Client *onion_c, uint32_t num, const Node_format *nodes, uint16_t num_nodes, IP_Port source) { if (num > onion_c->num_friends) return -1; if (num_nodes == 0) return 0; Onion_Node *list_nodes = NULL; uint8_t *reference_id = NULL; unsigned int list_length; Last_Pinged *last_pinged = NULL; uint8_t *last_pinged_index = NULL; if (num == 0) { list_nodes = onion_c->clients_announce_list; reference_id = onion_c->c->self_public_key; list_length = MAX_ONION_CLIENTS_ANNOUNCE; last_pinged = onion_c->last_pinged; last_pinged_index = &onion_c->last_pinged_index; } else { list_nodes = onion_c->friends_list[num - 1].clients_list; reference_id = onion_c->friends_list[num - 1].real_public_key; list_length = MAX_ONION_CLIENTS; last_pinged = onion_c->friends_list[num - 1].last_pinged; last_pinged_index = &onion_c->friends_list[num - 1].last_pinged_index; } unsigned int i, j; int lan_ips_accepted = (LAN_ip(source.ip) == 0); for (i = 0; i < num_nodes; ++i) { if (!lan_ips_accepted) if (LAN_ip(nodes[i].ip_port.ip) == 0) continue; if (is_timeout(list_nodes[0].timestamp, ONION_NODE_TIMEOUT) || id_closest(reference_id, list_nodes[0].public_key, nodes[i].public_key) == 2 || is_timeout(list_nodes[1].timestamp, ONION_NODE_TIMEOUT) || id_closest(reference_id, list_nodes[1].public_key, nodes[i].public_key) == 2 ) { /* check if node is already in list. */ for (j = 0; j < list_length; ++j) { if (public_key_cmp(list_nodes[j].public_key, nodes[i].public_key) == 0) { break; } } if (j == list_length && good_to_ping(last_pinged, last_pinged_index, nodes[i].public_key)) { client_send_announce_request(onion_c, num, nodes[i].ip_port, nodes[i].public_key, NULL, ~0); } } } return 0; } static int handle_announce_response(void *object, IP_Port source, const uint8_t *packet, uint16_t length) { Onion_Client *onion_c = object; if (length < ONION_ANNOUNCE_RESPONSE_MIN_SIZE || length > ONION_ANNOUNCE_RESPONSE_MAX_SIZE) return 1; uint16_t len_nodes = length - ONION_ANNOUNCE_RESPONSE_MIN_SIZE; uint8_t public_key[crypto_box_PUBLICKEYBYTES]; IP_Port ip_port; uint32_t path_num; uint32_t num = check_sendback(onion_c, packet + 1, public_key, &ip_port, &path_num); if (num > onion_c->num_friends) return 1; uint8_t plain[1 + ONION_PING_ID_SIZE + len_nodes]; int len = -1; if (num == 0) { len = decrypt_data(public_key, onion_c->c->self_secret_key, packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + crypto_box_NONCEBYTES, length - (1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + crypto_box_NONCEBYTES), plain); } else { if (onion_c->friends_list[num - 1].status == 0) return 1; len = decrypt_data(public_key, onion_c->friends_list[num - 1].temp_secret_key, packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH, packet + 1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + crypto_box_NONCEBYTES, length - (1 + ONION_ANNOUNCE_SENDBACK_DATA_LENGTH + crypto_box_NONCEBYTES), plain); } if ((uint32_t)len != sizeof(plain)) return 1; if (client_add_to_list(onion_c, num, public_key, ip_port, plain[0], plain + 1, path_num) == -1) return 1; if (len_nodes != 0) { Node_format nodes[MAX_SENT_NODES]; int num_nodes = unpack_nodes(nodes, MAX_SENT_NODES, 0, plain + 1 + ONION_PING_ID_SIZE, len_nodes, 0); if (num_nodes <= 0) return 1; if (client_ping_nodes(onion_c, num, nodes, num_nodes, source) == -1) return 1; } //TODO: LAN vs non LAN ips?, if we are connected only to LAN, are we offline? onion_c->last_packet_recv = unix_time(); return 0; } #define DATA_IN_RESPONSE_MIN_SIZE ONION_DATA_IN_RESPONSE_MIN_SIZE static int handle_data_response(void *object, IP_Port source, const uint8_t *packet, uint16_t length) { Onion_Client *onion_c = object; if (length <= (ONION_DATA_RESPONSE_MIN_SIZE + DATA_IN_RESPONSE_MIN_SIZE)) return 1; if (length > MAX_DATA_REQUEST_SIZE) return 1; uint8_t temp_plain[length - ONION_DATA_RESPONSE_MIN_SIZE]; int len = decrypt_data(packet + 1 + crypto_box_NONCEBYTES, onion_c->temp_secret_key, packet + 1, packet + 1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES, length - (1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES), temp_plain); if ((uint32_t)len != sizeof(temp_plain)) return 1; uint8_t plain[sizeof(temp_plain) - DATA_IN_RESPONSE_MIN_SIZE]; len = decrypt_data(temp_plain, onion_c->c->self_secret_key, packet + 1, temp_plain + crypto_box_PUBLICKEYBYTES, sizeof(temp_plain) - crypto_box_PUBLICKEYBYTES, plain); if ((uint32_t)len != sizeof(plain)) return 1; if (!onion_c->Onion_Data_Handlers[plain[0]].function) return 1; return onion_c->Onion_Data_Handlers[plain[0]].function(onion_c->Onion_Data_Handlers[plain[0]].object, temp_plain, plain, sizeof(plain)); } #define DHTPK_DATA_MIN_LENGTH (1 + sizeof(uint64_t) + crypto_box_PUBLICKEYBYTES) #define DHTPK_DATA_MAX_LENGTH (DHTPK_DATA_MIN_LENGTH + sizeof(Node_format)*MAX_SENT_NODES) static int handle_dhtpk_announce(void *object, const uint8_t *source_pubkey, const uint8_t *data, uint16_t length) { Onion_Client *onion_c = object; if (length < DHTPK_DATA_MIN_LENGTH) return 1; if (length > DHTPK_DATA_MAX_LENGTH) return 1; int friend_num = onion_friend_num(onion_c, source_pubkey); if (friend_num == -1) return 1; uint64_t no_replay; memcpy(&no_replay, data + 1, sizeof(uint64_t)); net_to_host((uint8_t *) &no_replay, sizeof(no_replay)); if (no_replay <= onion_c->friends_list[friend_num].last_noreplay) return 1; onion_c->friends_list[friend_num].last_noreplay = no_replay; if (onion_c->friends_list[friend_num].dht_pk_callback) onion_c->friends_list[friend_num].dht_pk_callback(onion_c->friends_list[friend_num].dht_pk_callback_object, onion_c->friends_list[friend_num].dht_pk_callback_number, data + 1 + sizeof(uint64_t)); onion_set_friend_DHT_pubkey(onion_c, friend_num, data + 1 + sizeof(uint64_t)); onion_c->friends_list[friend_num].last_seen = unix_time(); uint16_t len_nodes = length - DHTPK_DATA_MIN_LENGTH; if (len_nodes != 0) { Node_format nodes[MAX_SENT_NODES]; int num_nodes = unpack_nodes(nodes, MAX_SENT_NODES, 0, data + 1 + sizeof(uint64_t) + crypto_box_PUBLICKEYBYTES, len_nodes, 1); if (num_nodes <= 0) return 1; int i; for (i = 0; i < num_nodes; ++i) { uint8_t family = nodes[i].ip_port.ip.family; if (family == AF_INET || family == AF_INET6) { DHT_getnodes(onion_c->dht, &nodes[i].ip_port, nodes[i].public_key, onion_c->friends_list[friend_num].dht_public_key); } else if (family == TCP_INET || family == TCP_INET6) { if (onion_c->friends_list[friend_num].tcp_relay_node_callback) { void *obj = onion_c->friends_list[friend_num].tcp_relay_node_callback_object; uint32_t number = onion_c->friends_list[friend_num].tcp_relay_node_callback_number; onion_c->friends_list[friend_num].tcp_relay_node_callback(obj, number, nodes[i].ip_port, nodes[i].public_key); } } } } return 0; } static int handle_tcp_onion(void *object, const uint8_t *data, uint16_t length) { if (length == 0) return 1; IP_Port ip_port = {0}; ip_port.ip.family = TCP_FAMILY; if (data[0] == NET_PACKET_ANNOUNCE_RESPONSE) { return handle_announce_response(object, ip_port, data, length); } else if (data[0] == NET_PACKET_ONION_DATA_RESPONSE) { return handle_data_response(object, ip_port, data, length); } return 1; } /* Send data of length length to friendnum. * This data will be received by the friend using the Onion_Data_Handlers callbacks. * * Even if this function succeeds, the friend might not receive any data. * * return the number of packets sent on success * return -1 on failure. */ int send_onion_data(Onion_Client *onion_c, int friend_num, const uint8_t *data, uint16_t length) { if ((uint32_t)friend_num >= onion_c->num_friends) return -1; if (length + DATA_IN_RESPONSE_MIN_SIZE > MAX_DATA_REQUEST_SIZE) return -1; if (length == 0) return -1; unsigned int i, good_nodes[MAX_ONION_CLIENTS], num_good = 0, num_nodes = 0; Onion_Node *list_nodes = onion_c->friends_list[friend_num].clients_list; for (i = 0; i < MAX_ONION_CLIENTS; ++i) { if (is_timeout(list_nodes[i].timestamp, ONION_NODE_TIMEOUT)) continue; ++num_nodes; if (list_nodes[i].is_stored) { good_nodes[num_good] = i; ++num_good; } } if (num_good < (num_nodes / 4) + 1) return -1; uint8_t nonce[crypto_box_NONCEBYTES]; random_nonce(nonce); uint8_t packet[DATA_IN_RESPONSE_MIN_SIZE + length]; memcpy(packet, onion_c->c->self_public_key, crypto_box_PUBLICKEYBYTES); int len = encrypt_data(onion_c->friends_list[friend_num].real_public_key, onion_c->c->self_secret_key, nonce, data, length, packet + crypto_box_PUBLICKEYBYTES); if ((uint32_t)len + crypto_box_PUBLICKEYBYTES != sizeof(packet)) return -1; unsigned int good = 0; for (i = 0; i < num_good; ++i) { Onion_Path path; if (random_path(onion_c, &onion_c->onion_paths_friends, ~0, &path) == -1) continue; uint8_t o_packet[ONION_MAX_PACKET_SIZE]; len = create_data_request(o_packet, sizeof(o_packet), onion_c->friends_list[friend_num].real_public_key, list_nodes[good_nodes[i]].data_public_key, nonce, packet, sizeof(packet)); if (len == -1) continue; if (send_onion_packet_tcp_udp(onion_c, &path, list_nodes[good_nodes[i]].ip_port, o_packet, len) == 0) ++good; } return good; } /* Try to send the dht public key via the DHT instead of onion * * Even if this function succeeds, the friend might not receive any data. * * return the number of packets sent on success * return -1 on failure. */ static int send_dht_dhtpk(const Onion_Client *onion_c, int friend_num, const uint8_t *data, uint16_t length) { if ((uint32_t)friend_num >= onion_c->num_friends) return -1; if (!onion_c->friends_list[friend_num].know_dht_public_key) return -1; uint8_t nonce[crypto_box_NONCEBYTES]; new_nonce(nonce); uint8_t temp[DATA_IN_RESPONSE_MIN_SIZE + crypto_box_NONCEBYTES + length]; memcpy(temp, onion_c->c->self_public_key, crypto_box_PUBLICKEYBYTES); memcpy(temp + crypto_box_PUBLICKEYBYTES, nonce, crypto_box_NONCEBYTES); int len = encrypt_data(onion_c->friends_list[friend_num].real_public_key, onion_c->c->self_secret_key, nonce, data, length, temp + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES); if ((uint32_t)len + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES != sizeof(temp)) return -1; uint8_t packet[MAX_CRYPTO_REQUEST_SIZE]; len = create_request(onion_c->dht->self_public_key, onion_c->dht->self_secret_key, packet, onion_c->friends_list[friend_num].dht_public_key, temp, sizeof(temp), CRYPTO_PACKET_DHTPK); if (len == -1) return -1; return route_tofriend(onion_c->dht, onion_c->friends_list[friend_num].dht_public_key, packet, len); } static int handle_dht_dhtpk(void *object, IP_Port source, const uint8_t *source_pubkey, const uint8_t *packet, uint16_t length) { Onion_Client *onion_c = object; if (length < DHTPK_DATA_MIN_LENGTH + DATA_IN_RESPONSE_MIN_SIZE + crypto_box_NONCEBYTES) return 1; if (length > DHTPK_DATA_MAX_LENGTH + DATA_IN_RESPONSE_MIN_SIZE + crypto_box_NONCEBYTES) return 1; uint8_t plain[DHTPK_DATA_MAX_LENGTH]; int len = decrypt_data(packet, onion_c->c->self_secret_key, packet + crypto_box_PUBLICKEYBYTES, packet + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES, length - (crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES), plain); if (len != length - (DATA_IN_RESPONSE_MIN_SIZE + crypto_box_NONCEBYTES)) return 1; if (public_key_cmp(source_pubkey, plain + 1 + sizeof(uint64_t)) != 0) return 1; return handle_dhtpk_announce(onion_c, packet, plain, len); } /* Send the packets to tell our friends what our DHT public key is. * * if onion_dht_both is 0, use only the onion to send the packet. * if it is 1, use only the dht. * if it is something else, use both. * * return the number of packets sent on success * return -1 on failure. */ static int send_dhtpk_announce(Onion_Client *onion_c, uint16_t friend_num, uint8_t onion_dht_both) { if (friend_num >= onion_c->num_friends) return -1; uint8_t data[DHTPK_DATA_MAX_LENGTH]; data[0] = ONION_DATA_DHTPK; uint64_t no_replay = unix_time(); host_to_net((uint8_t *)&no_replay, sizeof(no_replay)); memcpy(data + 1, &no_replay, sizeof(no_replay)); memcpy(data + 1 + sizeof(uint64_t), onion_c->dht->self_public_key, crypto_box_PUBLICKEYBYTES); Node_format nodes[MAX_SENT_NODES]; uint16_t num_relays = copy_connected_tcp_relays(onion_c->c, nodes, (MAX_SENT_NODES / 2)); uint16_t num_nodes = closelist_nodes(onion_c->dht, &nodes[num_relays], MAX_SENT_NODES - num_relays); num_nodes += num_relays; int nodes_len = 0; if (num_nodes != 0) { nodes_len = pack_nodes(data + DHTPK_DATA_MIN_LENGTH, DHTPK_DATA_MAX_LENGTH - DHTPK_DATA_MIN_LENGTH, nodes, num_nodes); if (nodes_len <= 0) return -1; } int num1 = -1, num2 = -1; if (onion_dht_both != 1) num1 = send_onion_data(onion_c, friend_num, data, DHTPK_DATA_MIN_LENGTH + nodes_len); if (onion_dht_both != 0) num2 = send_dht_dhtpk(onion_c, friend_num, data, DHTPK_DATA_MIN_LENGTH + nodes_len); if (num1 == -1) return num2; if (num2 == -1) return num1; return num1 + num2; } /* Get the friend_num of a friend. * * return -1 on failure. * return friend number on success. */ int onion_friend_num(const Onion_Client *onion_c, const uint8_t *public_key) { unsigned int i; for (i = 0; i < onion_c->num_friends; ++i) { if (onion_c->friends_list[i].status == 0) continue; if (public_key_cmp(public_key, onion_c->friends_list[i].real_public_key) == 0) return i; } return -1; } /* Set the size of the friend list to num. * * return -1 if realloc fails. * return 0 if it succeeds. */ static int realloc_onion_friends(Onion_Client *onion_c, uint32_t num) { if (num == 0) { free(onion_c->friends_list); onion_c->friends_list = NULL; return 0; } Onion_Friend *newonion_friends = realloc(onion_c->friends_list, num * sizeof(Onion_Friend)); if (newonion_friends == NULL) return -1; onion_c->friends_list = newonion_friends; return 0; } /* Add a friend who we want to connect to. * * return -1 on failure. * return the friend number on success or if the friend was already added. */ int onion_addfriend(Onion_Client *onion_c, const uint8_t *public_key) { int num = onion_friend_num(onion_c, public_key); if (num != -1) return num; unsigned int i, index = ~0; for (i = 0; i < onion_c->num_friends; ++i) { if (onion_c->friends_list[i].status == 0) { index = i; break; } } if (index == (uint32_t)~0) { if (realloc_onion_friends(onion_c, onion_c->num_friends + 1) == -1) return -1; index = onion_c->num_friends; memset(&(onion_c->friends_list[onion_c->num_friends]), 0, sizeof(Onion_Friend)); ++onion_c->num_friends; } onion_c->friends_list[index].status = 1; memcpy(onion_c->friends_list[index].real_public_key, public_key, crypto_box_PUBLICKEYBYTES); crypto_box_keypair(onion_c->friends_list[index].temp_public_key, onion_c->friends_list[index].temp_secret_key); return index; } /* Delete a friend. * * return -1 on failure. * return the deleted friend number on success. */ int onion_delfriend(Onion_Client *onion_c, int friend_num) { if ((uint32_t)friend_num >= onion_c->num_friends) return -1; //if (onion_c->friends_list[friend_num].know_dht_public_key) // DHT_delfriend(onion_c->dht, onion_c->friends_list[friend_num].dht_public_key, 0); sodium_memzero(&(onion_c->friends_list[friend_num]), sizeof(Onion_Friend)); unsigned int i; for (i = onion_c->num_friends; i != 0; --i) { if (onion_c->friends_list[i - 1].status != 0) break; } if (onion_c->num_friends != i) { onion_c->num_friends = i; realloc_onion_friends(onion_c, onion_c->num_friends); } return friend_num; } /* Set the function for this friend that will be callbacked with object and number * when that friends gives us one of the TCP relays he is connected to. * * object and number will be passed as argument to this function. * * return -1 on failure. * return 0 on success. */ int recv_tcp_relay_handler(Onion_Client *onion_c, int friend_num, int (*tcp_relay_node_callback)(void *object, uint32_t number, IP_Port ip_port, const uint8_t *public_key), void *object, uint32_t number) { if ((uint32_t)friend_num >= onion_c->num_friends) return -1; onion_c->friends_list[friend_num].tcp_relay_node_callback = tcp_relay_node_callback; onion_c->friends_list[friend_num].tcp_relay_node_callback_object = object; onion_c->friends_list[friend_num].tcp_relay_node_callback_number = number; return 0; } /* Set the function for this friend that will be callbacked with object and number * when that friend gives us his DHT temporary public key. * * object and number will be passed as argument to this function. * * return -1 on failure. * return 0 on success. */ int onion_dht_pk_callback(Onion_Client *onion_c, int friend_num, void (*function)(void *data, int32_t number, const uint8_t *dht_public_key), void *object, uint32_t number) { if ((uint32_t)friend_num >= onion_c->num_friends) return -1; onion_c->friends_list[friend_num].dht_pk_callback = function; onion_c->friends_list[friend_num].dht_pk_callback_object = object; onion_c->friends_list[friend_num].dht_pk_callback_number = number; return 0; } /* Set a friends DHT public key. * * return -1 on failure. * return 0 on success. */ int onion_set_friend_DHT_pubkey(Onion_Client *onion_c, int friend_num, const uint8_t *dht_key) { if ((uint32_t)friend_num >= onion_c->num_friends) return -1; if (onion_c->friends_list[friend_num].status == 0) return -1; if (onion_c->friends_list[friend_num].know_dht_public_key) { if (public_key_cmp(dht_key, onion_c->friends_list[friend_num].dht_public_key) == 0) { return -1; } onion_c->friends_list[friend_num].know_dht_public_key = 0; } onion_c->friends_list[friend_num].last_seen = unix_time(); onion_c->friends_list[friend_num].know_dht_public_key = 1; memcpy(onion_c->friends_list[friend_num].dht_public_key, dht_key, crypto_box_PUBLICKEYBYTES); return 0; } /* Copy friends DHT public key into dht_key. * * return 0 on failure (no key copied). * return 1 on success (key copied). */ unsigned int onion_getfriend_DHT_pubkey(const Onion_Client *onion_c, int friend_num, uint8_t *dht_key) { if ((uint32_t)friend_num >= onion_c->num_friends) return 0; if (onion_c->friends_list[friend_num].status == 0) return 0; if (!onion_c->friends_list[friend_num].know_dht_public_key) return 0; memcpy(dht_key, onion_c->friends_list[friend_num].dht_public_key, crypto_box_PUBLICKEYBYTES); return 1; } /* Get the ip of friend friendnum and put it in 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 onion_getfriendip(const Onion_Client *onion_c, int friend_num, IP_Port *ip_port) { uint8_t dht_public_key[crypto_box_PUBLICKEYBYTES]; if (onion_getfriend_DHT_pubkey(onion_c, friend_num, dht_public_key) == 0) return -1; return DHT_getfriendip(onion_c->dht, dht_public_key, ip_port); } /* Set if friend is online or not. * NOTE: This function is there and should be used so that we don't send useless packets to the friend if he is online. * * is_online 1 means friend is online. * is_online 0 means friend is offline * * return -1 on failure. * return 0 on success. */ int onion_set_friend_online(Onion_Client *onion_c, int friend_num, uint8_t is_online) { if ((uint32_t)friend_num >= onion_c->num_friends) return -1; if (is_online == 0 && onion_c->friends_list[friend_num].is_online == 1) onion_c->friends_list[friend_num].last_seen = unix_time(); onion_c->friends_list[friend_num].is_online = is_online; /* This should prevent some clock related issues */ if (!is_online) { onion_c->friends_list[friend_num].last_noreplay = 0; onion_c->friends_list[friend_num].run_count = 0; } return 0; } static void populate_path_nodes(Onion_Client *onion_c) { Node_format nodes_list[MAX_FRIEND_CLIENTS]; unsigned int num_nodes = randfriends_nodes(onion_c->dht, nodes_list, MAX_FRIEND_CLIENTS); unsigned int i; for (i = 0; i < num_nodes; ++i) { onion_add_path_node(onion_c, nodes_list[i].ip_port, nodes_list[i].public_key); } } static void populate_path_nodes_tcp(Onion_Client *onion_c) { Node_format nodes_list[MAX_SENT_NODES]; unsigned int num_nodes = copy_connected_tcp_relays(onion_c->c, nodes_list, MAX_SENT_NODES);; unsigned int i; for (i = 0; i < num_nodes; ++i) { onion_add_bs_path_node(onion_c, nodes_list[i].ip_port, nodes_list[i].public_key); } } #define ANNOUNCE_FRIEND (ONION_NODE_PING_INTERVAL * 6) #define ANNOUNCE_FRIEND_BEGINNING 3 #define FRIEND_ONION_NODE_TIMEOUT (ONION_NODE_TIMEOUT * 6) #define RUN_COUNT_FRIEND_ANNOUNCE_BEGINNING 17 static void do_friend(Onion_Client *onion_c, uint16_t friendnum) { if (friendnum >= onion_c->num_friends) return; if (onion_c->friends_list[friendnum].status == 0) return; unsigned int interval = ANNOUNCE_FRIEND; if (onion_c->friends_list[friendnum].run_count < RUN_COUNT_FRIEND_ANNOUNCE_BEGINNING) interval = ANNOUNCE_FRIEND_BEGINNING; unsigned int i, count = 0; Onion_Node *list_nodes = onion_c->friends_list[friendnum].clients_list; if (!onion_c->friends_list[friendnum].is_online) { for (i = 0; i < MAX_ONION_CLIENTS; ++i) { if (is_timeout(list_nodes[i].timestamp, FRIEND_ONION_NODE_TIMEOUT)) continue; ++count; if (list_nodes[i].last_pinged == 0) { list_nodes[i].last_pinged = unix_time(); continue; } if (is_timeout(list_nodes[i].last_pinged, interval)) { if (client_send_announce_request(onion_c, friendnum + 1, list_nodes[i].ip_port, list_nodes[i].public_key, 0, ~0) == 0) { list_nodes[i].last_pinged = unix_time(); } } } if (count != MAX_ONION_CLIENTS) { unsigned int num_nodes = (onion_c->path_nodes_index < MAX_PATH_NODES) ? onion_c->path_nodes_index : MAX_PATH_NODES; unsigned int n = num_nodes; if (num_nodes > (MAX_ONION_CLIENTS / 2)) n = (MAX_ONION_CLIENTS / 2); if (num_nodes != 0) { unsigned int j; for (j = 0; j < n; ++j) { unsigned int num = rand() % num_nodes; client_send_announce_request(onion_c, friendnum + 1, onion_c->path_nodes[num].ip_port, onion_c->path_nodes[num].public_key, 0, ~0); } ++onion_c->friends_list[friendnum].run_count; } } else { ++onion_c->friends_list[friendnum].run_count; } /* send packets to friend telling them our DHT public key. */ if (is_timeout(onion_c->friends_list[friendnum].last_dht_pk_onion_sent, ONION_DHTPK_SEND_INTERVAL)) if (send_dhtpk_announce(onion_c, friendnum, 0) >= 1) onion_c->friends_list[friendnum].last_dht_pk_onion_sent = unix_time(); if (is_timeout(onion_c->friends_list[friendnum].last_dht_pk_dht_sent, DHT_DHTPK_SEND_INTERVAL)) if (send_dhtpk_announce(onion_c, friendnum, 1) >= 1) onion_c->friends_list[friendnum].last_dht_pk_dht_sent = unix_time(); } } /* Function to call when onion data packet with contents beginning with byte is received. */ void oniondata_registerhandler(Onion_Client *onion_c, uint8_t byte, oniondata_handler_callback cb, void *object) { onion_c->Onion_Data_Handlers[byte].function = cb; onion_c->Onion_Data_Handlers[byte].object = object; } #define ANNOUNCE_INTERVAL_NOT_ANNOUNCED 3 #define ANNOUNCE_INTERVAL_ANNOUNCED ONION_NODE_PING_INTERVAL static void do_announce(Onion_Client *onion_c) { unsigned int i, count = 0; Onion_Node *list_nodes = onion_c->clients_announce_list; for (i = 0; i < MAX_ONION_CLIENTS_ANNOUNCE; ++i) { if (is_timeout(list_nodes[i].timestamp, ONION_NODE_TIMEOUT)) continue; ++count; /* Don't announce ourselves the first time this is run to new peers */ if (list_nodes[i].last_pinged == 0) { list_nodes[i].last_pinged = 1; continue; } unsigned int interval = ANNOUNCE_INTERVAL_NOT_ANNOUNCED; if (list_nodes[i].is_stored && path_exists(&onion_c->onion_paths_self, list_nodes[i].path_used)) { interval = ANNOUNCE_INTERVAL_ANNOUNCED; } if (is_timeout(list_nodes[i].last_pinged, interval)) { if (client_send_announce_request(onion_c, 0, list_nodes[i].ip_port, list_nodes[i].public_key, list_nodes[i].ping_id, list_nodes[i].path_used) == 0) { list_nodes[i].last_pinged = unix_time(); } } } if (count != MAX_ONION_CLIENTS_ANNOUNCE) { unsigned int num_nodes; Node_format *path_nodes; if (rand() % 2 == 0 || onion_c->path_nodes_index == 0) { num_nodes = (onion_c->path_nodes_index_bs < MAX_PATH_NODES) ? onion_c->path_nodes_index_bs : MAX_PATH_NODES; path_nodes = onion_c->path_nodes_bs; } else { num_nodes = (onion_c->path_nodes_index < MAX_PATH_NODES) ? onion_c->path_nodes_index : MAX_PATH_NODES; path_nodes = onion_c->path_nodes; } if (count < (uint32_t)rand() % MAX_ONION_CLIENTS_ANNOUNCE) { if (num_nodes != 0) { for (i = 0; i < (MAX_ONION_CLIENTS_ANNOUNCE / 2); ++i) { unsigned int num = rand() % num_nodes; client_send_announce_request(onion_c, 0, path_nodes[num].ip_port, path_nodes[num].public_key, 0, ~0); } } } } } /* return 0 if we are not connected to the network. * return 1 if we are. */ static int onion_isconnected(const Onion_Client *onion_c) { unsigned int i, num = 0, announced = 0; if (is_timeout(onion_c->last_packet_recv, ONION_OFFLINE_TIMEOUT)) return 0; if (onion_c->path_nodes_index == 0) return 0; for (i = 0; i < MAX_ONION_CLIENTS_ANNOUNCE; ++i) { if (!is_timeout(onion_c->clients_announce_list[i].timestamp, ONION_NODE_TIMEOUT)) { ++num; if (onion_c->clients_announce_list[i].is_stored) { ++announced; } } } unsigned int pnodes = onion_c->path_nodes_index; if (pnodes > MAX_ONION_CLIENTS_ANNOUNCE) { pnodes = MAX_ONION_CLIENTS_ANNOUNCE; } /* Consider ourselves online if we are announced to half or more nodes we are connected to */ if (num && announced) { if ((num / 2) <= announced && (pnodes / 2) <= num) return 1; } return 0; } #define ONION_CONNECTION_SECONDS 3 /* return 0 if we are not connected to the network. * return 1 if we are connected with TCP only. * return 2 if we are also connected with UDP. */ unsigned int onion_connection_status(const Onion_Client *onion_c) { if (onion_c->onion_connected >= ONION_CONNECTION_SECONDS) { if (onion_c->UDP_connected) { return 2; } else { return 1; } } return 0; } void do_onion_client(Onion_Client *onion_c) { unsigned int i; if (onion_c->last_run == unix_time()) return; if (is_timeout(onion_c->first_run, ONION_CONNECTION_SECONDS)) { populate_path_nodes(onion_c); do_announce(onion_c); } if (onion_isconnected(onion_c)) { if (onion_c->onion_connected < ONION_CONNECTION_SECONDS * 2) { ++onion_c->onion_connected; } } else { populate_path_nodes_tcp(onion_c); if (onion_c->onion_connected != 0) { --onion_c->onion_connected; } } _Bool UDP_connected = DHT_non_lan_connected(onion_c->dht); if (is_timeout(onion_c->first_run, ONION_CONNECTION_SECONDS * 2)) { set_tcp_onion_status(onion_c->c->tcp_c, !UDP_connected); } onion_c->UDP_connected = UDP_connected || get_random_tcp_onion_conn_number(onion_c->c->tcp_c) == -1; /* Check if connected to any TCP relays. */ if (onion_connection_status(onion_c)) { for (i = 0; i < onion_c->num_friends; ++i) { do_friend(onion_c, i); } } if (onion_c->last_run == 0) { onion_c->first_run = unix_time(); } onion_c->last_run = unix_time(); } Onion_Client *new_onion_client(Net_Crypto *c) { if (c == NULL) return NULL; Onion_Client *onion_c = calloc(1, sizeof(Onion_Client)); if (onion_c == NULL) return NULL; if (ping_array_init(&onion_c->announce_ping_array, ANNOUNCE_ARRAY_SIZE, ANNOUNCE_TIMEOUT) != 0) { free(onion_c); return NULL; } onion_c->dht = c->dht; onion_c->net = c->dht->net; onion_c->c = c; new_symmetric_key(onion_c->secret_symmetric_key); crypto_box_keypair(onion_c->temp_public_key, onion_c->temp_secret_key); networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE, &handle_announce_response, onion_c); networking_registerhandler(onion_c->net, NET_PACKET_ONION_DATA_RESPONSE, &handle_data_response, onion_c); oniondata_registerhandler(onion_c, ONION_DATA_DHTPK, &handle_dhtpk_announce, onion_c); cryptopacket_registerhandler(onion_c->dht, CRYPTO_PACKET_DHTPK, &handle_dht_dhtpk, onion_c); set_onion_packet_tcp_connection_callback(onion_c->c->tcp_c, &handle_tcp_onion, onion_c); return onion_c; } void kill_onion_client(Onion_Client *onion_c) { if (onion_c == NULL) return; ping_array_free_all(&onion_c->announce_ping_array); realloc_onion_friends(onion_c, 0); networking_registerhandler(onion_c->net, NET_PACKET_ANNOUNCE_RESPONSE, NULL, NULL); networking_registerhandler(onion_c->net, NET_PACKET_ONION_DATA_RESPONSE, NULL, NULL); oniondata_registerhandler(onion_c, ONION_DATA_DHTPK, NULL, NULL); cryptopacket_registerhandler(onion_c->dht, CRYPTO_PACKET_DHTPK, NULL, NULL); set_onion_packet_tcp_connection_callback(onion_c->c->tcp_c, NULL, NULL); sodium_memzero(onion_c, sizeof(Onion_Client)); free(onion_c); } ================================================ FILE: toxcore/onion_client.h ================================================ /* * onion_client.h -- Implementation of the client part of docs/Prevent_Tracking.txt * (The part that uses the onion stuff to connect to the friend) * * 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 ONION_CLIENT_H #define ONION_CLIENT_H #include "onion_announce.h" #include "net_crypto.h" #include "ping_array.h" #define MAX_ONION_CLIENTS 8 #define MAX_ONION_CLIENTS_ANNOUNCE 12 /* Number of nodes to announce ourselves to. */ #define ONION_NODE_PING_INTERVAL 15 #define ONION_NODE_TIMEOUT (ONION_NODE_PING_INTERVAL * 3) /* The interval in seconds at which to tell our friends where we are */ #define ONION_DHTPK_SEND_INTERVAL 30 #define DHT_DHTPK_SEND_INTERVAL 20 #define NUMBER_ONION_PATHS 6 /* The timeout the first time the path is added and then for all the next consecutive times */ #define ONION_PATH_FIRST_TIMEOUT 4 #define ONION_PATH_TIMEOUT 10 #define ONION_PATH_MAX_LIFETIME 1200 #define ONION_PATH_MAX_NO_RESPONSE_USES 4 #define MAX_STORED_PINGED_NODES 9 #define MIN_NODE_PING_TIME 10 #define MAX_PATH_NODES 32 /* If no packets are received within that interval tox will * be considered offline. */ #define ONION_OFFLINE_TIMEOUT (ONION_NODE_PING_INTERVAL * 1.25) /* Onion data packet ids. */ #define ONION_DATA_FRIEND_REQ CRYPTO_PACKET_FRIEND_REQ #define ONION_DATA_DHTPK CRYPTO_PACKET_DHTPK typedef struct { uint8_t public_key[crypto_box_PUBLICKEYBYTES]; IP_Port ip_port; uint8_t ping_id[ONION_PING_ID_SIZE]; uint8_t data_public_key[crypto_box_PUBLICKEYBYTES]; uint8_t is_stored; uint64_t timestamp; uint64_t last_pinged; uint32_t path_used; } Onion_Node; typedef struct { Onion_Path paths[NUMBER_ONION_PATHS]; uint64_t last_path_success[NUMBER_ONION_PATHS]; uint64_t last_path_used[NUMBER_ONION_PATHS]; uint64_t path_creation_time[NUMBER_ONION_PATHS]; /* number of times used without success. */ unsigned int last_path_used_times[NUMBER_ONION_PATHS]; } Onion_Client_Paths; typedef struct { uint8_t public_key[crypto_box_PUBLICKEYBYTES]; uint64_t timestamp; } Last_Pinged; typedef struct { uint8_t status; /* 0 if friend is not valid, 1 if friend is valid.*/ uint8_t is_online; /* Set by the onion_set_friend_status function. */ uint8_t know_dht_public_key; /* 0 if we don't know the dht public key of the other, 1 if we do. */ uint8_t dht_public_key[crypto_box_PUBLICKEYBYTES]; uint8_t real_public_key[crypto_box_PUBLICKEYBYTES]; Onion_Node clients_list[MAX_ONION_CLIENTS]; uint8_t temp_public_key[crypto_box_PUBLICKEYBYTES]; uint8_t temp_secret_key[crypto_box_SECRETKEYBYTES]; uint64_t last_dht_pk_onion_sent; uint64_t last_dht_pk_dht_sent; uint64_t last_noreplay; uint64_t last_seen; Last_Pinged last_pinged[MAX_STORED_PINGED_NODES]; uint8_t last_pinged_index; int (*tcp_relay_node_callback)(void *object, uint32_t number, IP_Port ip_port, const uint8_t *public_key); void *tcp_relay_node_callback_object; uint32_t tcp_relay_node_callback_number; void (*dht_pk_callback)(void *data, int32_t number, const uint8_t *dht_public_key); void *dht_pk_callback_object; uint32_t dht_pk_callback_number; uint32_t run_count; } Onion_Friend; typedef int (*oniondata_handler_callback)(void *object, const uint8_t *source_pubkey, const uint8_t *data, uint16_t len); typedef struct { DHT *dht; Net_Crypto *c; Networking_Core *net; Onion_Friend *friends_list; uint16_t num_friends; Onion_Node clients_announce_list[MAX_ONION_CLIENTS_ANNOUNCE]; Onion_Client_Paths onion_paths_self; Onion_Client_Paths onion_paths_friends; uint8_t secret_symmetric_key[crypto_box_KEYBYTES]; uint64_t last_run, first_run; uint8_t temp_public_key[crypto_box_PUBLICKEYBYTES]; uint8_t temp_secret_key[crypto_box_SECRETKEYBYTES]; Last_Pinged last_pinged[MAX_STORED_PINGED_NODES]; Node_format path_nodes[MAX_PATH_NODES]; uint16_t path_nodes_index; Node_format path_nodes_bs[MAX_PATH_NODES]; uint16_t path_nodes_index_bs; Ping_Array announce_ping_array; uint8_t last_pinged_index; struct { oniondata_handler_callback function; void *object; } Onion_Data_Handlers[256]; uint64_t last_packet_recv; unsigned int onion_connected; _Bool UDP_connected; } Onion_Client; /* Add a node to the path_nodes bootstrap array. * * return -1 on failure * return 0 on success */ int onion_add_bs_path_node(Onion_Client *onion_c, IP_Port ip_port, const uint8_t *public_key); /* Put up to max_num nodes in nodes. * * return the number of nodes. */ uint16_t onion_backup_nodes(const Onion_Client *onion_c, Node_format *nodes, uint16_t max_num); /* Add a friend who we want to connect to. * * return -1 on failure. * return the friend number on success or if the friend was already added. */ int onion_friend_num(const Onion_Client *onion_c, const uint8_t *public_key); /* Add a friend who we want to connect to. * * return -1 on failure. * return the friend number on success. */ int onion_addfriend(Onion_Client *onion_c, const uint8_t *public_key); /* Delete a friend. * * return -1 on failure. * return the deleted friend number on success. */ int onion_delfriend(Onion_Client *onion_c, int friend_num); /* Set if friend is online or not. * NOTE: This function is there and should be used so that we don't send useless packets to the friend if he is online. * * is_online 1 means friend is online. * is_online 0 means friend is offline * * return -1 on failure. * return 0 on success. */ int onion_set_friend_online(Onion_Client *onion_c, int friend_num, uint8_t is_online); /* Get the ip of friend friendnum and put it in 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 onion_getfriendip(const Onion_Client *onion_c, int friend_num, IP_Port *ip_port); /* Set the function for this friend that will be callbacked with object and number * when that friends gives us one of the TCP relays he is connected to. * * object and number will be passed as argument to this function. * * return -1 on failure. * return 0 on success. */ int recv_tcp_relay_handler(Onion_Client *onion_c, int friend_num, int (*tcp_relay_node_callback)(void *object, uint32_t number, IP_Port ip_port, const uint8_t *public_key), void *object, uint32_t number); /* Set the function for this friend that will be callbacked with object and number * when that friend gives us his DHT temporary public key. * * object and number will be passed as argument to this function. * * return -1 on failure. * return 0 on success. */ int onion_dht_pk_callback(Onion_Client *onion_c, int friend_num, void (*function)(void *data, int32_t number, const uint8_t *dht_public_key), void *object, uint32_t number); /* Set a friends DHT public key. * timestamp is the time (current_time_monotonic()) at which the key was last confirmed belonging to * the other peer. * * return -1 on failure. * return 0 on success. */ int onion_set_friend_DHT_pubkey(Onion_Client *onion_c, int friend_num, const uint8_t *dht_key); /* Copy friends DHT public key into dht_key. * * return 0 on failure (no key copied). * return 1 on success (key copied). */ unsigned int onion_getfriend_DHT_pubkey(const Onion_Client *onion_c, int friend_num, uint8_t *dht_key); #define ONION_DATA_IN_RESPONSE_MIN_SIZE (crypto_box_PUBLICKEYBYTES + crypto_box_MACBYTES) #define ONION_CLIENT_MAX_DATA_SIZE (MAX_DATA_REQUEST_SIZE - ONION_DATA_IN_RESPONSE_MIN_SIZE) /* Send data of length length to friendnum. * Maximum length of data is ONION_CLIENT_MAX_DATA_SIZE. * This data will be received by the friend using the Onion_Data_Handlers callbacks. * * Even if this function succeeds, the friend might not receive any data. * * return the number of packets sent on success * return -1 on failure. */ int send_onion_data(Onion_Client *onion_c, int friend_num, const uint8_t *data, uint16_t length); /* Function to call when onion data packet with contents beginning with byte is received. */ void oniondata_registerhandler(Onion_Client *onion_c, uint8_t byte, oniondata_handler_callback cb, void *object); void do_onion_client(Onion_Client *onion_c); Onion_Client *new_onion_client(Net_Crypto *c); void kill_onion_client(Onion_Client *onion_c); /* return 0 if we are not connected to the network. * return 1 if we are connected with TCP only. * return 2 if we are also connected with UDP. */ unsigned int onion_connection_status(const Onion_Client *onion_c); #endif ================================================ FILE: toxcore/ping.c ================================================ /* * ping.c -- Buffered pinging using cyclic arrays. * * This file is donated to the Tox Project. * Copyright 2013 plutooo * * 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 "DHT.h" #include "ping.h" #include "network.h" #include "util.h" #include "ping_array.h" #define PING_NUM_MAX 512 /* Maximum newly announced nodes to ping per TIME_TO_PING seconds. */ #define MAX_TO_PING 32 /* Ping newly announced nodes to ping per TIME_TO_PING seconds*/ #define TIME_TO_PING 2 struct PING { DHT *dht; Ping_Array ping_array; Node_format to_ping[MAX_TO_PING]; uint64_t last_to_ping; }; #define PING_PLAIN_SIZE (1 + sizeof(uint64_t)) #define DHT_PING_SIZE (1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + PING_PLAIN_SIZE + crypto_box_MACBYTES) #define PING_DATA_SIZE (crypto_box_PUBLICKEYBYTES + sizeof(IP_Port)) int send_ping_request(PING *ping, IP_Port ipp, const uint8_t *public_key) { uint8_t pk[DHT_PING_SIZE]; int rc; uint64_t ping_id; if (id_equal(public_key, ping->dht->self_public_key)) return 1; uint8_t shared_key[crypto_box_BEFORENMBYTES]; // generate key to encrypt ping_id with recipient privkey DHT_get_shared_key_sent(ping->dht, shared_key, public_key); // Generate random ping_id. uint8_t data[PING_DATA_SIZE]; id_copy(data, public_key); memcpy(data + crypto_box_PUBLICKEYBYTES, &ipp, sizeof(IP_Port)); ping_id = ping_array_add(&ping->ping_array, data, sizeof(data)); if (ping_id == 0) return 1; uint8_t ping_plain[PING_PLAIN_SIZE]; ping_plain[0] = NET_PACKET_PING_REQUEST; memcpy(ping_plain + 1, &ping_id, sizeof(ping_id)); pk[0] = NET_PACKET_PING_REQUEST; id_copy(pk + 1, ping->dht->self_public_key); // Our pubkey new_nonce(pk + 1 + crypto_box_PUBLICKEYBYTES); // Generate new nonce rc = encrypt_data_symmetric(shared_key, pk + 1 + crypto_box_PUBLICKEYBYTES, ping_plain, sizeof(ping_plain), pk + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES); if (rc != PING_PLAIN_SIZE + crypto_box_MACBYTES) return 1; return sendpacket(ping->dht->net, ipp, pk, sizeof(pk)); } static int send_ping_response(PING *ping, IP_Port ipp, const uint8_t *public_key, uint64_t ping_id, uint8_t *shared_encryption_key) { uint8_t pk[DHT_PING_SIZE]; int rc; if (id_equal(public_key, ping->dht->self_public_key)) return 1; uint8_t ping_plain[PING_PLAIN_SIZE]; ping_plain[0] = NET_PACKET_PING_RESPONSE; memcpy(ping_plain + 1, &ping_id, sizeof(ping_id)); pk[0] = NET_PACKET_PING_RESPONSE; id_copy(pk + 1, ping->dht->self_public_key); // Our pubkey new_nonce(pk + 1 + crypto_box_PUBLICKEYBYTES); // Generate new nonce // Encrypt ping_id using recipient privkey rc = encrypt_data_symmetric(shared_encryption_key, pk + 1 + crypto_box_PUBLICKEYBYTES, ping_plain, sizeof(ping_plain), pk + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES ); if (rc != PING_PLAIN_SIZE + crypto_box_MACBYTES) return 1; return sendpacket(ping->dht->net, ipp, pk, sizeof(pk)); } static int handle_ping_request(void *_dht, IP_Port source, const uint8_t *packet, uint16_t length) { DHT *dht = _dht; int rc; if (length != DHT_PING_SIZE) return 1; PING *ping = dht->ping; if (id_equal(packet + 1, ping->dht->self_public_key)) return 1; uint8_t shared_key[crypto_box_BEFORENMBYTES]; uint8_t ping_plain[PING_PLAIN_SIZE]; // Decrypt ping_id DHT_get_shared_key_recv(dht, shared_key, packet + 1); rc = decrypt_data_symmetric(shared_key, packet + 1 + crypto_box_PUBLICKEYBYTES, packet + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES, PING_PLAIN_SIZE + crypto_box_MACBYTES, ping_plain ); if (rc != sizeof(ping_plain)) return 1; if (ping_plain[0] != NET_PACKET_PING_REQUEST) return 1; uint64_t ping_id; memcpy(&ping_id, ping_plain + 1, sizeof(ping_id)); // Send response send_ping_response(ping, source, packet + 1, ping_id, shared_key); add_to_ping(ping, packet + 1, source); return 0; } static int handle_ping_response(void *_dht, IP_Port source, const uint8_t *packet, uint16_t length) { DHT *dht = _dht; int rc; if (length != DHT_PING_SIZE) return 1; PING *ping = dht->ping; if (id_equal(packet + 1, ping->dht->self_public_key)) return 1; uint8_t shared_key[crypto_box_BEFORENMBYTES]; // generate key to encrypt ping_id with recipient privkey DHT_get_shared_key_sent(ping->dht, shared_key, packet + 1); uint8_t ping_plain[PING_PLAIN_SIZE]; // Decrypt ping_id rc = decrypt_data_symmetric(shared_key, packet + 1 + crypto_box_PUBLICKEYBYTES, packet + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES, PING_PLAIN_SIZE + crypto_box_MACBYTES, ping_plain); if (rc != sizeof(ping_plain)) return 1; if (ping_plain[0] != NET_PACKET_PING_RESPONSE) return 1; uint64_t ping_id; memcpy(&ping_id, ping_plain + 1, sizeof(ping_id)); uint8_t data[PING_DATA_SIZE]; if (ping_array_check(data, sizeof(data), &ping->ping_array, ping_id) != sizeof(data)) return 1; if (!id_equal(packet + 1, data)) return 1; IP_Port ipp; memcpy(&ipp, data + crypto_box_PUBLICKEYBYTES, sizeof(IP_Port)); if (!ipport_equal(&ipp, &source)) return 1; addto_lists(dht, source, packet + 1); return 0; } /* Check if public_key with ip_port is in the list. * * return 1 if it is. * return 0 if it isn't. */ static int in_list(const Client_data *list, uint16_t length, const uint8_t *public_key, IP_Port ip_port) { unsigned int i; for (i = 0; i < length; ++i) { if (id_equal(list[i].public_key, public_key)) { const IPPTsPng *ipptp; if (ip_port.ip.family == AF_INET) { ipptp = &list[i].assoc4; } else { ipptp = &list[i].assoc6; } if (!is_timeout(ipptp->timestamp, BAD_NODE_TIMEOUT) && ipport_equal(&ipptp->ip_port, &ip_port)) return 1; } } return 0; } /* Add nodes to the to_ping list. * All nodes in this list are pinged every TIME_TO_PING seconds * and are then removed from the list. * If the list is full the nodes farthest from our public_key are replaced. * The purpose of this list is to enable quick integration of new nodes into the * network while preventing amplification attacks. * * return 0 if node was added. * return -1 if node was not added. */ int add_to_ping(PING *ping, const uint8_t *public_key, IP_Port ip_port) { if (!ip_isset(&ip_port.ip)) return -1; if (!node_addable_to_close_list(ping->dht, public_key, ip_port)) return -1; if (in_list(ping->dht->close_clientlist, LCLIENT_LIST, public_key, ip_port)) return -1; IP_Port temp; if (DHT_getfriendip(ping->dht, public_key, &temp) == 0) { send_ping_request(ping, ip_port, public_key); return -1; } unsigned int i; for (i = 0; i < MAX_TO_PING; ++i) { if (!ip_isset(&ping->to_ping[i].ip_port.ip)) { memcpy(ping->to_ping[i].public_key, public_key, crypto_box_PUBLICKEYBYTES); ipport_copy(&ping->to_ping[i].ip_port, &ip_port); return 0; } if (public_key_cmp(ping->to_ping[i].public_key, public_key) == 0) { return -1; } } if (add_to_list(ping->to_ping, MAX_TO_PING, public_key, ip_port, ping->dht->self_public_key)) return 0; return -1; } /* Ping all the valid nodes in the to_ping list every TIME_TO_PING seconds. * This function must be run at least once every TIME_TO_PING seconds. */ void do_to_ping(PING *ping) { if (!is_timeout(ping->last_to_ping, TIME_TO_PING)) return; if (!ip_isset(&ping->to_ping[0].ip_port.ip)) return; unsigned int i; for (i = 0; i < MAX_TO_PING; ++i) { if (!ip_isset(&ping->to_ping[i].ip_port.ip)) break; if (!node_addable_to_close_list(ping->dht, ping->to_ping[i].public_key, ping->to_ping[i].ip_port)) continue; send_ping_request(ping, ping->to_ping[i].ip_port, ping->to_ping[i].public_key); ip_reset(&ping->to_ping[i].ip_port.ip); } if (i != 0) ping->last_to_ping = unix_time(); } PING *new_ping(DHT *dht) { PING *ping = calloc(1, sizeof(PING)); if (ping == NULL) return NULL; if (ping_array_init(&ping->ping_array, PING_NUM_MAX, PING_TIMEOUT) != 0) { free(ping); return NULL; } ping->dht = dht; networking_registerhandler(ping->dht->net, NET_PACKET_PING_REQUEST, &handle_ping_request, dht); networking_registerhandler(ping->dht->net, NET_PACKET_PING_RESPONSE, &handle_ping_response, dht); return ping; } void kill_ping(PING *ping) { networking_registerhandler(ping->dht->net, NET_PACKET_PING_REQUEST, NULL, NULL); networking_registerhandler(ping->dht->net, NET_PACKET_PING_RESPONSE, NULL, NULL); ping_array_free_all(&ping->ping_array); free(ping); } ================================================ FILE: toxcore/ping.h ================================================ /* * ping.h -- Buffered pinging using cyclic arrays. * * This file is donated to the Tox Project. * Copyright 2013 plutooo * * 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 __PING_H__ #define __PING_H__ typedef struct PING PING; /* Add nodes to the to_ping list. * All nodes in this list are pinged every TIME_TOPING seconds * and are then removed from the list. * If the list is full the nodes farthest from our public_key are replaced. * The purpose of this list is to enable quick integration of new nodes into the * network while preventing amplification attacks. * * return 0 if node was added. * return -1 if node was not added. */ int add_to_ping(PING *ping, const uint8_t *public_key, IP_Port ip_port); void do_to_ping(PING *ping); PING *new_ping(DHT *dht); void kill_ping(PING *ping); int send_ping_request(PING *ping, IP_Port ipp, const uint8_t *public_key); #endif /* __PING_H__ */ ================================================ FILE: toxcore/ping_array.c ================================================ /* ping_array.c * * Implementation of an efficient array to store that we pinged something. * * * 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 "ping_array.h" #include "crypto_core.h" #include "util.h" static void clear_entry(Ping_Array *array, uint32_t index) { free(array->entries[index].data); array->entries[index].data = NULL; array->entries[index].length = array->entries[index].time = array->entries[index].ping_id = 0; } /* Clear timed out entries. */ static void ping_array_clear_timedout(Ping_Array *array) { while (array->last_deleted != array->last_added) { uint32_t index = array->last_deleted % array->total_size; if (!is_timeout(array->entries[index].time, array->timeout)) break; clear_entry(array, index); ++array->last_deleted; } } /* Add a data with length to the Ping_Array list and return a ping_id. * * return ping_id on success. * return 0 on failure. */ uint64_t ping_array_add(Ping_Array *array, const uint8_t *data, uint32_t length) { ping_array_clear_timedout(array); uint32_t index = array->last_added % array->total_size; if (array->entries[index].data != NULL) { array->last_deleted = array->last_added - array->total_size; clear_entry(array, index); } array->entries[index].data = malloc(length); if (array->entries[index].data == NULL) return 0; memcpy(array->entries[index].data, data, length); array->entries[index].length = length; array->entries[index].time = unix_time(); ++array->last_added; uint64_t ping_id = random_64b(); ping_id /= array->total_size; ping_id *= array->total_size; ping_id += index; if (ping_id == 0) ping_id += array->total_size; array->entries[index].ping_id = ping_id; return ping_id; } /* Check if ping_id is valid and not timed out. * * On success, copies the data into data of length, * * return length of data copied on success. * return -1 on failure. */ int ping_array_check(uint8_t *data, uint32_t length, Ping_Array *array, uint64_t ping_id) { if (ping_id == 0) return -1; uint32_t index = ping_id % array->total_size; if (array->entries[index].ping_id != ping_id) return -1; if (is_timeout(array->entries[index].time, array->timeout)) return -1; if (array->entries[index].length > length) return -1; if (array->entries[index].data == NULL) return -1; memcpy(data, array->entries[index].data, array->entries[index].length); uint32_t len = array->entries[index].length; clear_entry(array, index); return len; } /* Initialize a Ping_Array. * size represents the total size of the array and should be a power of 2. * timeout represents the maximum timeout in seconds for the entry. * * return 0 on success. * return -1 on failure. */ int ping_array_init(Ping_Array *empty_array, uint32_t size, uint32_t timeout) { if (size == 0 || timeout == 0 || empty_array == NULL) return -1; empty_array->entries = calloc(size, sizeof(Ping_Array_Entry)); if (empty_array->entries == NULL) return -1; empty_array->last_deleted = empty_array->last_added = 0; empty_array->total_size = size; empty_array->timeout = timeout; return 0; } /* Free all the allocated memory in a Ping_Array. */ void ping_array_free_all(Ping_Array *array) { while (array->last_deleted != array->last_added) { uint32_t index = array->last_deleted % array->total_size; clear_entry(array, index); ++array->last_deleted; } free(array->entries); array->entries = NULL; } ================================================ FILE: toxcore/ping_array.h ================================================ /* ping_array.h * * Implementation of an efficient array to store that we pinged something. * * 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 PING_ARRAY_H #define PING_ARRAY_H #include "network.h" typedef struct { void *data; uint32_t length; uint64_t time; uint64_t ping_id; } Ping_Array_Entry; typedef struct { Ping_Array_Entry *entries; uint32_t last_deleted; /* number representing the next entry to be deleted. */ uint32_t last_added; /* number representing the last entry to be added. */ uint32_t total_size; /* The length of entries */ uint32_t timeout; /* The timeout after which entries are cleared. */ } Ping_Array; /* Add a data with length to the Ping_Array list and return a ping_id. * * return ping_id on success. * return 0 on failure. */ uint64_t ping_array_add(Ping_Array *array, const uint8_t *data, uint32_t length); /* Check if ping_id is valid and not timed out. * * On success, copies the data into data of length, * * return length of data copied on success. * return -1 on failure. */ int ping_array_check(uint8_t *data, uint32_t length, Ping_Array *array, uint64_t ping_id); /* Initialize a Ping_Array. * size represents the total size of the array and should be a power of 2. * timeout represents the maximum timeout in seconds for the entry. * * return 0 on success. * return -1 on failure. */ int ping_array_init(Ping_Array *empty_array, uint32_t size, uint32_t timeout); /* Free all the allocated memory in a Ping_Array. */ void ping_array_free_all(Ping_Array *array); #endif ================================================ FILE: toxcore/tox.c ================================================ /* tox.c * * 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 . * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "Messenger.h" #include "group.h" #include "logger.h" #include "../toxencryptsave/defines.h" #define TOX_DEFINED typedef struct Messenger Tox; #include "tox.h" #define SET_ERROR_PARAMETER(param, x) {if(param) {*param = x;}} #if TOX_HASH_LENGTH != crypto_hash_sha256_BYTES #error TOX_HASH_LENGTH is assumed to be equal to crypto_hash_sha256_BYTES #endif #if FILE_ID_LENGTH != crypto_box_KEYBYTES #error FILE_ID_LENGTH is assumed to be equal to crypto_box_KEYBYTES #endif #if TOX_FILE_ID_LENGTH != crypto_box_KEYBYTES #error TOX_FILE_ID_LENGTH is assumed to be equal to crypto_box_KEYBYTES #endif #if TOX_FILE_ID_LENGTH != TOX_HASH_LENGTH #error TOX_FILE_ID_LENGTH is assumed to be equal to TOX_HASH_LENGTH #endif #if TOX_PUBLIC_KEY_SIZE != crypto_box_PUBLICKEYBYTES #error TOX_PUBLIC_KEY_SIZE is assumed to be equal to crypto_box_PUBLICKEYBYTES #endif #if TOX_SECRET_KEY_SIZE != crypto_box_SECRETKEYBYTES #error TOX_SECRET_KEY_SIZE is assumed to be equal to crypto_box_SECRETKEYBYTES #endif #if TOX_MAX_NAME_LENGTH != MAX_NAME_LENGTH #error TOX_MAX_NAME_LENGTH is assumed to be equal to MAX_NAME_LENGTH #endif #if TOX_MAX_STATUS_MESSAGE_LENGTH != MAX_STATUSMESSAGE_LENGTH #error TOX_MAX_STATUS_MESSAGE_LENGTH is assumed to be equal to MAX_STATUSMESSAGE_LENGTH #endif uint32_t tox_version_major(void) { return TOX_VERSION_MAJOR; } uint32_t tox_version_minor(void) { return TOX_VERSION_MINOR; } uint32_t tox_version_patch(void) { return TOX_VERSION_PATCH; } bool tox_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch) { return (TOX_VERSION_MAJOR == major && /* Force the major version */ (TOX_VERSION_MINOR > minor || /* Current minor version must be newer than requested -- or -- */ (TOX_VERSION_MINOR == minor && TOX_VERSION_PATCH >= patch) /* the patch must be the same or newer */ ) ); } void tox_options_default(struct Tox_Options *options) { if (options) { memset(options, 0, sizeof(struct Tox_Options)); options->ipv6_enabled = 1; options->udp_enabled = 1; options->proxy_type = TOX_PROXY_TYPE_NONE; } } struct Tox_Options *tox_options_new(TOX_ERR_OPTIONS_NEW *error) { struct Tox_Options *options = calloc(sizeof(struct Tox_Options), 1); if (options) { tox_options_default(options); SET_ERROR_PARAMETER(error, TOX_ERR_OPTIONS_NEW_OK); return options; } SET_ERROR_PARAMETER(error, TOX_ERR_OPTIONS_NEW_MALLOC); return NULL; } void tox_options_free(struct Tox_Options *options) { free(options); } Tox *tox_new(const struct Tox_Options *options, TOX_ERR_NEW *error) { if (!logger_get_global()) logger_set_global(logger_new(LOGGER_OUTPUT_FILE, LOGGER_LEVEL, "toxcore")); Messenger_Options m_options = {0}; _Bool load_savedata_sk = 0, load_savedata_tox = 0; if (options == NULL) { m_options.ipv6enabled = TOX_ENABLE_IPV6_DEFAULT; } else { if (options->savedata_type != TOX_SAVEDATA_TYPE_NONE) { if (options->savedata_data == NULL || options->savedata_length == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); return NULL; } } if (options->savedata_type == TOX_SAVEDATA_TYPE_SECRET_KEY) { if (options->savedata_length != TOX_SECRET_KEY_SIZE) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); return NULL; } load_savedata_sk = 1; } else if (options->savedata_type == TOX_SAVEDATA_TYPE_TOX_SAVE) { if (options->savedata_length < TOX_ENC_SAVE_MAGIC_LENGTH) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); return NULL; } if (sodium_memcmp(options->savedata_data, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_ENCRYPTED); return NULL; } load_savedata_tox = 1; } m_options.ipv6enabled = options->ipv6_enabled; m_options.udp_disabled = !options->udp_enabled; m_options.port_range[0] = options->start_port; m_options.port_range[1] = options->end_port; m_options.tcp_server_port = options->tcp_port; switch (options->proxy_type) { case TOX_PROXY_TYPE_HTTP: m_options.proxy_info.proxy_type = TCP_PROXY_HTTP; break; case TOX_PROXY_TYPE_SOCKS5: m_options.proxy_info.proxy_type = TCP_PROXY_SOCKS5; break; case TOX_PROXY_TYPE_NONE: m_options.proxy_info.proxy_type = TCP_PROXY_NONE; break; default: SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_TYPE); return NULL; } if (m_options.proxy_info.proxy_type != TCP_PROXY_NONE) { if (options->proxy_port == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_PORT); return NULL; } ip_init(&m_options.proxy_info.ip_port.ip, m_options.ipv6enabled); if (m_options.ipv6enabled) m_options.proxy_info.ip_port.ip.family = AF_UNSPEC; if (!addr_resolve_or_parse_ip(options->proxy_host, &m_options.proxy_info.ip_port.ip, NULL)) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PROXY_BAD_HOST); //TODO: TOX_ERR_NEW_PROXY_NOT_FOUND if domain. return NULL; } m_options.proxy_info.ip_port.port = htons(options->proxy_port); } } unsigned int m_error; Messenger *m = new_messenger(&m_options, &m_error); if (!new_groupchats(m)) { kill_messenger(m); if (m_error == MESSENGER_ERROR_PORT) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PORT_ALLOC); } else if (m_error == MESSENGER_ERROR_TCP_SERVER) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_PORT_ALLOC); } else { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_MALLOC); } return NULL; } if (load_savedata_tox && messenger_load(m, options->savedata_data, options->savedata_length) == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_LOAD_BAD_FORMAT); } else if (load_savedata_sk) { load_secret_key(m->net_crypto, options->savedata_data); SET_ERROR_PARAMETER(error, TOX_ERR_NEW_OK); } else { SET_ERROR_PARAMETER(error, TOX_ERR_NEW_OK); } return m; } void tox_kill(Tox *tox) { Messenger *m = tox; kill_groupchats(m->group_chat_object); kill_messenger(m); logger_kill_global(); } size_t tox_get_savedata_size(const Tox *tox) { const Messenger *m = tox; return messenger_size(m); } void tox_get_savedata(const Tox *tox, uint8_t *data) { if (data) { const Messenger *m = tox; messenger_save(m, data); } } bool tox_bootstrap(Tox *tox, const char *address, uint16_t port, const uint8_t *public_key, TOX_ERR_BOOTSTRAP *error) { if (!address || !public_key) { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_NULL); return 0; } if (port == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_PORT); return 0; } struct addrinfo *root, *info; if (getaddrinfo(address, NULL, NULL, &root) != 0) { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); return 0; } info = root; unsigned int count = 0; do { IP_Port ip_port; ip_port.port = htons(port); ip_port.ip.family = info->ai_family; if (info->ai_socktype && info->ai_socktype != SOCK_DGRAM) { continue; } if (info->ai_family == AF_INET) { ip_port.ip.ip4.in_addr = ((struct sockaddr_in *)info->ai_addr)->sin_addr; } else if (info->ai_family == AF_INET6) { ip_port.ip.ip6.in6_addr = ((struct sockaddr_in6 *)info->ai_addr)->sin6_addr; } else { continue; } Messenger *m = tox; onion_add_bs_path_node(m->onion_c, ip_port, public_key); DHT_bootstrap(m->dht, ip_port, public_key); ++count; } while ((info = info->ai_next)); freeaddrinfo(root); if (count) { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_OK); return 1; } else { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); return 0; } } bool tox_add_tcp_relay(Tox *tox, const char *address, uint16_t port, const uint8_t *public_key, TOX_ERR_BOOTSTRAP *error) { if (!address || !public_key) { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_NULL); return 0; } if (port == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_PORT); return 0; } struct addrinfo *root, *info; if (getaddrinfo(address, NULL, NULL, &root) != 0) { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); return 0; } info = root; unsigned int count = 0; do { IP_Port ip_port; ip_port.port = htons(port); ip_port.ip.family = info->ai_family; if (info->ai_socktype && info->ai_socktype != SOCK_STREAM) { continue; } if (info->ai_family == AF_INET) { ip_port.ip.ip4.in_addr = ((struct sockaddr_in *)info->ai_addr)->sin_addr; } else if (info->ai_family == AF_INET6) { ip_port.ip.ip6.in6_addr = ((struct sockaddr_in6 *)info->ai_addr)->sin6_addr; } else { continue; } Messenger *m = tox; add_tcp_relay(m->net_crypto, ip_port, public_key); ++count; } while ((info = info->ai_next)); freeaddrinfo(root); if (count) { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_OK); return 1; } else { SET_ERROR_PARAMETER(error, TOX_ERR_BOOTSTRAP_BAD_HOST); return 0; } } TOX_CONNECTION tox_self_get_connection_status(const Tox *tox) { const Messenger *m = tox; unsigned int ret = onion_connection_status(m->onion_c); if (ret == 2) { return TOX_CONNECTION_UDP; } else if (ret == 1) { return TOX_CONNECTION_TCP; } else { return TOX_CONNECTION_NONE; } } void tox_callback_self_connection_status(Tox *tox, tox_self_connection_status_cb *function, void *user_data) { Messenger *m = tox; m_callback_core_connection(m, function, user_data); } uint32_t tox_iteration_interval(const Tox *tox) { const Messenger *m = tox; return messenger_run_interval(m); } void tox_iterate(Tox *tox) { Messenger *m = tox; do_messenger(m); do_groupchats(m->group_chat_object); } void tox_self_get_address(const Tox *tox, uint8_t *address) { if (address) { const Messenger *m = tox; getaddress(m, address); } } void tox_self_set_nospam(Tox *tox, uint32_t nospam) { Messenger *m = tox; set_nospam(&(m->fr), nospam); } uint32_t tox_self_get_nospam(const Tox *tox) { const Messenger *m = tox; return get_nospam(&(m->fr)); } void tox_self_get_public_key(const Tox *tox, uint8_t *public_key) { const Messenger *m = tox; if (public_key) memcpy(public_key, m->net_crypto->self_public_key, crypto_box_PUBLICKEYBYTES); } void tox_self_get_secret_key(const Tox *tox, uint8_t *secret_key) { const Messenger *m = tox; if (secret_key) memcpy(secret_key, m->net_crypto->self_secret_key, crypto_box_SECRETKEYBYTES); } bool tox_self_set_name(Tox *tox, const uint8_t *name, size_t length, TOX_ERR_SET_INFO *error) { if (!name && length != 0) { SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_NULL); return 0; } Messenger *m = tox; if (setname(m, name, length) == 0) { //TODO: function to set different per group names? send_name_all_groups(m->group_chat_object); SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_OK); return 1; } else { SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_TOO_LONG); return 0; } } size_t tox_self_get_name_size(const Tox *tox) { const Messenger *m = tox; return m_get_self_name_size(m); } void tox_self_get_name(const Tox *tox, uint8_t *name) { if (name) { const Messenger *m = tox; getself_name(m, name); } } bool tox_self_set_status_message(Tox *tox, const uint8_t *status, size_t length, TOX_ERR_SET_INFO *error) { if (!status && length != 0) { SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_NULL); return 0; } Messenger *m = tox; if (m_set_statusmessage(m, status, length) == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_OK); return 1; } else { SET_ERROR_PARAMETER(error, TOX_ERR_SET_INFO_TOO_LONG); return 0; } } size_t tox_self_get_status_message_size(const Tox *tox) { const Messenger *m = tox; return m_get_self_statusmessage_size(m); } void tox_self_get_status_message(const Tox *tox, uint8_t *status) { if (status) { const Messenger *m = tox; m_copy_self_statusmessage(m, status); } } void tox_self_set_status(Tox *tox, TOX_USER_STATUS user_status) { Messenger *m = tox; m_set_userstatus(m, user_status); } TOX_USER_STATUS tox_self_get_status(const Tox *tox) { const Messenger *m = tox; return m_get_self_userstatus(m); } static void set_friend_error(int32_t ret, TOX_ERR_FRIEND_ADD *error) { switch (ret) { case FAERR_TOOLONG: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_TOO_LONG); break; case FAERR_NOMESSAGE: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_NO_MESSAGE); break; case FAERR_OWNKEY: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_OWN_KEY); break; case FAERR_ALREADYSENT: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_ALREADY_SENT); break; case FAERR_BADCHECKSUM: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_BAD_CHECKSUM); break; case FAERR_SETNEWNOSPAM: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM); break; case FAERR_NOMEM: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_MALLOC); break; } } uint32_t tox_friend_add(Tox *tox, const uint8_t *address, const uint8_t *message, size_t length, TOX_ERR_FRIEND_ADD *error) { if (!address || !message) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_NULL); return UINT32_MAX; } Messenger *m = tox; int32_t ret = m_addfriend(m, address, message, length); if (ret >= 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_OK); return ret; } set_friend_error(ret, error); return UINT32_MAX; } uint32_t tox_friend_add_norequest(Tox *tox, const uint8_t *public_key, TOX_ERR_FRIEND_ADD *error) { if (!public_key) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_NULL); return UINT32_MAX; } Messenger *m = tox; int32_t ret = m_addfriend_norequest(m, public_key); if (ret >= 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_ADD_OK); return ret; } set_friend_error(ret, error); return UINT32_MAX; } bool tox_friend_delete(Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_DELETE *error) { Messenger *m = tox; int ret = m_delfriend(m, friend_number); //TODO handle if realloc fails? if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_DELETE_FRIEND_NOT_FOUND); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_DELETE_OK); return 1; } uint32_t tox_friend_by_public_key(const Tox *tox, const uint8_t *public_key, TOX_ERR_FRIEND_BY_PUBLIC_KEY *error) { if (!public_key) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_BY_PUBLIC_KEY_NULL); return UINT32_MAX; } const Messenger *m = tox; int32_t ret = getfriend_id(m, public_key); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_BY_PUBLIC_KEY_NOT_FOUND); return UINT32_MAX; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK); return ret; } bool tox_friend_get_public_key(const Tox *tox, uint32_t friend_number, uint8_t *public_key, TOX_ERR_FRIEND_GET_PUBLIC_KEY *error) { if (!public_key) { return 0; } const Messenger *m = tox; if (get_real_pk(m, friend_number, public_key) == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_PUBLIC_KEY_FRIEND_NOT_FOUND); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK); return 1; } bool tox_friend_exists(const Tox *tox, uint32_t friend_number) { const Messenger *m = tox; return m_friend_exists(m, friend_number); } uint64_t tox_friend_get_last_online(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_GET_LAST_ONLINE *error) { const Messenger *m = tox; uint64_t timestamp = m_get_last_online(m, friend_number); if (timestamp == UINT64_MAX) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_LAST_ONLINE_FRIEND_NOT_FOUND) return UINT64_MAX; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_GET_LAST_ONLINE_OK); return timestamp; } size_t tox_self_get_friend_list_size(const Tox *tox) { const Messenger *m = tox; return count_friendlist(m); } void tox_self_get_friend_list(const Tox *tox, uint32_t *list) { if (list) { const Messenger *m = tox; //TODO: size parameter? copy_friendlist(m, list, tox_self_get_friend_list_size(tox)); } } size_t tox_friend_get_name_size(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error) { const Messenger *m = tox; int ret = m_get_name_size(m, friend_number); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); return SIZE_MAX; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); return ret; } bool tox_friend_get_name(const Tox *tox, uint32_t friend_number, uint8_t *name, TOX_ERR_FRIEND_QUERY *error) { if (!name) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_NULL); return 0; } const Messenger *m = tox; int ret = getname(m, friend_number, name); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); return 1; } void tox_callback_friend_name(Tox *tox, tox_friend_name_cb *function, void *user_data) { Messenger *m = tox; m_callback_namechange(m, function, user_data); } size_t tox_friend_get_status_message_size(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error) { const Messenger *m = tox; int ret = m_get_statusmessage_size(m, friend_number); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); return SIZE_MAX; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); return ret; } bool tox_friend_get_status_message(const Tox *tox, uint32_t friend_number, uint8_t *message, TOX_ERR_FRIEND_QUERY *error) { if (!message) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_NULL); return 0; } const Messenger *m = tox; //TODO: size parameter? int ret = m_copy_statusmessage(m, friend_number, message, m_get_statusmessage_size(m, friend_number)); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); return 1; } void tox_callback_friend_status_message(Tox *tox, tox_friend_status_message_cb *function, void *user_data) { Messenger *m = tox; m_callback_statusmessage(m, function, user_data); } TOX_USER_STATUS tox_friend_get_status(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error) { const Messenger *m = tox; int ret = m_get_userstatus(m, friend_number); if (ret == USERSTATUS_INVALID) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); return TOX_USER_STATUS_BUSY + 1; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); return ret; } void tox_callback_friend_status(Tox *tox, tox_friend_status_cb *function, void *user_data) { Messenger *m = tox; m_callback_userstatus(m, function, user_data); } TOX_CONNECTION tox_friend_get_connection_status(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error) { const Messenger *m = tox; int ret = m_get_friend_connectionstatus(m, friend_number); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); return TOX_CONNECTION_NONE; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); return ret; } void tox_callback_friend_connection_status(Tox *tox, tox_friend_connection_status_cb *function, void *user_data) { Messenger *m = tox; m_callback_connectionstatus(m, function, user_data); } bool tox_friend_get_typing(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error) { const Messenger *m = tox; int ret = m_get_istyping(m, friend_number); if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_QUERY_OK); return !!ret; } void tox_callback_friend_typing(Tox *tox, tox_friend_typing_cb *function, void *user_data) { Messenger *m = tox; m_callback_typingchange(m, function, user_data); } bool tox_self_set_typing(Tox *tox, uint32_t friend_number, bool is_typing, TOX_ERR_SET_TYPING *error) { Messenger *m = tox; if (m_set_usertyping(m, friend_number, is_typing) == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_SET_TYPING_FRIEND_NOT_FOUND); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_SET_TYPING_OK); return 1; } static void set_message_error(int ret, TOX_ERR_FRIEND_SEND_MESSAGE *error) { switch (ret) { case 0: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_OK); break; case -1: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND); break; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG); break; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED); break; case -4: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ); break; case -5: /* can't happen */ break; } } uint32_t tox_friend_send_message(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length, TOX_ERR_FRIEND_SEND_MESSAGE *error) { if (!message) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_NULL); return 0; } if (!length) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY); return 0; } Messenger *m = tox; uint32_t message_id = 0; set_message_error(m_send_message_generic(m, friend_number, type, message, length, &message_id), error); return message_id; } void tox_callback_friend_read_receipt(Tox *tox, tox_friend_read_receipt_cb *function, void *user_data) { Messenger *m = tox; m_callback_read_receipt(m, function, user_data); } void tox_callback_friend_request(Tox *tox, tox_friend_request_cb *function, void *user_data) { Messenger *m = tox; m_callback_friendrequest(m, function, user_data); } void tox_callback_friend_message(Tox *tox, tox_friend_message_cb *function, void *user_data) { Messenger *m = tox; m_callback_friendmessage(m, function, user_data); } bool tox_hash(uint8_t *hash, const uint8_t *data, size_t length) { if (!hash || (length && !data)) { return 0; } crypto_hash_sha256(hash, data, length); return 1; } bool tox_file_control(Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control, TOX_ERR_FILE_CONTROL *error) { Messenger *m = tox; int ret = file_control(m, friend_number, file_number, control); if (ret == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_OK); return 1; } switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_FRIEND_NOT_FOUND); return 0; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_FRIEND_NOT_CONNECTED); return 0; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_NOT_FOUND); return 0; case -4: /* can't happen */ return 0; case -5: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_ALREADY_PAUSED); return 0; case -6: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_DENIED); return 0; case -7: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_NOT_PAUSED); return 0; case -8: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_CONTROL_SENDQ); return 0; } /* can't happen */ return 0; } bool tox_file_seek(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, TOX_ERR_FILE_SEEK *error) { Messenger *m = tox; int ret = file_seek(m, friend_number, file_number, position); if (ret == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_OK); return 1; } switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_FRIEND_NOT_FOUND); return 0; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_FRIEND_NOT_CONNECTED); return 0; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_NOT_FOUND); return 0; case -4: case -5: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_DENIED); return 0; case -6: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_INVALID_POSITION); return 0; case -8: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEEK_SENDQ); return 0; } /* can't happen */ return 0; } void tox_callback_file_recv_control(Tox *tox, tox_file_recv_control_cb *function, void *user_data) { Messenger *m = tox; callback_file_control(m, function, user_data); } bool tox_file_get_file_id(const Tox *tox, uint32_t friend_number, uint32_t file_number, uint8_t *file_id, TOX_ERR_FILE_GET *error) { if (!file_id) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_NULL); return 0; } const Messenger *m = tox; int ret = file_get_id(m, friend_number, file_number, file_id); if (ret == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_OK); return 1; } else if (ret == -1) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_FRIEND_NOT_FOUND); } else { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_GET_NOT_FOUND); } return 0; } uint32_t tox_file_send(Tox *tox, uint32_t friend_number, uint32_t kind, uint64_t file_size, const uint8_t *file_id, const uint8_t *filename, size_t filename_length, TOX_ERR_FILE_SEND *error) { if (filename_length && !filename) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_NULL); return UINT32_MAX; } uint8_t f_id[FILE_ID_LENGTH]; if (!file_id) { /* Tox keys are 32 bytes like FILE_ID_LENGTH. */ new_symmetric_key(f_id); file_id = f_id; } Messenger *m = tox; long int file_num = new_filesender(m, friend_number, kind, file_size, file_id, filename, filename_length); if (file_num >= 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_OK); return file_num; } switch (file_num) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND); return UINT32_MAX; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_NAME_TOO_LONG); return UINT32_MAX; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_TOO_MANY); return UINT32_MAX; case -4: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED); return UINT32_MAX; } /* can't happen */ return UINT32_MAX; } bool tox_file_send_chunk(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t *data, size_t length, TOX_ERR_FILE_SEND_CHUNK *error) { Messenger *m = tox; int ret = file_data(m, friend_number, file_number, position, data, length); if (ret == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_OK); return 1; } switch (ret) { case -1: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_FOUND); return 0; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_CONNECTED); return 0; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_NOT_FOUND); return 0; case -4: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_NOT_TRANSFERRING); return 0; case -5: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_INVALID_LENGTH); return 0; case -6: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_SENDQ); return 0; case -7: SET_ERROR_PARAMETER(error, TOX_ERR_FILE_SEND_CHUNK_WRONG_POSITION); return 0; } /* can't happen */ return 0; } void tox_callback_file_chunk_request(Tox *tox, tox_file_chunk_request_cb *function, void *user_data) { Messenger *m = tox; callback_file_reqchunk(m, function, user_data); } void tox_callback_file_recv(Tox *tox, tox_file_recv_cb *function, void *user_data) { Messenger *m = tox; callback_file_sendrequest(m, function, user_data); } void tox_callback_file_recv_chunk(Tox *tox, tox_file_recv_chunk_cb *function, void *user_data) { Messenger *m = tox; callback_file_data(m, function, user_data); } static void set_custom_packet_error(int ret, TOX_ERR_FRIEND_CUSTOM_PACKET *error) { switch (ret) { case 0: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_OK); break; case -1: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_FOUND); break; case -2: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_TOO_LONG); break; case -3: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_INVALID); break; case -4: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_CONNECTED); break; case -5: SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_SENDQ); break; } } bool tox_friend_send_lossy_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, TOX_ERR_FRIEND_CUSTOM_PACKET *error) { if (!data) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_NULL); return 0; } Messenger *m = tox; if (length == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_EMPTY); return 0; } if (data[0] < (PACKET_ID_LOSSY_RANGE_START + PACKET_LOSSY_AV_RESERVED)) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_INVALID); return 0; } int ret = send_custom_lossy_packet(m, friend_number, data, length); set_custom_packet_error(ret, error); if (ret == 0) { return 1; } else { return 0; } } void tox_callback_friend_lossy_packet(Tox *tox, tox_friend_lossy_packet_cb *function, void *user_data) { Messenger *m = tox; custom_lossy_packet_registerhandler(m, function, user_data); } bool tox_friend_send_lossless_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, TOX_ERR_FRIEND_CUSTOM_PACKET *error) { if (!data) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_NULL); return 0; } Messenger *m = tox; if (length == 0) { SET_ERROR_PARAMETER(error, TOX_ERR_FRIEND_CUSTOM_PACKET_EMPTY); return 0; } int ret = send_custom_lossless_packet(m, friend_number, data, length); set_custom_packet_error(ret, error); if (ret == 0) { return 1; } else { return 0; } } void tox_callback_friend_lossless_packet(Tox *tox, tox_friend_lossless_packet_cb *function, void *user_data) { Messenger *m = tox; custom_lossless_packet_registerhandler(m, function, user_data); } void tox_self_get_dht_id(const Tox *tox, uint8_t *dht_id) { if (dht_id) { const Messenger *m = tox; memcpy(dht_id , m->dht->self_public_key, crypto_box_PUBLICKEYBYTES); } } uint16_t tox_self_get_udp_port(const Tox *tox, TOX_ERR_GET_PORT *error) { const Messenger *m = tox; uint16_t port = htons(m->net->port); if (port) { SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_OK); } else { SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_NOT_BOUND); } return port; } uint16_t tox_self_get_tcp_port(const Tox *tox, TOX_ERR_GET_PORT *error) { const Messenger *m = tox; if (m->tcp_server) { SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_OK); return m->options.tcp_server_port; } else { SET_ERROR_PARAMETER(error, TOX_ERR_GET_PORT_NOT_BOUND); return 0; } } #include "tox_old_code.h" ================================================ FILE: toxcore/tox.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_get_name_size(tox); * uint8_t *name = malloc(length); * if (!name) abort(); * tox_self_get_name(tox, name); * \endcode * * If any other thread calls tox_self_set_name while this thread is allocating * memory, the length may have become invalid, and the call to * tox_self_get_name may cause undefined behaviour. */ /** * 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. */ #ifndef TOX_DEFINED #define TOX_DEFINED typedef struct Tox Tox; #endif /* TOX_DEFINED */ /******************************************************************************* * * :: 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] /** * 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 tox_version_major(void); /** * Return the minor version number of the library. */ uint32_t tox_version_minor(void); /** * Return the patch number of the library. */ uint32_t tox_version_patch(void); /** * Return whether the compiled library version is compatible with the passed * version numbers. */ bool tox_version_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. */ #define TOX_PUBLIC_KEY_SIZE 32 /** * The size of a Tox Secret Key in bytes. */ #define TOX_SECRET_KEY_SIZE 32 /** * The size of a Tox address in bytes. Tox addresses are in the format * [Public Key (TOX_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. */ #define TOX_ADDRESS_SIZE (TOX_PUBLIC_KEY_SIZE + sizeof(uint32_t) + sizeof(uint16_t)) /** * Maximum length of a nickname in bytes. */ #define TOX_MAX_NAME_LENGTH 128 /** * Maximum length of a status message in bytes. */ #define TOX_MAX_STATUS_MESSAGE_LENGTH 1007 /** * Maximum length of a friend request message in bytes. */ #define TOX_MAX_FRIEND_REQUEST_LENGTH 1016 /** * Maximum length of a single message after which it should be split. */ #define TOX_MAX_MESSAGE_LENGTH 1372 /** * Maximum size of custom packets. TODO: should be LENGTH? */ #define TOX_MAX_CUSTOM_PACKET_SIZE 1373 /** * The number of bytes in a hash generated by tox_hash. */ #define TOX_HASH_LENGTH 32 /** * The number of bytes in a file id. */ #define TOX_FILE_ID_LENGTH 32 /** * Maximum file name length for file transfers. */ #define TOX_MAX_FILENAME_LENGTH 255 /******************************************************************************* * * :: Global enumerations * ******************************************************************************/ /** * Represents the possible statuses a client can have. */ typedef enum TOX_USER_STATUS { /** * User is online and available. */ TOX_USER_STATUS_NONE, /** * User is away. Clients can set this e.g. after a user defined * inactivity time. */ TOX_USER_STATUS_AWAY, /** * User is busy. Signals to other clients that this client does not * currently wish to communicate. */ TOX_USER_STATUS_BUSY, } TOX_USER_STATUS; /** * Represents message types for tox_friend_send_message and group chat * messages. */ typedef enum TOX_MESSAGE_TYPE { /** * Normal text message. Similar to PRIVMSG on IRC. */ TOX_MESSAGE_TYPE_NORMAL, /** * A message describing an user action. This is similar to /me (CTCP ACTION) * on IRC. */ TOX_MESSAGE_TYPE_ACTION, } TOX_MESSAGE_TYPE; /******************************************************************************* * * :: Startup options * ******************************************************************************/ /** * Type of proxy used to connect to TCP relays. */ typedef enum TOX_PROXY_TYPE { /** * Don't use a proxy. */ TOX_PROXY_TYPE_NONE, /** * HTTP proxy using CONNECT. */ TOX_PROXY_TYPE_HTTP, /** * SOCKS proxy for simple socket pipes. */ TOX_PROXY_TYPE_SOCKS5, } TOX_PROXY_TYPE; /** * Type of savedata to create the Tox instance from. */ typedef enum TOX_SAVEDATA_TYPE { /** * No savedata. */ TOX_SAVEDATA_TYPE_NONE, /** * Savedata is one that was obtained from tox_get_savedata */ TOX_SAVEDATA_TYPE_TOX_SAVE, /** * Savedata is a secret key of length TOX_SECRET_KEY_SIZE */ TOX_SAVEDATA_TYPE_SECRET_KEY, } TOX_SAVEDATA_TYPE; /** * This struct contains all the startup options for Tox. You can either allocate * this object yourself, and pass it to tox_options_default, or call * tox_options_new to get a new default options object. */ struct Tox_Options { /** * 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; /** * Pass communications through a proxy. */ TOX_PROXY_TYPE proxy_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. */ const char *proxy_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 proxy_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; /** * The type of savedata to load from. */ TOX_SAVEDATA_TYPE savedata_type; /** * The savedata. */ const uint8_t *savedata_data; /** * The length of the savedata. */ size_t savedata_length; }; /** * Initialises a Tox_Options 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 tox_options_default(struct Tox_Options *options); typedef enum TOX_ERR_OPTIONS_NEW { /** * The function returned successfully. */ TOX_ERR_OPTIONS_NEW_OK, /** * The function failed to allocate enough memory for the options struct. */ TOX_ERR_OPTIONS_NEW_MALLOC, } TOX_ERR_OPTIONS_NEW; /** * Allocates a new Tox_Options 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 tox_options_free * function. * * @return A new Tox_Options object with default options or NULL on failure. */ struct Tox_Options *tox_options_new(TOX_ERR_OPTIONS_NEW *error); /** * Releases all resources associated with an options objects. * * Passing a pointer that was not returned by tox_options_new results in * undefined behaviour. */ void tox_options_free(struct Tox_Options *options); /******************************************************************************* * * :: Creation and destruction * ******************************************************************************/ typedef enum TOX_ERR_NEW { /** * The function returned successfully. */ TOX_ERR_NEW_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_NEW_NULL, /** * The function was unable to allocate enough memory to store the internal * structures for the Tox object. */ TOX_ERR_NEW_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. */ TOX_ERR_NEW_PORT_ALLOC, /** * proxy_type was invalid. */ TOX_ERR_NEW_PROXY_BAD_TYPE, /** * proxy_type was valid but the proxy_host passed had an invalid format * or was NULL. */ TOX_ERR_NEW_PROXY_BAD_HOST, /** * proxy_type was valid, but the proxy_port was invalid. */ TOX_ERR_NEW_PROXY_BAD_PORT, /** * The proxy address passed could not be resolved. */ TOX_ERR_NEW_PROXY_NOT_FOUND, /** * The byte array to be loaded contained an encrypted save. */ TOX_ERR_NEW_LOAD_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. */ TOX_ERR_NEW_LOAD_BAD_FORMAT, } TOX_ERR_NEW; /** * @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 tox_iterate for the event loop. * * @return A new Tox instance pointer on success or NULL on failure. */ Tox *tox_new(const struct Tox_Options *options, TOX_ERR_NEW *error); /** * 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 tox_kill(Tox *tox); /** * Calculates the number of bytes required to store the tox instance with * tox_get_savedata. This function cannot fail. The result is always greater than 0. * * @see threading for concurrency implications. */ size_t tox_get_savedata_size(const Tox *tox); /** * 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 tox_get_savedata_size to find the number of bytes required. If this parameter * is NULL, this function has no effect. */ void tox_get_savedata(const Tox *tox, uint8_t *savedata); /******************************************************************************* * * :: Connection lifecycle and event loop * ******************************************************************************/ typedef enum TOX_ERR_BOOTSTRAP { /** * The function returned successfully. */ TOX_ERR_BOOTSTRAP_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_BOOTSTRAP_NULL, /** * The address could not be resolved to an IP address, or the IP address * passed was invalid. */ TOX_ERR_BOOTSTRAP_BAD_HOST, /** * The port passed was invalid. The valid port range is (1, 65535). */ TOX_ERR_BOOTSTRAP_BAD_PORT, } TOX_ERR_BOOTSTRAP; /** * 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 Tox_Options.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 * (TOX_PUBLIC_KEY_SIZE bytes). * @return true on success. */ bool tox_bootstrap(Tox *tox, const char *address, uint16_t port, const uint8_t *public_key, TOX_ERR_BOOTSTRAP *error); /** * 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 * (TOX_PUBLIC_KEY_SIZE bytes). * @return true on success. */ bool tox_add_tcp_relay(Tox *tox, const char *address, uint16_t port, const uint8_t *public_key, TOX_ERR_BOOTSTRAP *error); /** * Protocols that can be used to connect to the network or friends. */ typedef enum TOX_CONNECTION { /** * There is no connection. This instance, or the friend the state change is * about, is now offline. */ TOX_CONNECTION_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. */ TOX_CONNECTION_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. */ TOX_CONNECTION_UDP, } TOX_CONNECTION; /** * Return whether we are connected to the DHT. The return value is equal to the * last value received through the `self_connection_status` callback. */ TOX_CONNECTION tox_self_get_connection_status(const Tox *tox); /** * @param connection_status Whether we are connected to the DHT. */ typedef void tox_self_connection_status_cb(Tox *tox, TOX_CONNECTION connection_status, void *user_data); /** * Set the callback for the `self_connection_status` event. Pass NULL to unset. * * This event is triggered whenever there is a change in the DHT connection * state. When disconnected, a client may choose to call tox_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? */ void tox_callback_self_connection_status(Tox *tox, tox_self_connection_status_cb *callback, void *user_data); /** * Return the time in milliseconds before tox_iterate() should be called again * for optimal performance. */ uint32_t tox_iteration_interval(const Tox *tox); /** * The main loop that needs to be run in intervals of tox_iteration_interval() * milliseconds. */ void tox_iterate(Tox *tox); /******************************************************************************* * * :: Internal client information (Tox address/id) * ******************************************************************************/ /** * 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 TOX_ADDRESS_SIZE bytes. If this * parameter is NULL, this function has no effect. * @see TOX_ADDRESS_SIZE for the address format. */ void tox_self_get_address(const Tox *tox, uint8_t *address); /** * Set the 4-byte nospam part of the address. * * @param nospam Any 32 bit unsigned integer. */ void tox_self_set_nospam(Tox *tox, uint32_t nospam); /** * Get the 4-byte nospam part of the address. */ uint32_t tox_self_get_nospam(const Tox *tox); /** * Copy the Tox Public Key (long term) from the Tox object. * * @param public_key A memory region of at least TOX_PUBLIC_KEY_SIZE bytes. If * this parameter is NULL, this function has no effect. */ void tox_self_get_public_key(const Tox *tox, uint8_t *public_key); /** * Copy the Tox Secret Key from the Tox object. * * @param secret_key A memory region of at least TOX_SECRET_KEY_SIZE bytes. If * this parameter is NULL, this function has no effect. */ void tox_self_get_secret_key(const Tox *tox, uint8_t *secret_key); /******************************************************************************* * * :: User-visible client information (nickname/status) * ******************************************************************************/ /** * Common error codes for all functions that set a piece of user-visible * client information. */ typedef enum TOX_ERR_SET_INFO { /** * The function returned successfully. */ TOX_ERR_SET_INFO_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_SET_INFO_NULL, /** * Information length exceeded maximum permissible size. */ TOX_ERR_SET_INFO_TOO_LONG, } TOX_ERR_SET_INFO; /** * Set the nickname for the Tox client. * * Nickname length cannot exceed TOX_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. */ bool tox_self_set_name(Tox *tox, const uint8_t *name, size_t length, TOX_ERR_SET_INFO *error); /** * Return the length of the current nickname as passed to tox_self_set_name. * * If no nickname was set before calling this function, the name is empty, * and this function returns 0. * * @see threading for concurrency implications. */ size_t tox_self_get_name_size(const Tox *tox); /** * Write the nickname set by tox_self_set_name to a byte array. * * If no nickname was set before calling this function, the name is empty, * and this function has no effect. * * Call tox_self_get_name_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. */ void tox_self_get_name(const Tox *tox, uint8_t *name); /** * Set the client's status message. * * Status message length cannot exceed TOX_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. */ bool tox_self_set_status_message(Tox *tox, const uint8_t *status_message, size_t length, TOX_ERR_SET_INFO *error); /** * Return the length of the current status message as passed to tox_self_set_status_message. * * 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_t tox_self_get_status_message_size(const Tox *tox); /** * Write the status message set by tox_self_set_status_message 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 tox_self_get_status_message_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. */ void tox_self_get_status_message(const Tox *tox, uint8_t *status_message); /** * Set the client's user status. * * @param user_status One of the user statuses listed in the enumeration above. */ void tox_self_set_status(Tox *tox, TOX_USER_STATUS status); /** * Returns the client's user status. */ TOX_USER_STATUS tox_self_get_status(const Tox *tox); /******************************************************************************* * * :: Friend list management * ******************************************************************************/ typedef enum TOX_ERR_FRIEND_ADD { /** * The function returned successfully. */ TOX_ERR_FRIEND_ADD_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_FRIEND_ADD_NULL, /** * The length of the friend request message exceeded * TOX_MAX_FRIEND_REQUEST_LENGTH. */ TOX_ERR_FRIEND_ADD_TOO_LONG, /** * The friend request message was empty. This, and the TOO_LONG code will * never be returned from tox_friend_add_norequest. */ TOX_ERR_FRIEND_ADD_NO_MESSAGE, /** * The friend address belongs to the sending client. */ TOX_ERR_FRIEND_ADD_OWN_KEY, /** * A friend request has already been sent, or the address belongs to a friend * that is already on the friend list. */ TOX_ERR_FRIEND_ADD_ALREADY_SENT, /** * The friend address checksum failed. */ TOX_ERR_FRIEND_ADD_BAD_CHECKSUM, /** * The friend was already there, but the nospam value was different. */ TOX_ERR_FRIEND_ADD_SET_NEW_NOSPAM, /** * A memory allocation failed when trying to increase the friend list size. */ TOX_ERR_FRIEND_ADD_MALLOC, } TOX_ERR_FRIEND_ADD; /** * 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 * TOX_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 tox_self_get_address of * the friend you wish to add) it must be TOX_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 tox_friend_add(Tox *tox, const uint8_t *address, const uint8_t *message, size_t length, TOX_ERR_FRIEND_ADD *error); /** * 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 TOX_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 tox_friend_add for a more detailed description of friend numbers. */ uint32_t tox_friend_add_norequest(Tox *tox, const uint8_t *public_key, TOX_ERR_FRIEND_ADD *error); typedef enum TOX_ERR_FRIEND_DELETE { /** * The function returned successfully. */ TOX_ERR_FRIEND_DELETE_OK, /** * There was no friend with the given friend number. No friends were deleted. */ TOX_ERR_FRIEND_DELETE_FRIEND_NOT_FOUND, } TOX_ERR_FRIEND_DELETE; /** * 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 tox_friend_delete(Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_DELETE *error); /******************************************************************************* * * :: Friend list queries * ******************************************************************************/ typedef enum TOX_ERR_FRIEND_BY_PUBLIC_KEY { /** * The function returned successfully. */ TOX_ERR_FRIEND_BY_PUBLIC_KEY_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_FRIEND_BY_PUBLIC_KEY_NULL, /** * No friend with the given Public Key exists on the friend list. */ TOX_ERR_FRIEND_BY_PUBLIC_KEY_NOT_FOUND, } TOX_ERR_FRIEND_BY_PUBLIC_KEY; /** * 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. */ uint32_t tox_friend_by_public_key(const Tox *tox, const uint8_t *public_key, TOX_ERR_FRIEND_BY_PUBLIC_KEY *error); /** * Checks if a friend with the given friend number exists and returns true if * it does. */ bool tox_friend_exists(const Tox *tox, uint32_t friend_number); /** * Return the number of friends on the friend list. * * This function can be used to determine how much memory to allocate for * tox_self_get_friend_list. */ size_t tox_self_get_friend_list_size(const Tox *tox); /** * Copy a list of valid friend numbers into an array. * * Call tox_self_get_friend_list_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. */ void tox_self_get_friend_list(const Tox *tox, uint32_t *friend_list); typedef enum TOX_ERR_FRIEND_GET_PUBLIC_KEY { /** * The function returned successfully. */ TOX_ERR_FRIEND_GET_PUBLIC_KEY_OK, /** * No friend with the given number exists on the friend list. */ TOX_ERR_FRIEND_GET_PUBLIC_KEY_FRIEND_NOT_FOUND, } TOX_ERR_FRIEND_GET_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 TOX_PUBLIC_KEY_SIZE bytes. If * this parameter is NULL, this function has no effect. * * @return true on success. */ bool tox_friend_get_public_key(const Tox *tox, uint32_t friend_number, uint8_t *public_key, TOX_ERR_FRIEND_GET_PUBLIC_KEY *error); typedef enum TOX_ERR_FRIEND_GET_LAST_ONLINE { /** * The function returned successfully. */ TOX_ERR_FRIEND_GET_LAST_ONLINE_OK, /** * No friend with the given number exists on the friend list. */ TOX_ERR_FRIEND_GET_LAST_ONLINE_FRIEND_NOT_FOUND, } TOX_ERR_FRIEND_GET_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. */ uint64_t tox_friend_get_last_online(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_GET_LAST_ONLINE *error); /******************************************************************************* * * :: Friend-specific state queries (can also be received through callbacks) * ******************************************************************************/ /** * Common error codes for friend state query functions. */ typedef enum TOX_ERR_FRIEND_QUERY { /** * The function returned successfully. */ TOX_ERR_FRIEND_QUERY_OK, /** * 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. */ TOX_ERR_FRIEND_QUERY_NULL, /** * The friend_number did not designate a valid friend. */ TOX_ERR_FRIEND_QUERY_FRIEND_NOT_FOUND, } TOX_ERR_FRIEND_QUERY; /** * 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 * `friend_name` callback. */ size_t tox_friend_get_name_size(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); /** * Write the name of the friend designated by the given friend number to a byte * array. * * Call tox_friend_get_name_size to determine the allocation size for the `name` * parameter. * * The data written to `name` is equal to the data received by the last * `friend_name` callback. * * @param name A valid memory region large enough to store the friend's name. * * @return true on success. */ bool tox_friend_get_name(const Tox *tox, uint32_t friend_number, uint8_t *name, TOX_ERR_FRIEND_QUERY *error); /** * @param friend_number The friend number of the friend whose name changed. * @param name A byte array containing the same data as * tox_friend_get_name would write to its `name` parameter. * @param length A value equal to the return value of * tox_friend_get_name_size. */ typedef void tox_friend_name_cb(Tox *tox, uint32_t friend_number, const uint8_t *name, size_t length, void *user_data); /** * Set the callback for the `friend_name` event. Pass NULL to unset. * * This event is triggered when a friend changes their name. */ void tox_callback_friend_name(Tox *tox, tox_friend_name_cb *callback, void *user_data); /** * Return the length of the friend's status message. If the friend number is * invalid, the return value is SIZE_MAX. */ size_t tox_friend_get_status_message_size(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); /** * Write the status message of the friend designated by the given friend number to a byte * array. * * Call tox_friend_get_status_message_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 * `friend_status_message` callback. * * @param status_message A valid memory region large enough to store the friend's status message. */ bool tox_friend_get_status_message(const Tox *tox, uint32_t friend_number, uint8_t *status_message, TOX_ERR_FRIEND_QUERY *error); /** * @param friend_number The friend number of the friend whose status message * changed. * @param message A byte array containing the same data as * tox_friend_get_status_message would write to its `status_message` parameter. * @param length A value equal to the return value of * tox_friend_get_status_message_size. */ typedef void tox_friend_status_message_cb(Tox *tox, uint32_t friend_number, const uint8_t *message, size_t length, void *user_data); /** * Set the callback for the `friend_status_message` event. Pass NULL to unset. * * This event is triggered when a friend changes their status message. */ void tox_callback_friend_status_message(Tox *tox, tox_friend_status_message_cb *callback, void *user_data); /** * 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 * `friend_status` callback. */ TOX_USER_STATUS tox_friend_get_status(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); /** * @param friend_number The friend number of the friend whose user status * changed. * @param status The new user status. */ typedef void tox_friend_status_cb(Tox *tox, uint32_t friend_number, TOX_USER_STATUS status, void *user_data); /** * Set the callback for the `friend_status` event. Pass NULL to unset. * * This event is triggered when a friend changes their user status. */ void tox_callback_friend_status(Tox *tox, tox_friend_status_cb *callback, void *user_data); /** * Check whether a friend is currently connected to this client. * * The result of this function is equal to the last value received by the * `friend_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 * `friend_connection_status` event. */ TOX_CONNECTION tox_friend_get_connection_status(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); /** * @param friend_number The friend number of the friend whose connection status * changed. * @param connection_status The result of calling * tox_friend_get_connection_status on the passed friend_number. */ typedef void tox_friend_connection_status_cb(Tox *tox, uint32_t friend_number, TOX_CONNECTION connection_status, void *user_data); /** * Set the callback for the `friend_connection_status` event. Pass NULL to unset. * * 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. */ void tox_callback_friend_connection_status(Tox *tox, tox_friend_connection_status_cb *callback, void *user_data); /** * 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. */ bool tox_friend_get_typing(const Tox *tox, uint32_t friend_number, TOX_ERR_FRIEND_QUERY *error); /** * @param friend_number The friend number of the friend who started or stopped * typing. * @param is_typing The result of calling tox_friend_get_typing on the passed * friend_number. */ typedef void tox_friend_typing_cb(Tox *tox, uint32_t friend_number, bool is_typing, void *user_data); /** * Set the callback for the `friend_typing` event. Pass NULL to unset. * * This event is triggered when a friend starts or stops typing. */ void tox_callback_friend_typing(Tox *tox, tox_friend_typing_cb *callback, void *user_data); /******************************************************************************* * * :: Sending private messages * ******************************************************************************/ typedef enum TOX_ERR_SET_TYPING { /** * The function returned successfully. */ TOX_ERR_SET_TYPING_OK, /** * The friend number did not designate a valid friend. */ TOX_ERR_SET_TYPING_FRIEND_NOT_FOUND, } TOX_ERR_SET_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. */ bool tox_self_set_typing(Tox *tox, uint32_t friend_number, bool typing, TOX_ERR_SET_TYPING *error); typedef enum TOX_ERR_FRIEND_SEND_MESSAGE { /** * The function returned successfully. */ TOX_ERR_FRIEND_SEND_MESSAGE_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_FRIEND_SEND_MESSAGE_NULL, /** * The friend number did not designate a valid friend. */ TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED, /** * An allocation error occurred while increasing the send queue size. */ TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ, /** * Message length exceeded TOX_MAX_MESSAGE_LENGTH. */ TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG, /** * Attempted to send a zero-length message. */ TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY, } TOX_ERR_FRIEND_SEND_MESSAGE; /** * 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 TOX_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 `friend_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 tox_friend_send_message(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length, TOX_ERR_FRIEND_SEND_MESSAGE *error); /** * @param friend_number The friend number of the friend who received the message. * @param message_id The message ID as returned from tox_friend_send_message * corresponding to the message sent. */ typedef void tox_friend_read_receipt_cb(Tox *tox, uint32_t friend_number, uint32_t message_id, void *user_data); /** * Set the callback for the `friend_read_receipt` event. Pass NULL to unset. * * This event is triggered when the friend receives the message sent with * tox_friend_send_message with the corresponding message ID. */ void tox_callback_friend_read_receipt(Tox *tox, tox_friend_read_receipt_cb *callback, void *user_data); /******************************************************************************* * * :: Receiving private messages and friend requests * ******************************************************************************/ /** * @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 tox_friend_request_cb(Tox *tox, const uint8_t *public_key, const uint8_t *message, size_t length, void *user_data); /** * Set the callback for the `friend_request` event. Pass NULL to unset. * * This event is triggered when a friend request is received. */ void tox_callback_friend_request(Tox *tox, tox_friend_request_cb *callback, void *user_data); /** * @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 friend_request for more information on time_delta. */ typedef void tox_friend_message_cb(Tox *tox, uint32_t friend_number, TOX_MESSAGE_TYPE type, const uint8_t *message, size_t length, void *user_data); /** * Set the callback for the `friend_message` event. Pass NULL to unset. * * This event is triggered when a message from a friend is received. */ void tox_callback_friend_message(Tox *tox, tox_friend_message_cb *callback, void *user_data); /******************************************************************************* * * :: 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. */ bool tox_hash(uint8_t *hash, const uint8_t *data, size_t length); enum TOX_FILE_KIND { /** * Arbitrary file data. Clients can choose to handle it based on the file name * or magic or any other way they choose. */ TOX_FILE_KIND_DATA, /** * Avatar file_id. This consists of tox_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 * TOX_FILE_CONTROL_CANCEL before any other controls), or accept it (by * sending TOX_FILE_CONTROL_RESUME). The file_id of length TOX_HASH_LENGTH bytes * (same length as TOX_FILE_ID_LENGTH) will contain the hash. A client can compare * this hash with a saved hash and send TOX_FILE_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. */ TOX_FILE_KIND_AVATAR, }; typedef enum TOX_FILE_CONTROL { /** * Sent by the receiving side to accept a file send request. Also sent after a * TOX_FILE_CONTROL_PAUSE command to continue sending or receiving. */ TOX_FILE_CONTROL_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 TOX_FILE_CONTROL_RESUME for the transfer to resume. */ TOX_FILE_CONTROL_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. */ TOX_FILE_CONTROL_CANCEL, } TOX_FILE_CONTROL; typedef enum TOX_ERR_FILE_CONTROL { /** * The function returned successfully. */ TOX_ERR_FILE_CONTROL_OK, /** * The friend_number passed did not designate a valid friend. */ TOX_ERR_FILE_CONTROL_FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ TOX_ERR_FILE_CONTROL_FRIEND_NOT_CONNECTED, /** * No file transfer with the given file number was found for the given friend. */ TOX_ERR_FILE_CONTROL_NOT_FOUND, /** * A RESUME control was sent, but the file transfer is running normally. */ TOX_ERR_FILE_CONTROL_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. */ TOX_ERR_FILE_CONTROL_DENIED, /** * A PAUSE control was sent, but the file transfer was already paused. */ TOX_ERR_FILE_CONTROL_ALREADY_PAUSED, /** * Packet queue is full. */ TOX_ERR_FILE_CONTROL_SENDQ, } TOX_ERR_FILE_CONTROL; /** * 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 tox_file_control(Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control, TOX_ERR_FILE_CONTROL *error); /** * When receiving TOX_FILE_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 tox_file_recv_control_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, TOX_FILE_CONTROL control, void *user_data); /** * Set the callback for the `file_recv_control` event. Pass NULL to unset. * * This event is triggered when a file control command is received from a * friend. */ void tox_callback_file_recv_control(Tox *tox, tox_file_recv_control_cb *callback, void *user_data); typedef enum TOX_ERR_FILE_SEEK { /** * The function returned successfully. */ TOX_ERR_FILE_SEEK_OK, /** * The friend_number passed did not designate a valid friend. */ TOX_ERR_FILE_SEEK_FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ TOX_ERR_FILE_SEEK_FRIEND_NOT_CONNECTED, /** * No file transfer with the given file number was found for the given friend. */ TOX_ERR_FILE_SEEK_NOT_FOUND, /** * File was not in a state where it could be seeked. */ TOX_ERR_FILE_SEEK_DENIED, /** * Seek position was invalid */ TOX_ERR_FILE_SEEK_INVALID_POSITION, /** * Packet queue is full. */ TOX_ERR_FILE_SEEK_SENDQ, } TOX_ERR_FILE_SEEK; /** * 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 * TOX_FILE_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 tox_file_seek(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, TOX_ERR_FILE_SEEK *error); typedef enum TOX_ERR_FILE_GET { /** * The function returned successfully. */ TOX_ERR_FILE_GET_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_FILE_GET_NULL, /** * The friend_number passed did not designate a valid friend. */ TOX_ERR_FILE_GET_FRIEND_NOT_FOUND, /** * No file transfer with the given file number was found for the given friend. */ TOX_ERR_FILE_GET_NOT_FOUND, } TOX_ERR_FILE_GET; /** * 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 TOX_FILE_ID_LENGTH bytes. If * this parameter is NULL, this function has no effect. * * @return true on success. */ bool tox_file_get_file_id(const Tox *tox, uint32_t friend_number, uint32_t file_number, uint8_t *file_id, TOX_ERR_FILE_GET *error); /******************************************************************************* * * :: File transmission: sending * ******************************************************************************/ typedef enum TOX_ERR_FILE_SEND { /** * The function returned successfully. */ TOX_ERR_FILE_SEND_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_FILE_SEND_NULL, /** * The friend_number passed did not designate a valid friend. */ TOX_ERR_FILE_SEND_FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ TOX_ERR_FILE_SEND_FRIEND_NOT_CONNECTED, /** * Filename length exceeded TOX_MAX_FILENAME_LENGTH bytes. */ TOX_ERR_FILE_SEND_NAME_TOO_LONG, /** * Too many ongoing transfers. The maximum number of concurrent file transfers * is 256 per friend per direction (sending and receiving). */ TOX_ERR_FILE_SEND_TOO_MANY, } TOX_ERR_FILE_SEND; /** * Send a file transmission request. * * Maximum filename length is TOX_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 * `file_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 * file_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 TOX_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 tox_file_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 tox_file_send(Tox *tox, uint32_t friend_number, uint32_t kind, uint64_t file_size, const uint8_t *file_id, const uint8_t *filename, size_t filename_length, TOX_ERR_FILE_SEND *error); typedef enum TOX_ERR_FILE_SEND_CHUNK { /** * The function returned successfully. */ TOX_ERR_FILE_SEND_CHUNK_OK, /** * The length parameter was non-zero, but data was NULL. */ TOX_ERR_FILE_SEND_CHUNK_NULL, /** * The friend_number passed did not designate a valid friend. */ TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ TOX_ERR_FILE_SEND_CHUNK_FRIEND_NOT_CONNECTED, /** * No file transfer with the given file number was found for the given friend. */ TOX_ERR_FILE_SEND_CHUNK_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). */ TOX_ERR_FILE_SEND_CHUNK_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. */ TOX_ERR_FILE_SEND_CHUNK_INVALID_LENGTH, /** * Packet queue is full. */ TOX_ERR_FILE_SEND_CHUNK_SENDQ, /** * Position parameter was wrong. */ TOX_ERR_FILE_SEND_CHUNK_WRONG_POSITION, } TOX_ERR_FILE_SEND_CHUNK; /** * Send a chunk of file data to a friend. * * This function is called in response to the `file_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 tox_file_send_chunk(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t *data, size_t length, TOX_ERR_FILE_SEND_CHUNK *error); /** * 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 * `tox_file_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 tox_file_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 tox_file_chunk_request_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, size_t length, void *user_data); /** * Set the callback for the `file_chunk_request` event. Pass NULL to unset. * * This event is triggered when Core is ready to send more file data. */ void tox_callback_file_chunk_request(Tox *tox, tox_file_chunk_request_cb *callback, void *user_data); /******************************************************************************* * * :: File transmission: receiving * ******************************************************************************/ /** * 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 TOX_FILE_CONTROL_CANCEL * control command before any other control commands. It can be accepted by * sending TOX_FILE_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 tox_file_recv_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, uint32_t kind, uint64_t file_size, const uint8_t *filename, size_t filename_length, void *user_data); /** * Set the callback for the `file_recv` event. Pass NULL to unset. * * This event is triggered when a file transfer request is received. */ void tox_callback_file_recv(Tox *tox, tox_file_recv_cb *callback, void *user_data); /** * 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 tox_file_recv_chunk_cb(Tox *tox, uint32_t friend_number, uint32_t file_number, uint64_t position, const uint8_t *data, size_t length, void *user_data); /** * Set the callback for the `file_recv_chunk` event. Pass NULL to unset. * * 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. */ void tox_callback_file_recv_chunk(Tox *tox, tox_file_recv_chunk_cb *callback, void *user_data); /******************************************************************************* * * :: Group chat management * ******************************************************************************/ /******************************************************************************* * * :: Group chat message sending and receiving * ******************************************************************************/ /******************************************************************************* * * :: Low-level custom packet sending and receiving * ******************************************************************************/ typedef enum TOX_ERR_FRIEND_CUSTOM_PACKET { /** * The function returned successfully. */ TOX_ERR_FRIEND_CUSTOM_PACKET_OK, /** * One of the arguments to the function was NULL when it was not expected. */ TOX_ERR_FRIEND_CUSTOM_PACKET_NULL, /** * The friend number did not designate a valid friend. */ TOX_ERR_FRIEND_CUSTOM_PACKET_FRIEND_NOT_FOUND, /** * This client is currently not connected to the friend. */ TOX_ERR_FRIEND_CUSTOM_PACKET_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. */ TOX_ERR_FRIEND_CUSTOM_PACKET_INVALID, /** * Attempted to send an empty packet. */ TOX_ERR_FRIEND_CUSTOM_PACKET_EMPTY, /** * Packet data length exceeded TOX_MAX_CUSTOM_PACKET_SIZE. */ TOX_ERR_FRIEND_CUSTOM_PACKET_TOO_LONG, /** * Packet queue is full. */ TOX_ERR_FRIEND_CUSTOM_PACKET_SENDQ, } TOX_ERR_FRIEND_CUSTOM_PACKET; /** * 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 TOX_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 tox_friend_send_lossy_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, TOX_ERR_FRIEND_CUSTOM_PACKET *error); /** * 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 TOX_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 tox_friend_send_lossless_packet(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, TOX_ERR_FRIEND_CUSTOM_PACKET *error); /** * @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 tox_friend_lossy_packet_cb(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, void *user_data); /** * Set the callback for the `friend_lossy_packet` event. Pass NULL to unset. * */ void tox_callback_friend_lossy_packet(Tox *tox, tox_friend_lossy_packet_cb *callback, void *user_data); /** * @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 tox_friend_lossless_packet_cb(Tox *tox, uint32_t friend_number, const uint8_t *data, size_t length, void *user_data); /** * Set the callback for the `friend_lossless_packet` event. Pass NULL to unset. * */ void tox_callback_friend_lossless_packet(Tox *tox, tox_friend_lossless_packet_cb *callback, void *user_data); /******************************************************************************* * * :: Low-level network information * ******************************************************************************/ /** * 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 tox_self_get_udp_port) 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 TOX_PUBLIC_KEY_SIZE bytes. If this * parameter is NULL, this function has no effect. */ void tox_self_get_dht_id(const Tox *tox, uint8_t *dht_id); typedef enum TOX_ERR_GET_PORT { /** * The function returned successfully. */ TOX_ERR_GET_PORT_OK, /** * The instance was not bound to any port. */ TOX_ERR_GET_PORT_NOT_BOUND, } TOX_ERR_GET_PORT; /** * Return the UDP port this Tox instance is bound to. */ uint16_t tox_self_get_udp_port(const Tox *tox, TOX_ERR_GET_PORT *error); /** * Return the TCP port this Tox instance is bound to. This is only relevant if * the instance is acting as a TCP relay. */ uint16_t tox_self_get_tcp_port(const Tox *tox, TOX_ERR_GET_PORT *error); #include "tox_old.h" #ifdef __cplusplus } #endif #endif ================================================ FILE: toxcore/tox_old.h ================================================ /**********GROUP CHAT FUNCTIONS ************/ /* Group chat types for tox_callback_group_invite function. * * TOX_GROUPCHAT_TYPE_TEXT groupchats must be accepted with the tox_join_groupchat() function. * The function to accept TOX_GROUPCHAT_TYPE_AV is in toxav. */ enum { TOX_GROUPCHAT_TYPE_TEXT, TOX_GROUPCHAT_TYPE_AV }; /* Set the callback for group invites. * * Function(Tox *tox, int32_t friendnumber, uint8_t type, const uint8_t *data, uint16_t length, void *userdata) * * data of length is what needs to be passed to join_groupchat(). * * for what type means see the enum right above this comment. */ void tox_callback_group_invite(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, const uint8_t *, uint16_t, void *), void *userdata); /* Set the callback for group messages. * * Function(Tox *tox, int groupnumber, int peernumber, const uint8_t * message, uint16_t length, void *userdata) */ void tox_callback_group_message(Tox *tox, void (*function)(Tox *tox, int, int, const uint8_t *, uint16_t, void *), void *userdata); /* Set the callback for group actions. * * Function(Tox *tox, int groupnumber, int peernumber, const uint8_t * action, uint16_t length, void *userdata) */ void tox_callback_group_action(Tox *tox, void (*function)(Tox *tox, int, int, const uint8_t *, uint16_t, void *), void *userdata); /* Set callback function for title changes. * * Function(Tox *tox, int groupnumber, int peernumber, uint8_t * title, uint8_t length, void *userdata) * if peernumber == -1, then author is unknown (e.g. initial joining the group) */ void tox_callback_group_title(Tox *tox, void (*function)(Tox *tox, int, int, const uint8_t *, uint8_t, void *), void *userdata); /* Set callback function for peer name list changes. * * It gets called every time the name list changes(new peer/name, deleted peer) * Function(Tox *tox, int groupnumber, int peernumber, TOX_CHAT_CHANGE change, void *userdata) */ typedef enum { TOX_CHAT_CHANGE_PEER_ADD, TOX_CHAT_CHANGE_PEER_DEL, TOX_CHAT_CHANGE_PEER_NAME, } TOX_CHAT_CHANGE; void tox_callback_group_namelist_change(Tox *tox, void (*function)(Tox *tox, int, int, uint8_t, void *), void *userdata); /* Creates a new groupchat and puts it in the chats array. * * return group number on success. * return -1 on failure. */ int tox_add_groupchat(Tox *tox); /* Delete a groupchat from the chats array. * * return 0 on success. * return -1 if failure. */ int tox_del_groupchat(Tox *tox, int groupnumber); /* Copy the name of peernumber who is in groupnumber to name. * name must be at least TOX_MAX_NAME_LENGTH long. * * return length of name if success * return -1 if failure */ int tox_group_peername(const Tox *tox, int groupnumber, int peernumber, uint8_t *name); /* Copy the public key of peernumber who is in groupnumber to public_key. * public_key must be TOX_PUBLIC_KEY_SIZE long. * * returns 0 on success * returns -1 on failure */ int tox_group_peer_pubkey(const Tox *tox, int groupnumber, int peernumber, uint8_t *public_key); /* invite friendnumber to groupnumber * return 0 on success * return -1 on failure */ int tox_invite_friend(Tox *tox, int32_t friendnumber, int groupnumber); /* Join a group (you need to have been invited first.) using data of length obtained * in the group invite callback. * * returns group number on success * returns -1 on failure. */ int tox_join_groupchat(Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length); /* send a group message * return 0 on success * return -1 on failure */ int tox_group_message_send(Tox *tox, int groupnumber, const uint8_t *message, uint16_t length); /* send a group action * return 0 on success * return -1 on failure */ int tox_group_action_send(Tox *tox, int groupnumber, const uint8_t *action, uint16_t length); /* set the group's title, limited to MAX_NAME_LENGTH * return 0 on success * return -1 on failure */ int tox_group_set_title(Tox *tox, int groupnumber, const uint8_t *title, uint8_t length); /* Get group title from groupnumber and put it in title. * title needs to be a valid memory location with a max_length size of at least MAX_NAME_LENGTH (128) bytes. * * return length of copied title if success. * return -1 if failure. */ int tox_group_get_title(Tox *tox, int groupnumber, uint8_t *title, uint32_t max_length); /* Check if the current peernumber corresponds to ours. * * return 1 if the peernumber corresponds to ours. * return 0 on failure. */ unsigned int tox_group_peernumber_is_ours(const Tox *tox, int groupnumber, int peernumber); /* Return the number of peers in the group chat on success. * return -1 on failure */ int tox_group_number_peers(const Tox *tox, int groupnumber); /* List all the peers in the group chat. * * Copies the names of the peers to the name[length][TOX_MAX_NAME_LENGTH] array. * * Copies the lengths of the names to lengths[length] * * returns the number of peers on success. * * return -1 on failure. */ int tox_group_get_names(const Tox *tox, int groupnumber, uint8_t names[][TOX_MAX_NAME_LENGTH], uint16_t lengths[], uint16_t length); /* Return the number of chats in the instance m. * You should use this to determine how much memory to allocate * for copy_chatlist. */ uint32_t tox_count_chatlist(const Tox *tox); /* Copy a list of valid chat 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 tox_get_chatlist(const Tox *tox, int32_t *out_list, uint32_t list_size); /* return the type of groupchat (TOX_GROUPCHAT_TYPE_) that groupnumber is. * * return -1 on failure. * return type on success. */ int tox_group_get_type(const Tox *tox, int groupnumber); ================================================ FILE: toxcore/tox_old_code.h ================================================ /**********GROUP CHAT FUNCTIONS: WARNING Group chats will be rewritten so this might change ************/ /* Set the callback for group invites. * * Function(Tox *tox, int32_t friendnumber, uint8_t type, uint8_t *data, uint16_t length, void *userdata) * * data of length is what needs to be passed to join_groupchat(). */ void tox_callback_group_invite(Tox *tox, void (*function)(Messenger *tox, int32_t, uint8_t, const uint8_t *, uint16_t, void *), void *userdata) { Messenger *m = tox; g_callback_group_invite(m->group_chat_object, function, userdata); } /* Set the callback for group messages. * * Function(Tox *tox, int groupnumber, int peernumber, uint8_t * message, uint16_t length, void *userdata) */ void tox_callback_group_message(Tox *tox, void (*function)(Messenger *tox, int, int, const uint8_t *, uint16_t, void *), void *userdata) { Messenger *m = tox; g_callback_group_message(m->group_chat_object, function, userdata); } /* Set the callback for group actions. * * Function(Tox *tox, int groupnumber, int peernumber, uint8_t * action, uint16_t length, void *userdata) */ void tox_callback_group_action(Tox *tox, void (*function)(Messenger *tox, int, int, const uint8_t *, uint16_t, void *), void *userdata) { Messenger *m = tox; g_callback_group_action(m->group_chat_object, function, userdata); } /* Set callback function for title changes. * * Function(Tox *tox, int groupnumber, int peernumber, uint8_t * title, uint8_t length, void *userdata) * if peernumber == -1, then author is unknown (e.g. initial joining the group) */ void tox_callback_group_title(Tox *tox, void (*function)(Messenger *tox, int, int, const uint8_t *, uint8_t, void *), void *userdata) { Messenger *m = tox; g_callback_group_title(m->group_chat_object, function, userdata); } /* Set callback function for peer name list changes. * * It gets called every time the name list changes(new peer/name, deleted peer) * Function(Tox *tox, int groupnumber, void *userdata) */ void tox_callback_group_namelist_change(Tox *tox, void (*function)(Tox *tox, int, int, uint8_t, void *), void *userdata) { Messenger *m = tox; g_callback_group_namelistchange(m->group_chat_object, function, userdata); } /* Creates a new groupchat and puts it in the chats array. * * return group number on success. * return -1 on failure. */ int tox_add_groupchat(Tox *tox) { Messenger *m = tox; return add_groupchat(m->group_chat_object, GROUPCHAT_TYPE_TEXT); } /* Delete a groupchat from the chats array. * * return 0 on success. * return -1 if failure. */ int tox_del_groupchat(Tox *tox, int groupnumber) { Messenger *m = tox; return del_groupchat(m->group_chat_object, groupnumber); } /* Copy the name of peernumber who is in groupnumber to name. * name must be at least MAX_NICK_BYTES long. * * return length of name if success * return -1 if failure */ int tox_group_peername(const Tox *tox, int groupnumber, int peernumber, uint8_t *name) { const Messenger *m = tox; return group_peername(m->group_chat_object, groupnumber, peernumber, name); } /* Copy the public key of peernumber who is in groupnumber to public_key. * public_key must be crypto_box_PUBLICKEYBYTES long. * * returns 0 on success * returns -1 on failure */ int tox_group_peer_pubkey(const Tox *tox, int groupnumber, int peernumber, uint8_t *public_key) { const Messenger *m = tox; return group_peer_pubkey(m->group_chat_object, groupnumber, peernumber, public_key); } /* invite friendnumber to groupnumber * return 0 on success * return -1 on failure */ int tox_invite_friend(Tox *tox, int32_t friendnumber, int groupnumber) { Messenger *m = tox; return invite_friend(m->group_chat_object, friendnumber, groupnumber); } /* Join a group (you need to have been invited first.) using data of length obtained * in the group invite callback. * * returns group number on success * returns -1 on failure. */ int tox_join_groupchat(Tox *tox, int32_t friendnumber, const uint8_t *data, uint16_t length) { Messenger *m = tox; return join_groupchat(m->group_chat_object, friendnumber, GROUPCHAT_TYPE_TEXT, data, length); } /* send a group message * return 0 on success * return -1 on failure */ int tox_group_message_send(Tox *tox, int groupnumber, const uint8_t *message, uint16_t length) { Messenger *m = tox; return group_message_send(m->group_chat_object, groupnumber, message, length); } /* send a group action * return 0 on success * return -1 on failure */ int tox_group_action_send(Tox *tox, int groupnumber, const uint8_t *action, uint16_t length) { Messenger *m = tox; return group_action_send(m->group_chat_object, groupnumber, action, length); } /* set the group's title, limited to MAX_NAME_LENGTH * return 0 on success * return -1 on failure */ int tox_group_set_title(Tox *tox, int groupnumber, const uint8_t *title, uint8_t length) { Messenger *m = tox; return group_title_send(m->group_chat_object, groupnumber, title, length); } /* Get group title from groupnumber and put it in title. * title needs to be a valid memory location with a max_length size of at least MAX_NAME_LENGTH (128) bytes. * * return length of copied title if success. * return -1 if failure. */ int tox_group_get_title(Tox *tox, int groupnumber, uint8_t *title, uint32_t max_length) { Messenger *m = tox; return group_title_get(m->group_chat_object, groupnumber, title, max_length); } /* Check if the current peernumber corresponds to ours. * * return 1 if the peernumber corresponds to ours. * return 0 on failure. */ unsigned int tox_group_peernumber_is_ours(const Tox *tox, int groupnumber, int peernumber) { const Messenger *m = tox; return group_peernumber_is_ours(m->group_chat_object, groupnumber, peernumber); } /* Return the number of peers in the group chat on success. * return -1 on failure */ int tox_group_number_peers(const Tox *tox, int groupnumber) { const Messenger *m = tox; return group_number_peers(m->group_chat_object, groupnumber); } /* List all the peers in the group chat. * * Copies the names of the peers to the name[length][MAX_NICK_BYTES] array. * * Copies the lengths of the names to lengths[length] * * returns the number of peers on success. * * return -1 on failure. */ int tox_group_get_names(const Tox *tox, int groupnumber, uint8_t names[][TOX_MAX_NAME_LENGTH], uint16_t lengths[], uint16_t length) { const Messenger *m = tox; return group_names(m->group_chat_object, groupnumber, names, lengths, length); } /* Return the number of chats in the instance m. * You should use this to determine how much memory to allocate * for copy_chatlist. */ uint32_t tox_count_chatlist(const Tox *tox) { const Messenger *m = tox; return count_chatlist(m->group_chat_object); } /* Copy a list of valid chat 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 tox_get_chatlist(const Tox *tox, int32_t *out_list, uint32_t list_size) { const Messenger *m = tox; return copy_chatlist(m->group_chat_object, out_list, list_size); } /* return the type of groupchat (TOX_GROUPCHAT_TYPE_) that groupnumber is. * * return -1 on failure. * return type on success. */ int tox_group_get_type(const Tox *tox, int groupnumber) { const Messenger *m = tox; return group_get_type(m->group_chat_object, groupnumber); } ================================================ FILE: toxcore/util.c ================================================ /* * util.c -- Utilities. * * This file is donated to the Tox Project. * Copyright 2013 plutooo * * 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 /* for crypto_box_PUBLICKEYBYTES */ #include "crypto_core.h" #include "util.h" /* don't call into system billions of times for no reason */ static uint64_t unix_time_value; static uint64_t unix_base_time_value; void unix_time_update() { if (unix_base_time_value == 0) unix_base_time_value = ((uint64_t)time(NULL) - (current_time_monotonic() / 1000ULL)); unix_time_value = (current_time_monotonic() / 1000ULL) + unix_base_time_value; } uint64_t unix_time() { return unix_time_value; } int is_timeout(uint64_t timestamp, uint64_t timeout) { return timestamp + timeout <= unix_time(); } /* id functions */ bool id_equal(const uint8_t *dest, const uint8_t *src) { return public_key_cmp(dest, src) == 0; } uint32_t id_copy(uint8_t *dest, const uint8_t *src) { memcpy(dest, src, crypto_box_PUBLICKEYBYTES); return crypto_box_PUBLICKEYBYTES; } void host_to_net(uint8_t *num, uint16_t numbytes) { #ifndef WORDS_BIGENDIAN uint32_t i; uint8_t buff[numbytes]; for (i = 0; i < numbytes; ++i) { buff[i] = num[numbytes - i - 1]; } memcpy(num, buff, numbytes); #endif return; } uint16_t lendian_to_host16(uint16_t lendian) { #ifdef WORDS_BIGENDIAN return (lendian << 8) | (lendian >> 8 ); #else return lendian; #endif } void host_to_lendian32(uint8_t *dest, uint32_t num) { #ifdef WORDS_BIGENDIAN num = ((num << 8) & 0xFF00FF00 ) | ((num >> 8) & 0xFF00FF ); num = (num << 16) | (num >> 16); #endif memcpy(dest, &num, sizeof(uint32_t)); } void lendian_to_host32(uint32_t *dest, const uint8_t *lendian) { uint32_t d; memcpy(&d, lendian, sizeof(uint32_t)); #ifdef WORDS_BIGENDIAN d = ((d << 8) & 0xFF00FF00 ) | ((d >> 8) & 0xFF00FF ); d = (d << 16) | (d >> 16); #endif *dest = d; } /* state load/save */ int load_state(load_state_callback_func load_state_callback, void *outer, const uint8_t *data, uint32_t length, uint16_t cookie_inner) { if (!load_state_callback || !data) { #ifdef DEBUG fprintf(stderr, "load_state() called with invalid args.\n"); #endif return -1; } uint16_t type; uint32_t length_sub, cookie_type; uint32_t size_head = sizeof(uint32_t) * 2; while (length >= size_head) { lendian_to_host32(&length_sub, data); lendian_to_host32(&cookie_type, data + sizeof(length_sub)); data += size_head; length -= size_head; if (length < length_sub) { /* file truncated */ #ifdef DEBUG fprintf(stderr, "state file too short: %u < %u\n", length, length_sub); #endif return -1; } if (lendian_to_host16((cookie_type >> 16)) != cookie_inner) { /* something is not matching up in a bad way, give up */ #ifdef DEBUG fprintf(stderr, "state file garbeled: %04hx != %04hx\n", (cookie_type >> 16), cookie_inner); #endif return -1; } type = lendian_to_host16(cookie_type & 0xFFFF); int ret = load_state_callback(outer, data, length_sub, type); if (ret == -1) { return -1; } /* -2 means end of save. */ if (ret == -2) return 0; data += length_sub; length -= length_sub; } return length == 0 ? 0 : -1; }; int create_recursive_mutex(pthread_mutex_t *mutex) { pthread_mutexattr_t attr; if (pthread_mutexattr_init(&attr) != 0) return -1; if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) { pthread_mutexattr_destroy(&attr); return -1; } /* Create queue mutex */ if (pthread_mutex_init(mutex, &attr) != 0) { pthread_mutexattr_destroy(&attr); return -1; } pthread_mutexattr_destroy(&attr); return 0; } struct RingBuffer { uint16_t size; /* Max size */ uint16_t start; uint16_t end; void **data; }; bool rb_full(const RingBuffer *b) { return (b->end + 1) % b->size == b->start; } bool rb_empty(const RingBuffer *b) { return b->end == b->start; } void *rb_write(RingBuffer *b, void *p) { void *rc = NULL; if ((b->end + 1) % b->size == b->start) /* full */ rc = b->data[b->start]; b->data[b->end] = p; b->end = (b->end + 1) % b->size; if (b->end == b->start) b->start = (b->start + 1) % b->size; return rc; } bool rb_read(RingBuffer *b, void **p) { if (b->end == b->start) { /* Empty */ *p = NULL; return false; } *p = b->data[b->start]; b->start = (b->start + 1) % b->size; return true; } RingBuffer *rb_new(int size) { RingBuffer *buf = calloc(sizeof(RingBuffer), 1); if (!buf) return NULL; buf->size = size + 1; /* include empty elem */ if (!(buf->data = calloc(buf->size, sizeof(void *)))) { free(buf); return NULL; } return buf; } void rb_kill(RingBuffer *b) { if (b) { free(b->data); free(b); } } uint16_t rb_size(const RingBuffer *b) { if (rb_empty(b)) return 0; return b->end > b->start ? b->end - b->start : (b->size - b->start) + b->end; } uint16_t rb_data(const RingBuffer *b, void **dest) { uint16_t i = 0; for (; i < rb_size(b); i++) dest[i] = b->data[(b->start + i) % b->size]; return i; } ================================================ FILE: toxcore/util.h ================================================ /* * util.h -- Utilities. * * This file is donated to the Tox Project. * Copyright 2013 plutooo * * 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 __UTIL_H__ #define __UTIL_H__ #include #include #include #define MIN(a,b) (((a)<(b))?(a):(b)) #define PAIR(TYPE1__, TYPE2__) struct { TYPE1__ first; TYPE2__ second; } void unix_time_update(); uint64_t unix_time(); int is_timeout(uint64_t timestamp, uint64_t timeout); /* id functions */ bool id_equal(const uint8_t *dest, const uint8_t *src); uint32_t id_copy(uint8_t *dest, const uint8_t *src); /* return value is CLIENT_ID_SIZE */ void host_to_net(uint8_t *num, uint16_t numbytes); #define net_to_host(x, y) host_to_net(x, y) uint16_t lendian_to_host16(uint16_t lendian); #define host_tolendian16(x) lendian_to_host16(x) void host_to_lendian32(uint8_t *dest, uint32_t num); void lendian_to_host32(uint32_t *dest, const uint8_t *lendian); /* state load/save */ typedef int (*load_state_callback_func)(void *outer, const uint8_t *data, uint32_t len, uint16_t type); int load_state(load_state_callback_func load_state_callback, void *outer, const uint8_t *data, uint32_t length, uint16_t cookie_inner); /* Returns -1 if failed or 0 if success */ int create_recursive_mutex(pthread_mutex_t *mutex); /* Ring buffer */ typedef struct RingBuffer RingBuffer; bool rb_full(const RingBuffer *b); bool rb_empty(const RingBuffer *b); void *rb_write(RingBuffer *b, void *p); bool rb_read(RingBuffer *b, void **p); RingBuffer *rb_new(int size); void rb_kill(RingBuffer *b); uint16_t rb_size(const RingBuffer *b); uint16_t rb_data(const RingBuffer *b, void **dest); #endif /* __UTIL_H__ */ ================================================ FILE: toxdns/Makefile.inc ================================================ lib_LTLIBRARIES += libtoxdns.la libtoxdns_la_include_HEADERS = \ ../toxdns/toxdns.h libtoxdns_la_includedir = $(includedir)/tox libtoxdns_la_SOURCES = ../toxdns/toxdns.h \ ../toxdns/toxdns.c libtoxdns_la_CFLAGS = -I$(top_srcdir) \ -I$(top_srcdir)/toxcore \ $(LIBSODIUM_CFLAGS) \ $(NACL_CFLAGS) \ $(PTHREAD_CFLAGS) libtoxdns_la_LDFLAGS = $(TOXCORE_LT_LDFLAGS) \ $(EXTRA_LT_LDFLAGS) \ $(LIBSODIUM_LDFLAGS) \ $(NACL_LDFLAGS) \ $(MATH_LDFLAGS) \ $(RT_LIBS) \ $(WINSOCK2_LIBS) libtoxdns_la_LIBADD = $(LIBSODIUM_LIBS) \ $(NACL_OBJECTS) \ $(NAC_LIBS) \ $(PTHREAD_LIBS) \ libtoxcore.la ================================================ FILE: toxdns/toxdns.c ================================================ /* toxdns.c * * Tox secure username DNS toxid resolving functions. * * 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 "../toxcore/logger.h" #include "toxdns.h" static const char base32[32] = {"abcdefghijklmnopqrstuvwxyz012345"}; #define _encode(a, b, c) \ { \ uint8_t i = 0; \ while(i != c) { \ *a++ = base32[((b[0] >> bits) | (b[1] << (8 - bits))) & 0x1F]; \ bits += 5; \ if(bits >= 8) { \ bits -= 8; \ b++; \ i++; \ } \ } \ } \ typedef struct { uint8_t temp_pk[crypto_box_PUBLICKEYBYTES]; uint8_t temp_sk[crypto_box_SECRETKEYBYTES]; uint8_t server_public_key[crypto_box_PUBLICKEYBYTES]; uint8_t shared_key[crypto_box_KEYBYTES]; uint32_t nonce; uint32_t nonce_start; } DNS_Object; static void dns_new_temp_keys(DNS_Object *d) { d->nonce = d->nonce_start = random_int(); crypto_box_keypair(d->temp_pk, d->temp_sk); encrypt_precompute(d->server_public_key, d->temp_sk, d->shared_key); } /* Create a new tox_dns3 object for server with server_public_key. * * return Null on failure. * return pointer object on success. */ void *tox_dns3_new(uint8_t *server_public_key) { DNS_Object *d = malloc(sizeof(DNS_Object)); if (d == NULL) return NULL; memcpy(d->server_public_key, server_public_key, crypto_box_PUBLICKEYBYTES); dns_new_temp_keys(d); return d; } /* Destroy the tox dns3 object. */ void tox_dns3_kill(void *dns3_object) { memset(dns3_object, 0, sizeof(DNS_Object)); free(dns3_object); } /* Generate a dns3 string of string_max_len used to query the dns server referred to by to * dns3_object for a tox id registered to user with name of name_len. * * the uint32_t pointed by request_id will be set to the request id which must be passed to * tox_decrypt_dns3_TXT() to correctly decode the response. * * This is what the string returned looks like: * 4haaaaipr1o3mz0bxweox541airydbovqlbju51mb4p0ebxq.rlqdj4kkisbep2ks3fj2nvtmk4daduqiueabmexqva1jc * * returns length of string on success. * returns -1 on failure. */ int tox_generate_dns3_string(void *dns3_object, uint8_t *string, uint16_t string_max_len, uint32_t *request_id, uint8_t *name, uint8_t name_len) { #define DOT_INTERVAL (6 * 5) int base = (sizeof(uint32_t) + crypto_box_PUBLICKEYBYTES + name_len + crypto_box_MACBYTES); int end_len = ((base * 8) / 5) + (base / DOT_INTERVAL) + !!(base % 5); end_len -= !(base % DOT_INTERVAL); if (end_len > string_max_len) return -1; DNS_Object *d = dns3_object; uint8_t buffer[1024]; uint8_t nonce[crypto_box_NONCEBYTES] = {0}; memcpy(nonce, &d->nonce, sizeof(uint32_t)); memcpy(buffer, &d->nonce, sizeof(uint32_t)); memcpy(buffer + sizeof(uint32_t), d->temp_pk, crypto_box_PUBLICKEYBYTES); int len = encrypt_data_symmetric(d->shared_key, nonce, name, name_len, buffer + sizeof(uint32_t) + crypto_box_PUBLICKEYBYTES); if (len == -1) return -1; int total_len = len + sizeof(uint32_t) + crypto_box_PUBLICKEYBYTES; uint8_t *buff = buffer, *old_str = string; buffer[total_len] = 0; uint8_t bits = 0; int i; for (i = !(total_len % DOT_INTERVAL); i < (total_len / DOT_INTERVAL); ++i) { _encode(string, buff, DOT_INTERVAL); *string = '.'; ++string; } int left = total_len - (buff - buffer); _encode(string, buff, left); #undef DOT_INTERVAL *request_id = d->nonce; ++d->nonce; if (d->nonce == d->nonce_start) { dns_new_temp_keys(d); } if (end_len != string - old_str) { LOGGER_ERROR("tox_generate_dns3_string Fail, %u != %lu\n", end_len, string - old_str); return -1; } return string - old_str; } static int decode(uint8_t *dest, uint8_t *src) { uint8_t *p = src, *op = dest, bits = 0; *op = 0; while (*p) { uint8_t ch = *p++; if ('A' <= ch && ch <= 'Z') { ch = ch - 'A'; } else if ('a' <= ch && ch <= 'z') { ch = ch - 'a'; } else if ('0' <= ch && ch <= '5') { ch = ch - '0' + 26; } else { return - 1; } *op |= (ch << bits); bits += 5; if (bits >= 8) { bits -= 8; ++op; *op = (ch >> (5 - bits)); } } return op - dest; } /* Decode and decrypt the id_record returned of length id_record_len into * tox_id (needs to be at least TOX_FRIEND_ADDRESS_SIZE). * * request_id is the request id given by tox_generate_dns3_string() when creating the request. * * the id_record passed to this function should look somewhat like this: * 2vgcxuycbuctvauik3plsv3d3aadv4zfjfhi3thaizwxinelrvigchv0ah3qjcsx5qhmaksb2lv2hm5cwbtx0yp * * returns -1 on failure. * returns 0 on success. * */ int tox_decrypt_dns3_TXT(void *dns3_object, uint8_t *tox_id, uint8_t *id_record, uint32_t id_record_len, uint32_t request_id) { DNS_Object *d = dns3_object; if (id_record_len != 87) return -1; /*if (id_record_len > 255 || id_record_len <= (sizeof(uint32_t) + crypto_box_MACBYTES)) return -1;*/ uint8_t id_record_null[id_record_len + 1]; memcpy(id_record_null, id_record, id_record_len); id_record_null[id_record_len] = 0; uint8_t data[id_record_len]; int length = decode(data, id_record_null); if (length == -1) return -1; uint8_t nonce[crypto_box_NONCEBYTES] = {0}; memcpy(nonce, &request_id, sizeof(uint32_t)); nonce[sizeof(uint32_t)] = 1; int len = decrypt_data_symmetric(d->shared_key, nonce, data, length, tox_id); if (len != FRIEND_ADDRESS_SIZE) return -1; return 0; } ================================================ FILE: toxdns/toxdns.h ================================================ /* toxdns.h * * Tox secure username DNS toxid resolving functions. * * 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 TOXDNS_H #define TOXDNS_H #ifdef __cplusplus extern "C" { #endif #include /* Clients are encouraged to set this as the maximum length names can have. */ #define TOXDNS_MAX_RECOMMENDED_NAME_LENGTH 32 /* How to use this api to make secure tox dns3 requests: * * 1. Get the public key of a server that supports tox dns3. * 2. use tox_dns3_new() to create a new object to create DNS requests * and handle responses for that server. * 3. Use tox_generate_dns3_string() to generate a string based on the name we want to query and a request_id * that must be stored somewhere for when we want to decrypt the response. * 4. take the string and use it for your DNS request like this: * _4haaaaipr1o3mz0bxweox541airydbovqlbju51mb4p0ebxq.rlqdj4kkisbep2ks3fj2nvtmk4daduqiueabmexqva1jc._tox.utox.org * 5. The TXT in the DNS you receive should look like this: * v=tox3;id=2vgcxuycbuctvauik3plsv3d3aadv4zfjfhi3thaizwxinelrvigchv0ah3qjcsx5qhmaksb2lv2hm5cwbtx0yp * 6. Take the id string and use it with tox_decrypt_dns3_TXT() and the request_id corresponding to the * request we stored earlier to get the Tox id returned by the DNS server. */ /* Create a new tox_dns3 object for server with server_public_key of size TOX_CLIENT_ID_SIZE. * * return Null on failure. * return pointer object on success. */ void *tox_dns3_new(uint8_t *server_public_key); /* Destroy the tox dns3 object. */ void tox_dns3_kill(void *dns3_object); /* Generate a dns3 string of string_max_len used to query the dns server referred to by to * dns3_object for a tox id registered to user with name of name_len. * * the uint32_t pointed by request_id will be set to the request id which must be passed to * tox_decrypt_dns3_TXT() to correctly decode the response. * * This is what the string returned looks like: * 4haaaaipr1o3mz0bxweox541airydbovqlbju51mb4p0ebxq.rlqdj4kkisbep2ks3fj2nvtmk4daduqiueabmexqva1jc * * returns length of string on success. * returns -1 on failure. */ int tox_generate_dns3_string(void *dns3_object, uint8_t *string, uint16_t string_max_len, uint32_t *request_id, uint8_t *name, uint8_t name_len); /* Decode and decrypt the id_record returned of length id_record_len into * tox_id (needs to be at least TOX_FRIEND_ADDRESS_SIZE). * * request_id is the request id given by tox_generate_dns3_string() when creating the request. * * the id_record passed to this function should look somewhat like this: * 2vgcxuycbuctvauik3plsv3d3aadv4zfjfhi3thaizwxinelrvigchv0ah3qjcsx5qhmaksb2lv2hm5cwbtx0yp * * returns -1 on failure. * returns 0 on success. * */ int tox_decrypt_dns3_TXT(void *dns3_object, uint8_t *tox_id, uint8_t *id_record, uint32_t id_record_len, uint32_t request_id); #ifdef __cplusplus } #endif #endif ================================================ FILE: toxencryptsave/Makefile.inc ================================================ lib_LTLIBRARIES += libtoxencryptsave.la libtoxencryptsave_la_include_HEADERS = \ ../toxencryptsave/toxencryptsave.h libtoxencryptsave_la_includedir = $(includedir)/tox libtoxencryptsave_la_SOURCES = ../toxencryptsave/toxencryptsave.h \ ../toxencryptsave/toxencryptsave.c \ ../toxencryptsave/defines.h if WITH_NACL libtoxencryptsave_la_SOURCES += ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/crypto_pwhash_scryptsalsa208sha256.h \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/crypto_scrypt.h \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/pbkdf2-sha256.c \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/runtime.h \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/utils.c \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/crypto_scrypt-common.c \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/export.h \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/pbkdf2-sha256.h \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/runtime.c \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/scrypt_platform.c \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/sysendian.h \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/utils.h \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c \ ../toxencryptsave/crypto_pwhash_scryptsalsa208sha256/sse/pwhash_scryptsalsa208sha256_sse.c endif libtoxencryptsave_la_CFLAGS = -I$(top_srcdir) \ -I$(top_srcdir)/toxcore \ $(LIBSODIUM_CFLAGS) \ $(NACL_CFLAGS) \ $(PTHREAD_CFLAGS) libtoxencryptsave_la_LDFLAGS = $(TOXCORE_LT_LDFLAGS) \ $(EXTRA_LT_LDFLAGS) \ $(LIBSODIUM_LDFLAGS) \ $(NACL_LDFLAGS) \ $(MATH_LDFLAGS) \ $(RT_LIBS) \ $(WINSOCK2_LIBS) libtoxencryptsave_la_LIBADD = $(LIBSODIUM_LIBS) \ $(NACL_OBJECTS) \ $(NAC_LIBS) \ $(PTHREAD_LIBS) \ libtoxcore.la ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/crypto_pwhash_scryptsalsa208sha256.h ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ #ifndef crypto_pwhash_scryptsalsa208sha256_H #define crypto_pwhash_scryptsalsa208sha256_H #include #include #include "export.h" #ifdef __cplusplus # if __GNUC__ # pragma GCC diagnostic ignored "-Wlong-long" # endif extern "C" { #endif #define crypto_pwhash_scryptsalsa208sha256_SALTBYTES 32U SODIUM_EXPORT size_t crypto_pwhash_scryptsalsa208sha256_saltbytes(void); #define crypto_pwhash_scryptsalsa208sha256_STRBYTES 102U SODIUM_EXPORT size_t crypto_pwhash_scryptsalsa208sha256_strbytes(void); #define crypto_pwhash_scryptsalsa208sha256_STRPREFIX "$7$" SODIUM_EXPORT const char *crypto_pwhash_scryptsalsa208sha256_strprefix(void); #define crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE 524288ULL SODIUM_EXPORT size_t crypto_pwhash_scryptsalsa208sha256_opslimit_interactive(void); #define crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE 16777216ULL SODIUM_EXPORT size_t crypto_pwhash_scryptsalsa208sha256_memlimit_interactive(void); #define crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE 33554432ULL SODIUM_EXPORT size_t crypto_pwhash_scryptsalsa208sha256_opslimit_sensitive(void); #define crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE 1073741824ULL SODIUM_EXPORT size_t crypto_pwhash_scryptsalsa208sha256_memlimit_sensitive(void); SODIUM_EXPORT int crypto_pwhash_scryptsalsa208sha256(unsigned char * const out, unsigned long long outlen, const char * const passwd, unsigned long long passwdlen, const unsigned char * const salt, unsigned long long opslimit, size_t memlimit); SODIUM_EXPORT int crypto_pwhash_scryptsalsa208sha256_str(char out[crypto_pwhash_scryptsalsa208sha256_STRBYTES], const char * const passwd, unsigned long long passwdlen, unsigned long long opslimit, size_t memlimit); SODIUM_EXPORT int crypto_pwhash_scryptsalsa208sha256_str_verify(const char str[crypto_pwhash_scryptsalsa208sha256_STRBYTES], const char * const passwd, unsigned long long passwdlen); SODIUM_EXPORT int crypto_pwhash_scryptsalsa208sha256_ll(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t r, uint32_t p, uint8_t * buf, size_t buflen); #ifdef __cplusplus } #endif /* Backward compatibility with version 0.5.0 */ #define crypto_pwhash_scryptxsalsa208sha256_SALTBYTES crypto_pwhash_scryptsalsa208sha256_SALTBYTES #define crypto_pwhash_scryptxsalsa208sha256_saltbytes crypto_pwhash_scryptsalsa208sha256_saltbytes #define crypto_pwhash_scryptxsalsa208sha256_STRBYTES crypto_pwhash_scryptsalsa208sha256_STRBYTES #define crypto_pwhash_scryptxsalsa208sha256_strbytes crypto_pwhash_scryptsalsa208sha256_strbytes #define crypto_pwhash_scryptxsalsa208sha256 crypto_pwhash_scryptsalsa208sha256 #define crypto_pwhash_scryptxsalsa208sha256_str crypto_pwhash_scryptsalsa208sha256_str #define crypto_pwhash_scryptxsalsa208sha256_str_verify crypto_pwhash_scryptsalsa208sha256_str_verify #endif #endif ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/crypto_scrypt-common.c ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ /*- * Copyright 2013 Alexander Peslyak * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include "crypto_pwhash_scryptsalsa208sha256.h" #include "crypto_scrypt.h" #include "runtime.h" #include "utils.h" static const char * const itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; static uint8_t * encode64_uint32(uint8_t * dst, size_t dstlen, uint32_t src, uint32_t srcbits) { uint32_t bit; for (bit = 0; bit < srcbits; bit += 6) { if (dstlen < 1) { return NULL; } *dst++ = itoa64[src & 0x3f]; dstlen--; src >>= 6; } return dst; } static uint8_t * encode64(uint8_t * dst, size_t dstlen, const uint8_t * src, size_t srclen) { size_t i; for (i = 0; i < srclen; ) { uint8_t * dnext; uint32_t value = 0, bits = 0; do { value |= (uint32_t)src[i++] << bits; bits += 8; } while (bits < 24 && i < srclen); dnext = encode64_uint32(dst, dstlen, value, bits); if (!dnext) { return NULL; } dstlen -= dnext - dst; dst = dnext; } return dst; } static int decode64_one(uint32_t * dst, uint8_t src) { const char *ptr = strchr(itoa64, src); if (ptr) { *dst = ptr - itoa64; return 0; } *dst = 0; return -1; } static const uint8_t * decode64_uint32(uint32_t * dst, uint32_t dstbits, const uint8_t * src) { uint32_t bit; uint32_t value; value = 0; for (bit = 0; bit < dstbits; bit += 6) { uint32_t one; if (decode64_one(&one, *src)) { *dst = 0; return NULL; } src++; value |= one << bit; } *dst = value; return src; } uint8_t * escrypt_r(escrypt_local_t * local, const uint8_t * passwd, size_t passwdlen, const uint8_t * setting, uint8_t * buf, size_t buflen) { uint8_t hash[crypto_pwhash_scryptsalsa208sha256_STRHASHBYTES]; escrypt_kdf_t escrypt_kdf; const uint8_t *src; const uint8_t *salt; uint8_t *dst; size_t prefixlen; size_t saltlen; size_t need; uint64_t N; uint32_t N_log2; uint32_t r; uint32_t p; if (setting[0] != '$' || setting[1] != '7' || setting[2] != '$') { return NULL; } src = setting + 3; if (decode64_one(&N_log2, *src)) { return NULL; } src++; N = (uint64_t)1 << N_log2; src = decode64_uint32(&r, 30, src); if (!src) { return NULL; } src = decode64_uint32(&p, 30, src); if (!src) { return NULL; } prefixlen = src - setting; salt = src; src = (uint8_t *) strrchr((char *)salt, '$'); if (src) { saltlen = src - salt; } else { saltlen = strlen((char *)salt); } need = prefixlen + saltlen + 1 + crypto_pwhash_scryptsalsa208sha256_STRHASHBYTES_ENCODED + 1; if (need > buflen || need < saltlen) { return NULL; } #if defined(HAVE_EMMINTRIN_H) || defined(_MSC_VER) escrypt_kdf = sodium_runtime_has_sse2() ? escrypt_kdf_sse : escrypt_kdf_nosse; #else escrypt_kdf = escrypt_kdf_nosse; #endif if (escrypt_kdf(local, passwd, passwdlen, salt, saltlen, N, r, p, hash, sizeof(hash))) { return NULL; } dst = buf; memcpy(dst, setting, prefixlen + saltlen); dst += prefixlen + saltlen; *dst++ = '$'; dst = encode64(dst, buflen - (dst - buf), hash, sizeof(hash)); sodium_memzero(hash, sizeof hash); if (!dst || dst >= buf + buflen) { /* Can't happen */ return NULL; } *dst = 0; /* NUL termination */ return buf; } uint8_t * escrypt_gensalt_r(uint32_t N_log2, uint32_t r, uint32_t p, const uint8_t * src, size_t srclen, uint8_t * buf, size_t buflen) { uint8_t *dst; size_t prefixlen = (sizeof "$7$" - 1U) + (1U /* N_log2 */) + (5U /* r */) + (5U /* p */); size_t saltlen = BYTES2CHARS(srclen); size_t need; need = prefixlen + saltlen + 1; if (need > buflen || need < saltlen || saltlen < srclen) { return NULL; } if (N_log2 > 63 || ((uint64_t)r * (uint64_t)p >= (1U << 30))) { return NULL; } dst = buf; *dst++ = '$'; *dst++ = '7'; *dst++ = '$'; *dst++ = itoa64[N_log2]; dst = encode64_uint32(dst, buflen - (dst - buf), r, 30); if (!dst) { /* Can't happen */ return NULL; } dst = encode64_uint32(dst, buflen - (dst - buf), p, 30); if (!dst) { /* Can't happen */ return NULL; } dst = encode64(dst, buflen - (dst - buf), src, srclen); if (!dst || dst >= buf + buflen) { /* Can't happen */ return NULL; } *dst = 0; /* NUL termination */ return buf; } int crypto_pwhash_scryptsalsa208sha256_ll(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t r, uint32_t p, uint8_t * buf, size_t buflen) { escrypt_kdf_t escrypt_kdf; escrypt_local_t local; int retval; if (escrypt_init_local(&local)) { return -1; } #if defined(HAVE_EMMINTRIN_H) || defined(_MSC_VER) escrypt_kdf = sodium_runtime_has_sse2() ? escrypt_kdf_sse : escrypt_kdf_nosse; #else escrypt_kdf = escrypt_kdf_nosse; #endif retval = escrypt_kdf(&local, passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen); if (escrypt_free_local(&local)) { return -1; } return retval; } #endif ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/crypto_scrypt.h ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ /*- * Copyright 2009 Colin Percival * Copyright 2013 Alexander Peslyak * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This file was originally written by Colin Percival as part of the Tarsnap * online backup system. */ #ifndef _CRYPTO_SCRYPT_H_ #define _CRYPTO_SCRYPT_H_ #include #define crypto_pwhash_scryptsalsa208sha256_STRPREFIXBYTES 14 #define crypto_pwhash_scryptsalsa208sha256_STRSETTINGBYTES 57 #define crypto_pwhash_scryptsalsa208sha256_STRSALTBYTES 32 #define crypto_pwhash_scryptsalsa208sha256_STRSALTBYTES_ENCODED 43 #define crypto_pwhash_scryptsalsa208sha256_STRHASHBYTES 32 #define crypto_pwhash_scryptsalsa208sha256_STRHASHBYTES_ENCODED 43 #define BYTES2CHARS(bytes) ((((bytes) * 8) + 5) / 6) typedef struct { void * base, * aligned; size_t size; } escrypt_region_t; typedef escrypt_region_t escrypt_local_t; extern int escrypt_init_local(escrypt_local_t * __local); extern int escrypt_free_local(escrypt_local_t * __local); extern void *alloc_region(escrypt_region_t * region, size_t size); extern int free_region(escrypt_region_t * region); typedef int (*escrypt_kdf_t)(escrypt_local_t * __local, const uint8_t * __passwd, size_t __passwdlen, const uint8_t * __salt, size_t __saltlen, uint64_t __N, uint32_t __r, uint32_t __p, uint8_t * __buf, size_t __buflen); extern int escrypt_kdf_nosse(escrypt_local_t * __local, const uint8_t * __passwd, size_t __passwdlen, const uint8_t * __salt, size_t __saltlen, uint64_t __N, uint32_t __r, uint32_t __p, uint8_t * __buf, size_t __buflen); extern int escrypt_kdf_sse(escrypt_local_t * __local, const uint8_t * __passwd, size_t __passwdlen, const uint8_t * __salt, size_t __saltlen, uint64_t __N, uint32_t __r, uint32_t __p, uint8_t * __buf, size_t __buflen); extern uint8_t * escrypt_r(escrypt_local_t * __local, const uint8_t * __passwd, size_t __passwdlen, const uint8_t * __setting, uint8_t * __buf, size_t __buflen); extern uint8_t * escrypt_gensalt_r( uint32_t __N_log2, uint32_t __r, uint32_t __p, const uint8_t * __src, size_t __srclen, uint8_t * __buf, size_t __buflen); #endif /* !_CRYPTO_SCRYPT_H_ */ #endif ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/export.h ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ #ifndef __SODIUM_EXPORT_H__ #define __SODIUM_EXPORT_H__ #ifndef __GNUC__ # ifdef __attribute__ # undef __attribute__ # endif # define __attribute__(a) #endif #ifdef SODIUM_STATIC # define SODIUM_EXPORT #else # if defined(_MSC_VER) # ifdef DLL_EXPORT # define SODIUM_EXPORT __declspec(dllexport) # else # define SODIUM_EXPORT __declspec(dllimport) # endif # else # if defined(__SUNPRO_C) # define SODIUM_EXPORT __attribute__ __global # elif defined(_MSG_VER) # define SODIUM_EXPORT extern __declspec(dllexport) # else # define SODIUM_EXPORT __attribute__ ((visibility ("default"))) # endif # endif #endif #endif #endif ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ /*- * Copyright 2009 Colin Percival * Copyright 2013 Alexander Peslyak * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This file was originally written by Colin Percival as part of the Tarsnap * online backup system. */ #include #include #include #include #include #include "../pbkdf2-sha256.h" #include "../sysendian.h" #include "../crypto_scrypt.h" static inline void blkcpy(void * dest, const void * src, size_t len) { size_t * D = (size_t *) dest; const size_t * S = (const size_t *) src; size_t L = len / sizeof(size_t); size_t i; for (i = 0; i < L; i++) D[i] = S[i]; } static inline void blkxor(void * dest, const void * src, size_t len) { size_t * D = (size_t *) dest; const size_t * S = (const size_t *) src; size_t L = len / sizeof(size_t); size_t i; for (i = 0; i < L; i++) D[i] ^= S[i]; } /** * salsa20_8(B): * Apply the salsa20/8 core to the provided block. */ static void salsa20_8(uint32_t B[16]) { uint32_t x[16]; size_t i; blkcpy(x, B, 64); for (i = 0; i < 8; i += 2) { #define R(a,b) (((a) << (b)) | ((a) >> (32 - (b)))) /* Operate on columns. */ x[ 4] ^= R(x[ 0]+x[12], 7); x[ 8] ^= R(x[ 4]+x[ 0], 9); x[12] ^= R(x[ 8]+x[ 4],13); x[ 0] ^= R(x[12]+x[ 8],18); x[ 9] ^= R(x[ 5]+x[ 1], 7); x[13] ^= R(x[ 9]+x[ 5], 9); x[ 1] ^= R(x[13]+x[ 9],13); x[ 5] ^= R(x[ 1]+x[13],18); x[14] ^= R(x[10]+x[ 6], 7); x[ 2] ^= R(x[14]+x[10], 9); x[ 6] ^= R(x[ 2]+x[14],13); x[10] ^= R(x[ 6]+x[ 2],18); x[ 3] ^= R(x[15]+x[11], 7); x[ 7] ^= R(x[ 3]+x[15], 9); x[11] ^= R(x[ 7]+x[ 3],13); x[15] ^= R(x[11]+x[ 7],18); /* Operate on rows. */ x[ 1] ^= R(x[ 0]+x[ 3], 7); x[ 2] ^= R(x[ 1]+x[ 0], 9); x[ 3] ^= R(x[ 2]+x[ 1],13); x[ 0] ^= R(x[ 3]+x[ 2],18); x[ 6] ^= R(x[ 5]+x[ 4], 7); x[ 7] ^= R(x[ 6]+x[ 5], 9); x[ 4] ^= R(x[ 7]+x[ 6],13); x[ 5] ^= R(x[ 4]+x[ 7],18); x[11] ^= R(x[10]+x[ 9], 7); x[ 8] ^= R(x[11]+x[10], 9); x[ 9] ^= R(x[ 8]+x[11],13); x[10] ^= R(x[ 9]+x[ 8],18); x[12] ^= R(x[15]+x[14], 7); x[13] ^= R(x[12]+x[15], 9); x[14] ^= R(x[13]+x[12],13); x[15] ^= R(x[14]+x[13],18); #undef R } for (i = 0; i < 16; i++) B[i] += x[i]; } /** * blockmix_salsa8(Bin, Bout, X, r): * Compute Bout = BlockMix_{salsa20/8, r}(Bin). The input Bin must be 128r * bytes in length; the output Bout must also be the same size. The * temporary space X must be 64 bytes. */ static void blockmix_salsa8(const uint32_t * Bin, uint32_t * Bout, uint32_t * X, size_t r) { size_t i; /* 1: X <-- B_{2r - 1} */ blkcpy(X, &Bin[(2 * r - 1) * 16], 64); /* 2: for i = 0 to 2r - 1 do */ for (i = 0; i < 2 * r; i += 2) { /* 3: X <-- H(X \xor B_i) */ blkxor(X, &Bin[i * 16], 64); salsa20_8(X); /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ blkcpy(&Bout[i * 8], X, 64); /* 3: X <-- H(X \xor B_i) */ blkxor(X, &Bin[i * 16 + 16], 64); salsa20_8(X); /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ blkcpy(&Bout[i * 8 + r * 16], X, 64); } } /** * integerify(B, r): * Return the result of parsing B_{2r-1} as a little-endian integer. */ static inline uint64_t integerify(const void * B, size_t r) { const uint32_t * X = (const uint32_t *)((uintptr_t)(B) + (2 * r - 1) * 64); return (((uint64_t)(X[1]) << 32) + X[0]); } /** * smix(B, r, N, V, XY): * Compute B = SMix_r(B, N). The input B must be 128r bytes in length; * the temporary storage V must be 128rN bytes in length; the temporary * storage XY must be 256r + 64 bytes in length. The value N must be a * power of 2 greater than 1. The arrays B, V, and XY must be aligned to a * multiple of 64 bytes. */ static void smix(uint8_t * B, size_t r, uint64_t N, uint32_t * V, uint32_t * XY) { uint32_t * X = XY; uint32_t * Y = &XY[32 * r]; uint32_t * Z = &XY[64 * r]; uint64_t i; uint64_t j; size_t k; /* 1: X <-- B */ for (k = 0; k < 32 * r; k++) X[k] = le32dec(&B[4 * k]); /* 2: for i = 0 to N - 1 do */ for (i = 0; i < N; i += 2) { /* 3: V_i <-- X */ blkcpy(&V[i * (32 * r)], X, 128 * r); /* 4: X <-- H(X) */ blockmix_salsa8(X, Y, Z, r); /* 3: V_i <-- X */ blkcpy(&V[(i + 1) * (32 * r)], Y, 128 * r); /* 4: X <-- H(X) */ blockmix_salsa8(Y, X, Z, r); } /* 6: for i = 0 to N - 1 do */ for (i = 0; i < N; i += 2) { /* 7: j <-- Integerify(X) mod N */ j = integerify(X, r) & (N - 1); /* 8: X <-- H(X \xor V_j) */ blkxor(X, &V[j * (32 * r)], 128 * r); blockmix_salsa8(X, Y, Z, r); /* 7: j <-- Integerify(X) mod N */ j = integerify(Y, r) & (N - 1); /* 8: X <-- H(X \xor V_j) */ blkxor(Y, &V[j * (32 * r)], 128 * r); blockmix_salsa8(Y, X, Z, r); } /* 10: B' <-- X */ for (k = 0; k < 32 * r; k++) le32enc(&B[4 * k], X[k]); } /** * escrypt_kdf(local, passwd, passwdlen, salt, saltlen, * N, r, p, buf, buflen): * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r, * p, buflen) and write the result into buf. The parameters r, p, and buflen * must satisfy r * p < 2^30 and buflen <= (2^32 - 1) * 32. The parameter N * must be a power of 2 greater than 1. * * Return 0 on success; or -1 on error. */ int escrypt_kdf_nosse(escrypt_local_t * local, const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t _r, uint32_t _p, uint8_t * buf, size_t buflen) { size_t B_size, V_size, XY_size, need; uint8_t * B; uint32_t * V, * XY; size_t r = _r, p = _p; uint32_t i; /* Sanity-check parameters. */ #if SIZE_MAX > UINT32_MAX if (buflen > (((uint64_t)(1) << 32) - 1) * 32) { errno = EFBIG; return -1; } #endif if ((uint64_t)(r) * (uint64_t)(p) >= (1 << 30)) { errno = EFBIG; return -1; } if (((N & (N - 1)) != 0) || (N < 2)) { errno = EINVAL; return -1; } if (r == 0 || p == 0) { errno = EINVAL; return -1; } if ((r > SIZE_MAX / 128 / p) || #if SIZE_MAX / 256 <= UINT32_MAX (r > SIZE_MAX / 256) || #endif (N > SIZE_MAX / 128 / r)) { errno = ENOMEM; return -1; } /* Allocate memory. */ B_size = (size_t)128 * r * p; V_size = (size_t)128 * r * N; need = B_size + V_size; if (need < V_size) { errno = ENOMEM; return -1; } XY_size = (size_t)256 * r + 64; need += XY_size; if (need < XY_size) { errno = ENOMEM; return -1; } if (local->size < need) { if (free_region(local)) return -1; if (!alloc_region(local, need)) return -1; } B = (uint8_t *)local->aligned; V = (uint32_t *)((uint8_t *)B + B_size); XY = (uint32_t *)((uint8_t *)V + V_size); /* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */ PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, 1, B, B_size); /* 2: for i = 0 to p - 1 do */ for (i = 0; i < p; i++) { /* 3: B_i <-- MF(B_i, N) */ smix(&B[(size_t)128 * i * r], r, N, V, XY); } /* 5: DK <-- PBKDF2(P, B, 1, dkLen) */ PBKDF2_SHA256(passwd, passwdlen, B, B_size, 1, buf, buflen); /* Success! */ return 0; } #endif ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/note_to_maintainers.txt ================================================ This folder is only meant for use with nacl, i.e. when sodium is unavailable. The files in this folder were mostly copied from https://github.com/jedisct1/libsodium/tree/0.7.0/src/libsodium/crypto_pwhash/scryptsalsa208sha256, with #ifdef VANILLA_NACL added around each of them as required for this module. export.h, utils.h, and runtime.h were copied from https://github.com/jedisct1/libsodium/tree/0.7.0/src/libsodium/include/sodium. utils.h was significantly truncated. utils.c and runtime.c were copied from https://github.com/jedisct1/libsodium/blob/0.7.0/src/libsodium/sodium. utils.c was also significantly truncated. ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/pbkdf2-sha256.c ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ /*- * Copyright 2005,2007,2009 Colin Percival * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include "pbkdf2-sha256.h" #include "sysendian.h" #include "utils.h" /** * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). */ void PBKDF2_SHA256(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, size_t saltlen, uint64_t c, uint8_t * buf, size_t dkLen) { uint8_t key[32] = {0}; size_t i; uint8_t salt_and_ivec[saltlen + 4]; uint8_t U[32]; uint8_t T[32]; uint64_t j; int k; size_t clen; if (passwdlen > 32) { /* For some reason libsodium allows 64byte keys meaning keys * between 32byte and 64bytes are not compatible with libsodium. toxencryptsave should only give 32byte passwds so this isn't an issue here.*/ crypto_hash_sha256(key, passwd, passwdlen); } else { memcpy(key, passwd, passwdlen); } memcpy(salt_and_ivec, salt, saltlen); for (i = 0; i * 32 < dkLen; i++) { be32enc(salt_and_ivec + saltlen, (uint32_t)(i + 1)); crypto_auth_hmacsha256(U, salt_and_ivec, sizeof(salt_and_ivec), key); memcpy(T, U, 32); for (j = 2; j <= c; j++) { crypto_auth_hmacsha256(U, U, 32, key); for (k = 0; k < 32; k++) { T[k] ^= U[k]; } } clen = dkLen - i * 32; if (clen > 32) { clen = 32; } memcpy(&buf[i * 32], T, clen); } sodium_memzero((void *) key, sizeof(key)); } #endif ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/pbkdf2-sha256.h ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ /*- * Copyright 2005,2007,2009 Colin Percival * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #ifndef _SHA256_H_ #define _SHA256_H_ #include #include #include "crypto_auth_hmacsha256.h" /** * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). */ void PBKDF2_SHA256(const uint8_t *, size_t, const uint8_t *, size_t, uint64_t, uint8_t *, size_t); #endif /* !_SHA256_H_ */ #endif ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ #include #include #include #include #include //#include #include "crypto_pwhash_scryptsalsa208sha256.h" #include "crypto_scrypt.h" #include "randombytes.h" #include "utils.h" #define SETTING_SIZE(saltbytes) \ (sizeof "$7$" - 1U) + \ (1U /* N_log2 */) + (5U /* r */) + (5U /* p */) + BYTES2CHARS(saltbytes) static int pickparams(unsigned long long opslimit, const size_t memlimit, uint32_t * const N_log2, uint32_t * const p, uint32_t * const r) { unsigned long long maxN; unsigned long long maxrp; if (opslimit < 32768) { opslimit = 32768; } *r = 8; if (opslimit < memlimit / 32) { *p = 1; maxN = opslimit / (*r * 4); for (*N_log2 = 1; *N_log2 < 63; *N_log2 += 1) { if ((uint64_t)(1) << *N_log2 > maxN / 2) { break; } } } else { maxN = memlimit / (*r * 128); for (*N_log2 = 1; *N_log2 < 63; *N_log2 += 1) { if ((uint64_t) (1) << *N_log2 > maxN / 2) { break; } } maxrp = (opslimit / 4) / ((uint64_t) (1) << *N_log2); if (maxrp > 0x3fffffff) { maxrp = 0x3fffffff; } *p = (uint32_t) (maxrp) / *r; } return 0; } size_t crypto_pwhash_scryptsalsa208sha256_saltbytes(void) { return crypto_pwhash_scryptsalsa208sha256_SALTBYTES; } size_t crypto_pwhash_scryptsalsa208sha256_strbytes(void) { return crypto_pwhash_scryptsalsa208sha256_STRBYTES; } const char * crypto_pwhash_scryptsalsa208sha256_strprefix(void) { return crypto_pwhash_scryptsalsa208sha256_STRPREFIX; } size_t crypto_pwhash_scryptsalsa208sha256_opslimit_interactive(void) { return crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE; } size_t crypto_pwhash_scryptsalsa208sha256_memlimit_interactive(void) { return crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE; } size_t crypto_pwhash_scryptsalsa208sha256_opslimit_sensitive(void) { return crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE; } size_t crypto_pwhash_scryptsalsa208sha256_memlimit_sensitive(void) { return crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE; } int crypto_pwhash_scryptsalsa208sha256(unsigned char * const out, unsigned long long outlen, const char * const passwd, unsigned long long passwdlen, const unsigned char * const salt, unsigned long long opslimit, size_t memlimit) { //fprintf(stderr, "Doing that dirty thang!!!!\n"); uint32_t N_log2; uint32_t p; uint32_t r; memset(out, 0, outlen); if (passwdlen > SIZE_MAX || outlen > SIZE_MAX) { errno = EFBIG; return -1; } if (pickparams(opslimit, memlimit, &N_log2, &p, &r) != 0) { errno = EINVAL; return -1; } return crypto_pwhash_scryptsalsa208sha256_ll((const uint8_t *) passwd, (size_t) passwdlen, (const uint8_t *) salt, crypto_pwhash_scryptsalsa208sha256_SALTBYTES, (uint64_t) (1) << N_log2, r, p, out, (size_t) outlen); } int crypto_pwhash_scryptsalsa208sha256_str(char out[crypto_pwhash_scryptsalsa208sha256_STRBYTES], const char * const passwd, unsigned long long passwdlen, unsigned long long opslimit, size_t memlimit) { uint8_t salt[crypto_pwhash_scryptsalsa208sha256_STRSALTBYTES]; char setting[crypto_pwhash_scryptsalsa208sha256_STRSETTINGBYTES + 1U]; escrypt_local_t escrypt_local; uint32_t N_log2; uint32_t p; uint32_t r; memset(out, 0, crypto_pwhash_scryptsalsa208sha256_STRBYTES); if (passwdlen > SIZE_MAX) { errno = EFBIG; return -1; } if (pickparams(opslimit, memlimit, &N_log2, &p, &r) != 0) { errno = EINVAL; return -1; } randombytes(salt, sizeof salt); if (escrypt_gensalt_r(N_log2, r, p, salt, sizeof salt, (uint8_t *) setting, sizeof setting) == NULL) { errno = EINVAL; return -1; } if (escrypt_init_local(&escrypt_local) != 0) { return -1; } if (escrypt_r(&escrypt_local, (const uint8_t *) passwd, (size_t) passwdlen, (const uint8_t *) setting, (uint8_t *) out, crypto_pwhash_scryptsalsa208sha256_STRBYTES) == NULL) { escrypt_free_local(&escrypt_local); errno = EINVAL; return -1; } escrypt_free_local(&escrypt_local); (void) sizeof (int[SETTING_SIZE(crypto_pwhash_scryptsalsa208sha256_STRSALTBYTES) == crypto_pwhash_scryptsalsa208sha256_STRSETTINGBYTES ? 1 : -1]); (void) sizeof (int[crypto_pwhash_scryptsalsa208sha256_STRSETTINGBYTES + 1U + crypto_pwhash_scryptsalsa208sha256_STRHASHBYTES_ENCODED + 1U == crypto_pwhash_scryptsalsa208sha256_STRBYTES ? 1 : -1]); return 0; } int crypto_pwhash_scryptsalsa208sha256_str_verify(const char str[crypto_pwhash_scryptsalsa208sha256_STRBYTES], const char * const passwd, unsigned long long passwdlen) { char wanted[crypto_pwhash_scryptsalsa208sha256_STRBYTES]; escrypt_local_t escrypt_local; int ret = -1; if (memchr(str, 0, crypto_pwhash_scryptsalsa208sha256_STRBYTES) != &str[crypto_pwhash_scryptsalsa208sha256_STRBYTES - 1U]) { return -1; } if (escrypt_init_local(&escrypt_local) != 0) { return -1; } if (escrypt_r(&escrypt_local, (const uint8_t *) passwd, (size_t) passwdlen, (const uint8_t *) str, (uint8_t *) wanted, sizeof wanted) == NULL) { escrypt_free_local(&escrypt_local); return -1; } escrypt_free_local(&escrypt_local); ret = sodium_memcmp(wanted, str, sizeof wanted); sodium_memzero(wanted, sizeof wanted); return ret; } #endif ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/runtime.c ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ #ifdef HAVE_ANDROID_GETCPUFEATURES # include #endif #include "runtime.h" typedef struct CPUFeatures_ { int initialized; int has_neon; int has_sse2; int has_sse3; } CPUFeatures; static CPUFeatures _cpu_features; #define CPUID_SSE2 0x04000000 #define CPUIDECX_SSE3 0x00000001 static int _sodium_runtime_arm_cpu_features(CPUFeatures * const cpu_features) { #ifndef __arm__ cpu_features->has_neon = 0; return -1; #else # ifdef __APPLE__ # ifdef __ARM_NEON__ cpu_features->has_neon = 1; # else cpu_features->has_neon = 0; # endif # elif defined(HAVE_ANDROID_GETCPUFEATURES) && defined(ANDROID_CPU_ARM_FEATURE_NEON) cpu_features->has_neon = (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON) != 0x0; # else cpu_features->has_neon = 0; # endif return 0; #endif } static void _cpuid(unsigned int cpu_info[4U], const unsigned int cpu_info_type) { #ifdef _MSC_VER __cpuidex((int *) cpu_info, cpu_info_type, 0); #elif defined(HAVE_CPUID) cpu_info[0] = cpu_info[1] = cpu_info[2] = cpu_info[3] = 0; # ifdef __i386__ __asm__ __volatile__ ("pushfl; pushfl; " "popl %0; " "movl %0, %1; xorl %2, %0; " "pushl %0; " "popfl; pushfl; popl %0; popfl" : "=&r" (cpu_info[0]), "=&r" (cpu_info[1]) : "i" (0x200000)); if (((cpu_info[0] ^ cpu_info[1]) & 0x200000) == 0x0) { return; } # endif # ifdef __i386__ __asm__ __volatile__ ("xchgl %%ebx, %k1; cpuid; xchgl %%ebx, %k1" : "=a" (cpu_info[0]), "=&r" (cpu_info[1]), "=c" (cpu_info[2]), "=d" (cpu_info[3]) : "0" (cpu_info_type), "2" (0U)); # elif defined(__x86_64__) __asm__ __volatile__ ("xchgq %%rbx, %q1; cpuid; xchgq %%rbx, %q1" : "=a" (cpu_info[0]), "=&r" (cpu_info[1]), "=c" (cpu_info[2]), "=d" (cpu_info[3]) : "0" (cpu_info_type), "2" (0U)); # else __asm__ __volatile__ ("cpuid" : "=a" (cpu_info[0]), "=b" (cpu_info[1]), "=c" (cpu_info[2]), "=d" (cpu_info[3]) : "0" (cpu_info_type), "2" (0U)); # endif #else cpu_info[0] = cpu_info[1] = cpu_info[2] = cpu_info[3] = 0; #endif } static int _sodium_runtime_intel_cpu_features(CPUFeatures * const cpu_features) { unsigned int cpu_info[4]; unsigned int id; _cpuid(cpu_info, 0x0); if ((id = cpu_info[0]) == 0U) { return -1; } _cpuid(cpu_info, 0x00000001); #ifndef HAVE_EMMINTRIN_H cpu_features->has_sse2 = 0; #else cpu_features->has_sse2 = ((cpu_info[3] & CPUID_SSE2) != 0x0); #endif #ifndef HAVE_PMMINTRIN_H cpu_features->has_sse3 = 0; #else cpu_features->has_sse3 = ((cpu_info[2] & CPUIDECX_SSE3) != 0x0); #endif return 0; } int sodium_runtime_get_cpu_features(void) { int ret = -1; ret &= _sodium_runtime_arm_cpu_features(&_cpu_features); ret &= _sodium_runtime_intel_cpu_features(&_cpu_features); _cpu_features.initialized = 1; return ret; } int sodium_runtime_has_neon(void) { return _cpu_features.has_neon; } int sodium_runtime_has_sse2(void) { return _cpu_features.has_sse2; } int sodium_runtime_has_sse3(void) { return _cpu_features.has_sse3; } #endif ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/runtime.h ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ #ifndef __SODIUM_RUNTIME_H__ #define __SODIUM_RUNTIME_H__ 1 #include "export.h" #ifdef __cplusplus extern "C" { #endif SODIUM_EXPORT int sodium_runtime_get_cpu_features(void); SODIUM_EXPORT int sodium_runtime_has_neon(void); SODIUM_EXPORT int sodium_runtime_has_sse2(void); SODIUM_EXPORT int sodium_runtime_has_sse3(void); #ifdef __cplusplus } #endif #endif #endif ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/scrypt_platform.c ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ /*- * Copyright 2013 Alexander Peslyak * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifdef HAVE_SYS_MMAN_H # include #endif #include #include #include "crypto_scrypt.h" #include "runtime.h" #if !defined(MAP_ANON) && defined(MAP_ANONYMOUS) # define MAP_ANON MAP_ANONYMOUS #endif void * alloc_region(escrypt_region_t * region, size_t size) { uint8_t * base, * aligned; #ifdef MAP_ANON if ((base = (uint8_t *) mmap(NULL, size, PROT_READ | PROT_WRITE, #ifdef MAP_NOCORE MAP_ANON | MAP_PRIVATE | MAP_NOCORE, #else MAP_ANON | MAP_PRIVATE, #endif -1, 0)) == MAP_FAILED) base = NULL; aligned = base; #elif defined(HAVE_POSIX_MEMALIGN) if ((errno = posix_memalign((void **) &base, 64, size)) != 0) base = NULL; aligned = base; #else base = aligned = NULL; if (size + 63 < size) errno = ENOMEM; else if ((base = (uint8_t *) malloc(size + 63)) != NULL) { aligned = base + 63; aligned -= (uintptr_t)aligned & 63; } #endif region->base = base; region->aligned = aligned; region->size = base ? size : 0; return aligned; } static inline void init_region(escrypt_region_t * region) { region->base = region->aligned = NULL; region->size = 0; } int free_region(escrypt_region_t * region) { if (region->base) { #ifdef MAP_ANON if (munmap(region->base, region->size)) return -1; #else free(region->base); #endif } init_region(region); return 0; } int escrypt_init_local(escrypt_local_t * local) { init_region(local); return 0; } int escrypt_free_local(escrypt_local_t * local) { return free_region(local); } #endif ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/sse/pwhash_scryptsalsa208sha256_sse.c ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ /*- * Copyright 2009 Colin Percival * Copyright 2012,2013 Alexander Peslyak * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This file was originally written by Colin Percival as part of the Tarsnap * online backup system. */ #if defined(HAVE_EMMINTRIN_H) || defined(_MSC_VER) #if __GNUC__ # pragma GCC target("sse2") #endif #include #if defined(__XOP__) && defined(DISABLED) # include #endif #include #include #include #include #include #include "../pbkdf2-sha256.h" #include "../sysendian.h" #include "../crypto_scrypt.h" #if defined(__XOP__) && defined(DISABLED) #define ARX(out, in1, in2, s) \ out = _mm_xor_si128(out, _mm_roti_epi32(_mm_add_epi32(in1, in2), s)); #else #define ARX(out, in1, in2, s) \ { \ __m128i T = _mm_add_epi32(in1, in2); \ out = _mm_xor_si128(out, _mm_slli_epi32(T, s)); \ out = _mm_xor_si128(out, _mm_srli_epi32(T, 32-s)); \ } #endif #define SALSA20_2ROUNDS \ /* Operate on "columns". */ \ ARX(X1, X0, X3, 7) \ ARX(X2, X1, X0, 9) \ ARX(X3, X2, X1, 13) \ ARX(X0, X3, X2, 18) \ \ /* Rearrange data. */ \ X1 = _mm_shuffle_epi32(X1, 0x93); \ X2 = _mm_shuffle_epi32(X2, 0x4E); \ X3 = _mm_shuffle_epi32(X3, 0x39); \ \ /* Operate on "rows". */ \ ARX(X3, X0, X1, 7) \ ARX(X2, X3, X0, 9) \ ARX(X1, X2, X3, 13) \ ARX(X0, X1, X2, 18) \ \ /* Rearrange data. */ \ X1 = _mm_shuffle_epi32(X1, 0x39); \ X2 = _mm_shuffle_epi32(X2, 0x4E); \ X3 = _mm_shuffle_epi32(X3, 0x93); /** * Apply the salsa20/8 core to the block provided in (X0 ... X3) ^ (Z0 ... Z3). */ #define SALSA20_8_XOR(in, out) \ { \ __m128i Y0 = X0 = _mm_xor_si128(X0, (in)[0]); \ __m128i Y1 = X1 = _mm_xor_si128(X1, (in)[1]); \ __m128i Y2 = X2 = _mm_xor_si128(X2, (in)[2]); \ __m128i Y3 = X3 = _mm_xor_si128(X3, (in)[3]); \ SALSA20_2ROUNDS \ SALSA20_2ROUNDS \ SALSA20_2ROUNDS \ SALSA20_2ROUNDS \ (out)[0] = X0 = _mm_add_epi32(X0, Y0); \ (out)[1] = X1 = _mm_add_epi32(X1, Y1); \ (out)[2] = X2 = _mm_add_epi32(X2, Y2); \ (out)[3] = X3 = _mm_add_epi32(X3, Y3); \ } /** * blockmix_salsa8(Bin, Bout, r): * Compute Bout = BlockMix_{salsa20/8, r}(Bin). The input Bin must be 128r * bytes in length; the output Bout must also be the same size. */ static inline void blockmix_salsa8(const __m128i * Bin, __m128i * Bout, size_t r) { __m128i X0, X1, X2, X3; size_t i; /* 1: X <-- B_{2r - 1} */ X0 = Bin[8 * r - 4]; X1 = Bin[8 * r - 3]; X2 = Bin[8 * r - 2]; X3 = Bin[8 * r - 1]; /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ SALSA20_8_XOR(Bin, Bout) /* 2: for i = 0 to 2r - 1 do */ r--; for (i = 0; i < r;) { /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ SALSA20_8_XOR(&Bin[i * 8 + 4], &Bout[(r + i) * 4 + 4]) i++; /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ SALSA20_8_XOR(&Bin[i * 8], &Bout[i * 4]) } /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ SALSA20_8_XOR(&Bin[i * 8 + 4], &Bout[(r + i) * 4 + 4]) } #define XOR4(in) \ X0 = _mm_xor_si128(X0, (in)[0]); \ X1 = _mm_xor_si128(X1, (in)[1]); \ X2 = _mm_xor_si128(X2, (in)[2]); \ X3 = _mm_xor_si128(X3, (in)[3]); #define XOR4_2(in1, in2) \ X0 = _mm_xor_si128((in1)[0], (in2)[0]); \ X1 = _mm_xor_si128((in1)[1], (in2)[1]); \ X2 = _mm_xor_si128((in1)[2], (in2)[2]); \ X3 = _mm_xor_si128((in1)[3], (in2)[3]); static inline uint32_t blockmix_salsa8_xor(const __m128i * Bin1, const __m128i * Bin2, __m128i * Bout, size_t r) { __m128i X0, X1, X2, X3; size_t i; /* 1: X <-- B_{2r - 1} */ XOR4_2(&Bin1[8 * r - 4], &Bin2[8 * r - 4]) /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ XOR4(Bin1) SALSA20_8_XOR(Bin2, Bout) /* 2: for i = 0 to 2r - 1 do */ r--; for (i = 0; i < r;) { /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ XOR4(&Bin1[i * 8 + 4]) SALSA20_8_XOR(&Bin2[i * 8 + 4], &Bout[(r + i) * 4 + 4]) i++; /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ XOR4(&Bin1[i * 8]) SALSA20_8_XOR(&Bin2[i * 8], &Bout[i * 4]) } /* 3: X <-- H(X \xor B_i) */ /* 4: Y_i <-- X */ /* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */ XOR4(&Bin1[i * 8 + 4]) SALSA20_8_XOR(&Bin2[i * 8 + 4], &Bout[(r + i) * 4 + 4]) return _mm_cvtsi128_si32(X0); } #undef ARX #undef SALSA20_2ROUNDS #undef SALSA20_8_XOR #undef XOR4 #undef XOR4_2 /** * integerify(B, r): * Return the result of parsing B_{2r-1} as a little-endian integer. */ static inline uint32_t integerify(const void * B, size_t r) { return *(const uint32_t *)((uintptr_t)(B) + (2 * r - 1) * 64); } /** * smix(B, r, N, V, XY): * Compute B = SMix_r(B, N). The input B must be 128r bytes in length; * the temporary storage V must be 128rN bytes in length; the temporary * storage XY must be 256r + 64 bytes in length. The value N must be a * power of 2 greater than 1. The arrays B, V, and XY must be aligned to a * multiple of 64 bytes. */ static void smix(uint8_t * B, size_t r, uint32_t N, void * V, void * XY) { size_t s = 128 * r; __m128i * X = (__m128i *) V, * Y; uint32_t * X32 = (uint32_t *) V; uint32_t i, j; size_t k; /* 1: X <-- B */ /* 3: V_i <-- X */ for (k = 0; k < 2 * r; k++) { for (i = 0; i < 16; i++) { X32[k * 16 + i] = le32dec(&B[(k * 16 + (i * 5 % 16)) * 4]); } } /* 2: for i = 0 to N - 1 do */ for (i = 1; i < N - 1; i += 2) { /* 4: X <-- H(X) */ /* 3: V_i <-- X */ Y = (__m128i *)((uintptr_t)(V) + i * s); blockmix_salsa8(X, Y, r); /* 4: X <-- H(X) */ /* 3: V_i <-- X */ X = (__m128i *)((uintptr_t)(V) + (i + 1) * s); blockmix_salsa8(Y, X, r); } /* 4: X <-- H(X) */ /* 3: V_i <-- X */ Y = (__m128i *)((uintptr_t)(V) + i * s); blockmix_salsa8(X, Y, r); /* 4: X <-- H(X) */ /* 3: V_i <-- X */ X = (__m128i *) XY; blockmix_salsa8(Y, X, r); X32 = (uint32_t *) XY; Y = (__m128i *)((uintptr_t)(XY) + s); /* 7: j <-- Integerify(X) mod N */ j = integerify(X, r) & (N - 1); /* 6: for i = 0 to N - 1 do */ for (i = 0; i < N; i += 2) { __m128i * V_j = (__m128i *)((uintptr_t)(V) + j * s); /* 8: X <-- H(X \xor V_j) */ /* 7: j <-- Integerify(X) mod N */ j = blockmix_salsa8_xor(X, V_j, Y, r) & (N - 1); V_j = (__m128i *)((uintptr_t)(V) + j * s); /* 8: X <-- H(X \xor V_j) */ /* 7: j <-- Integerify(X) mod N */ j = blockmix_salsa8_xor(Y, V_j, X, r) & (N - 1); } /* 10: B' <-- X */ for (k = 0; k < 2 * r; k++) { for (i = 0; i < 16; i++) { le32enc(&B[(k * 16 + (i * 5 % 16)) * 4], X32[k * 16 + i]); } } } /** * escrypt_kdf(local, passwd, passwdlen, salt, saltlen, * N, r, p, buf, buflen): * Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r, * p, buflen) and write the result into buf. The parameters r, p, and buflen * must satisfy r * p < 2^30 and buflen <= (2^32 - 1) * 32. The parameter N * must be a power of 2 greater than 1. * * Return 0 on success; or -1 on error. */ int escrypt_kdf_sse(escrypt_local_t * local, const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, size_t saltlen, uint64_t N, uint32_t _r, uint32_t _p, uint8_t * buf, size_t buflen) { size_t B_size, V_size, XY_size, need; uint8_t * B; uint32_t * V, * XY; size_t r = _r, p = _p; uint32_t i; /* Sanity-check parameters. */ #if SIZE_MAX > UINT32_MAX if (buflen > (((uint64_t)(1) << 32) - 1) * 32) { errno = EFBIG; return -1; } #endif if ((uint64_t)(r) * (uint64_t)(p) >= (1 << 30)) { errno = EFBIG; return -1; } if (N > UINT32_MAX) { errno = EFBIG; return -1; } if (((N & (N - 1)) != 0) || (N < 2)) { errno = EINVAL; return -1; } if (r == 0 || p == 0) { errno = EINVAL; return -1; } if ((r > SIZE_MAX / 128 / p) || #if SIZE_MAX / 256 <= UINT32_MAX (r > SIZE_MAX / 256) || #endif (N > SIZE_MAX / 128 / r)) { errno = ENOMEM; return -1; } /* Allocate memory. */ B_size = (size_t)128 * r * p; V_size = (size_t)128 * r * N; need = B_size + V_size; if (need < V_size) { errno = ENOMEM; return -1; } XY_size = (size_t)256 * r + 64; need += XY_size; if (need < XY_size) { errno = ENOMEM; return -1; } if (local->size < need) { if (free_region(local)) return -1; if (!alloc_region(local, need)) return -1; } B = (uint8_t *)local->aligned; V = (uint32_t *)((uint8_t *)B + B_size); XY = (uint32_t *)((uint8_t *)V + V_size); /* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */ PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, 1, B, B_size); /* 2: for i = 0 to p - 1 do */ for (i = 0; i < p; i++) { /* 3: B_i <-- MF(B_i, N) */ smix(&B[(size_t)128 * i * r], r, N, V, XY); } /* 5: DK <-- PBKDF2(P, B, 1, dkLen) */ PBKDF2_SHA256(passwd, passwdlen, B, B_size, 1, buf, buflen); /* Success! */ return 0; } #endif #endif ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/sysendian.h ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ #ifndef _SYSENDIAN_H_ #define _SYSENDIAN_H_ #include /* Avoid namespace collisions with BSD . */ #define be16dec scrypt_be16dec #define be16enc scrypt_be16enc #define be32dec scrypt_be32dec #define be32enc scrypt_be32enc #define be64dec scrypt_be64dec #define be64enc scrypt_be64enc #define le16dec scrypt_le16dec #define le16enc scrypt_le16enc #define le32dec scrypt_le32dec #define le32enc scrypt_le32enc #define le64dec scrypt_le64dec #define le64enc scrypt_le64enc static inline uint16_t be16dec(const void *pp) { const uint8_t *p = (uint8_t const *)pp; return ((uint16_t)(p[1]) + ((uint16_t)(p[0]) << 8)); } static inline void be16enc(void *pp, uint16_t x) { uint8_t * p = (uint8_t *)pp; p[1] = x & 0xff; p[0] = (x >> 8) & 0xff; } static inline uint32_t be32dec(const void *pp) { const uint8_t *p = (uint8_t const *)pp; return ((uint32_t)(p[3]) + ((uint32_t)(p[2]) << 8) + ((uint32_t)(p[1]) << 16) + ((uint32_t)(p[0]) << 24)); } static inline void be32enc(void *pp, uint32_t x) { uint8_t * p = (uint8_t *)pp; p[3] = x & 0xff; p[2] = (x >> 8) & 0xff; p[1] = (x >> 16) & 0xff; p[0] = (x >> 24) & 0xff; } static inline uint64_t be64dec(const void *pp) { const uint8_t *p = (uint8_t const *)pp; return ((uint64_t)(p[7]) + ((uint64_t)(p[6]) << 8) + ((uint64_t)(p[5]) << 16) + ((uint64_t)(p[4]) << 24) + ((uint64_t)(p[3]) << 32) + ((uint64_t)(p[2]) << 40) + ((uint64_t)(p[1]) << 48) + ((uint64_t)(p[0]) << 56)); } static inline void be64enc(void *pp, uint64_t x) { uint8_t * p = (uint8_t *)pp; p[7] = x & 0xff; p[6] = (x >> 8) & 0xff; p[5] = (x >> 16) & 0xff; p[4] = (x >> 24) & 0xff; p[3] = (x >> 32) & 0xff; p[2] = (x >> 40) & 0xff; p[1] = (x >> 48) & 0xff; p[0] = (x >> 56) & 0xff; } static inline uint16_t le16dec(const void *pp) { const uint8_t *p = (uint8_t const *)pp; return ((uint16_t)(p[0]) + ((uint16_t)(p[1]) << 8)); } static inline void le16enc(void *pp, uint16_t x) { uint8_t * p = (uint8_t *)pp; p[0] = x & 0xff; p[1] = (x >> 8) & 0xff; } static inline uint32_t le32dec(const void *pp) { const uint8_t *p = (uint8_t const *)pp; return ((uint32_t)(p[0]) + ((uint32_t)(p[1]) << 8) + ((uint32_t)(p[2]) << 16) + ((uint32_t)(p[3]) << 24)); } static inline void le32enc(void *pp, uint32_t x) { uint8_t * p = (uint8_t *)pp; p[0] = x & 0xff; p[1] = (x >> 8) & 0xff; p[2] = (x >> 16) & 0xff; p[3] = (x >> 24) & 0xff; } static inline uint64_t le64dec(const void *pp) { const uint8_t *p = (uint8_t const *)pp; return ((uint64_t)(p[0]) + ((uint64_t)(p[1]) << 8) + ((uint64_t)(p[2]) << 16) + ((uint64_t)(p[3]) << 24) + ((uint64_t)(p[4]) << 32) + ((uint64_t)(p[5]) << 40) + ((uint64_t)(p[6]) << 48) + ((uint64_t)(p[7]) << 56)); } static inline void le64enc(void *pp, uint64_t x) { uint8_t * p = (uint8_t *)pp; p[0] = x & 0xff; p[1] = (x >> 8) & 0xff; p[2] = (x >> 16) & 0xff; p[3] = (x >> 24) & 0xff; p[4] = (x >> 32) & 0xff; p[5] = (x >> 40) & 0xff; p[6] = (x >> 48) & 0xff; p[7] = (x >> 56) & 0xff; } #endif /* !_SYSENDIAN_H_ */ #endif ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/utils.c ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ #ifndef __STDC_WANT_LIB_EXT1__ # define __STDC_WANT_LIB_EXT1__ 1 #endif #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_MMAN_H # include #endif #include "utils.h" #ifdef _WIN32 # include # include #else # include #endif #ifdef HAVE_WEAK_SYMBOLS __attribute__((weak)) void __sodium_dummy_symbol_to_prevent_lto(void * const pnt, const size_t len) { (void) pnt; (void) len; } #endif void sodium_memzero(void * const pnt, const size_t len) { #ifdef _WIN32 SecureZeroMemory(pnt, len); #elif defined(HAVE_MEMSET_S) if (memset_s(pnt, (rsize_t) len, 0, (rsize_t) len) != 0) { abort(); } #elif defined(HAVE_EXPLICIT_BZERO) explicit_bzero(pnt, len); #elif HAVE_WEAK_SYMBOLS memset(pnt, 0, len); __sodium_dummy_symbol_to_prevent_lto(pnt, len); #else volatile unsigned char *pnt_ = (volatile unsigned char *) pnt; size_t i = (size_t) 0U; while (i < len) { pnt_[i++] = 0U; } #endif } int sodium_memcmp(const void * const b1_, const void * const b2_, size_t len) { const unsigned char *b1 = (const unsigned char *) b1_; const unsigned char *b2 = (const unsigned char *) b2_; size_t i; unsigned char d = (unsigned char) 0U; for (i = 0U; i < len; i++) { d |= b1[i] ^ b2[i]; } return (int) ((1 & ((d - 1) >> 8)) - 1); } #endif ================================================ FILE: toxencryptsave/crypto_pwhash_scryptsalsa208sha256/utils.h ================================================ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef VANILLA_NACL /* toxcore only uses this when libsodium is unavailable */ #ifndef __SODIUM_UTILS_H__ #define __SODIUM_UTILS_H__ #include #include "export.h" #ifdef __cplusplus extern "C" { #endif #if defined(__cplusplus) || !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L # define _SODIUM_C99(X) #else # define _SODIUM_C99(X) X #endif SODIUM_EXPORT void sodium_memzero(void * const pnt, const size_t len); /* WARNING: sodium_memcmp() must be used to verify if two secret keys * are equal, in constant time. * It returns 0 if the keys are equal, and -1 if they differ. * This function is not designed for lexicographical comparisons. */ SODIUM_EXPORT int sodium_memcmp(const void * const b1_, const void * const b2_, size_t len); #ifdef __cplusplus } #endif #endif #endif ================================================ FILE: toxencryptsave/defines.h ================================================ #define TOX_ENC_SAVE_MAGIC_NUMBER "toxEsave" #define TOX_ENC_SAVE_MAGIC_LENGTH 8 ================================================ FILE: toxencryptsave/toxencryptsave.c ================================================ /* toxencryptsave.c * * The Tox encrypted save functions. * * 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 "toxencryptsave.h" #include "defines.h" #include "../toxcore/crypto_core.h" #define SET_ERROR_PARAMETER(param, x) {if(param) {*param = x;}} #ifdef VANILLA_NACL #include "crypto_pwhash_scryptsalsa208sha256/crypto_pwhash_scryptsalsa208sha256.h" #include #endif #if TOX_PASS_SALT_LENGTH != crypto_pwhash_scryptsalsa208sha256_SALTBYTES #error TOX_PASS_SALT_LENGTH is assumed to be equal to crypto_pwhash_scryptsalsa208sha256_SALTBYTES #endif #if TOX_PASS_KEY_LENGTH != crypto_box_KEYBYTES #error TOX_PASS_KEY_LENGTH is assumed to be equal to crypto_box_KEYBYTES #endif #if TOX_PASS_ENCRYPTION_EXTRA_LENGTH != (crypto_box_MACBYTES + crypto_box_NONCEBYTES + crypto_pwhash_scryptsalsa208sha256_SALTBYTES + TOX_ENC_SAVE_MAGIC_LENGTH) #error TOX_PASS_ENCRYPTION_EXTRA_LENGTH is assumed to be equal to (crypto_box_MACBYTES + crypto_box_NONCEBYTES + crypto_pwhash_scryptsalsa208sha256_SALTBYTES + TOX_ENC_SAVE_MAGIC_LENGTH) #endif uint32_t toxes_version_major(void) { return TOXES_VERSION_MAJOR; } uint32_t toxes_version_minor(void) { return TOXES_VERSION_MINOR; } uint32_t toxes_version_patch(void) { return TOXES_VERSION_PATCH; } bool toxes_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch) { return (TOXES_VERSION_MAJOR == major && /* Force the major version */ (TOXES_VERSION_MINOR > minor || /* Current minor version must be newer than requested -- or -- */ (TOXES_VERSION_MINOR == minor && TOXES_VERSION_PATCH >= patch) /* the patch must be the same or newer */ ) ); } /* Clients should consider alerting their users that, unlike plain data, if even one bit * becomes corrupted, the data will be entirely unrecoverable. * Ditto if they forget their password, there is no way to recover the data. */ /* This retrieves the salt used to encrypt the given data, which can then be passed to * derive_key_with_salt to produce the same key as was previously used. Any encrpyted * data with this module can be used as input. * * returns true if magic number matches * success does not say anything about the validity of the data, only that data of * the appropriate size was copied */ bool tox_get_salt(const uint8_t *data, uint8_t *salt) { if (memcmp(data, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) != 0) return 0; data += TOX_ENC_SAVE_MAGIC_LENGTH; memcpy(salt, data, crypto_pwhash_scryptsalsa208sha256_SALTBYTES); return 1; } /* Generates a secret symmetric key from the given passphrase. out_key must be at least * TOX_PASS_KEY_LENGTH bytes long. * Be sure to not compromise the key! Only keep it in memory, do not write to disk. * The password is zeroed after key derivation. * The key should only be used with the other functions in this module, as it * includes a salt. * Note that this function is not deterministic; to derive the same key from a * password, you also must know the random salt that was used. See below. * * returns true on success */ bool tox_derive_key_from_pass(const uint8_t *passphrase, size_t pplength, TOX_PASS_KEY *out_key, TOX_ERR_KEY_DERIVATION *error) { uint8_t salt[crypto_pwhash_scryptsalsa208sha256_SALTBYTES]; randombytes(salt, sizeof salt); return tox_derive_key_with_salt(passphrase, pplength, salt, out_key, error); } /* Same as above, except with use the given salt for deterministic key derivation. * The salt must be TOX_PASS_SALT_LENGTH bytes in length. */ bool tox_derive_key_with_salt(const uint8_t *passphrase, size_t pplength, const uint8_t *salt, TOX_PASS_KEY *out_key, TOX_ERR_KEY_DERIVATION *error) { if (!salt || !out_key || (!passphrase && pplength != 0)) { SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_NULL); return 0; } uint8_t passkey[crypto_hash_sha256_BYTES]; crypto_hash_sha256(passkey, passphrase, pplength); uint8_t key[crypto_box_KEYBYTES]; /* Derive a key from the password */ /* http://doc.libsodium.org/key_derivation/README.html */ /* note that, according to the documentation, a generic pwhash interface will be created * once the pwhash competition (https://password-hashing.net/) is over */ if (crypto_pwhash_scryptsalsa208sha256( key, sizeof(key), (char *)passkey, sizeof(passkey), salt, crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE * 2, /* slightly stronger */ crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE) != 0) { /* out of memory most likely */ SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_FAILED); return 0; } sodium_memzero(passkey, crypto_hash_sha256_BYTES); /* wipe plaintext pw */ memcpy(out_key->salt, salt, crypto_pwhash_scryptsalsa208sha256_SALTBYTES); memcpy(out_key->key, key, crypto_box_KEYBYTES); SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_OK); return 1; } /* Encrypt arbitrary with a key produced by tox_derive_key_*. The output * array must be at least data_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes long. * key must be TOX_PASS_KEY_LENGTH bytes. * If you already have a symmetric key from somewhere besides this module, simply * call encrypt_data_symmetric in toxcore/crypto_core directly. * * returns true on success */ bool tox_pass_key_encrypt(const uint8_t *data, size_t data_len, const TOX_PASS_KEY *key, uint8_t *out, TOX_ERR_ENCRYPTION *error) { if (data_len == 0 || !data || !key || !out) { SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_NULL); return 0; } /* the output data consists of, in order: * salt, nonce, mac, enc_data * where the mac is automatically prepended by the encrypt() * the salt+nonce is called the prefix * I'm not sure what else I'm supposed to do with the salt and nonce, since we * need them to decrypt the data */ /* first add the magic number */ memcpy(out, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH); out += TOX_ENC_SAVE_MAGIC_LENGTH; /* then add the rest prefix */ memcpy(out, key->salt, crypto_pwhash_scryptsalsa208sha256_SALTBYTES); out += crypto_pwhash_scryptsalsa208sha256_SALTBYTES; uint8_t nonce[crypto_box_NONCEBYTES]; random_nonce(nonce); memcpy(out, nonce, crypto_box_NONCEBYTES); out += crypto_box_NONCEBYTES; /* now encrypt */ if (encrypt_data_symmetric(key->key, nonce, data, data_len, out) != data_len + crypto_box_MACBYTES) { SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_FAILED); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_OK); return 1; } /* Encrypts the given data with the given passphrase. The output array must be * at least data_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes long. This delegates * to tox_derive_key_from_pass and tox_pass_key_encrypt. * * returns true on success */ bool tox_pass_encrypt(const uint8_t *data, size_t data_len, const uint8_t *passphrase, size_t pplength, uint8_t *out, TOX_ERR_ENCRYPTION *error) { TOX_PASS_KEY key; TOX_ERR_KEY_DERIVATION _error; if (!tox_derive_key_from_pass(passphrase, pplength, &key, &_error)) { if (_error == TOX_ERR_KEY_DERIVATION_NULL) { SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_NULL); } else if (_error == TOX_ERR_KEY_DERIVATION_FAILED) { SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_KEY_DERIVATION_FAILED); } return 0; } return tox_pass_key_encrypt(data, data_len, &key, out, error); } /* This is the inverse of tox_pass_key_encrypt, also using only keys produced by * tox_derive_key_from_pass. * * the output data has size data_length - TOX_PASS_ENCRYPTION_EXTRA_LENGTH * * returns true on success */ bool tox_pass_key_decrypt(const uint8_t *data, size_t length, const TOX_PASS_KEY *key, uint8_t *out, TOX_ERR_DECRYPTION *error) { if (length <= TOX_PASS_ENCRYPTION_EXTRA_LENGTH) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_INVALID_LENGTH); return 0; } if (!data || !key || !out) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_NULL); return 0; } if (memcmp(data, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) != 0) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_BAD_FORMAT); return 0; } data += TOX_ENC_SAVE_MAGIC_LENGTH; data += crypto_pwhash_scryptsalsa208sha256_SALTBYTES; // salt only affects key derivation size_t decrypt_length = length - TOX_PASS_ENCRYPTION_EXTRA_LENGTH; uint8_t nonce[crypto_box_NONCEBYTES]; memcpy(nonce, data, crypto_box_NONCEBYTES); data += crypto_box_NONCEBYTES; /* decrypt the data */ if (decrypt_data_symmetric(key->key, nonce, data, decrypt_length + crypto_box_MACBYTES, out) != decrypt_length) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_FAILED); return 0; } SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_OK); return 1; } /* Decrypts the given data with the given passphrase. The output array must be * at least data_len - TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes long. This delegates * to tox_pass_key_decrypt. * * the output data has size data_length - TOX_PASS_ENCRYPTION_EXTRA_LENGTH * * returns true on success */ bool tox_pass_decrypt(const uint8_t *data, size_t length, const uint8_t *passphrase, size_t pplength, uint8_t *out, TOX_ERR_DECRYPTION *error) { if (length <= TOX_PASS_ENCRYPTION_EXTRA_LENGTH) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_INVALID_LENGTH); return 0; } if (!data || !passphrase || !out) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_NULL); return 0; } if (memcmp(data, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) != 0) { SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_BAD_FORMAT); return 0; } uint8_t salt[crypto_pwhash_scryptsalsa208sha256_SALTBYTES]; memcpy(salt, data + TOX_ENC_SAVE_MAGIC_LENGTH, crypto_pwhash_scryptsalsa208sha256_SALTBYTES); /* derive the key */ TOX_PASS_KEY key; if (!tox_derive_key_with_salt(passphrase, pplength, salt, &key, NULL)) { /* out of memory most likely */ SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_KEY_DERIVATION_FAILED); return 0; } return tox_pass_key_decrypt(data, length, &key, out, error); } /* Determines whether or not the given data is encrypted (by checking the magic number) */ bool tox_is_data_encrypted(const uint8_t *data) { if (memcmp(data, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) == 0) return 1; else return 0; } ================================================ FILE: toxencryptsave/toxencryptsave.h ================================================ /* toxencryptsave.h * * The Tox encrypted save functions. * * 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 TOXENCRYPTSAVE_H #define TOXENCRYPTSAVE_H #ifdef __cplusplus extern "C" { #endif #include #include #include #ifndef TOX_DEFINED #define TOX_DEFINED typedef struct Tox Tox; struct Tox_Options; #endif #define TOX_PASS_SALT_LENGTH 32 #define TOX_PASS_KEY_LENGTH 32 #define TOX_PASS_ENCRYPTION_EXTRA_LENGTH 80 /** * ToxEncryptSave. */ #ifndef TOXES_DEFINED #define TOXES_DEFINED #endif /* TOXES_DEFINED */ /******************************************************************************* * * :: API version * ******************************************************************************/ /** * The major version number. Incremented when the API or ABI changes in an * incompatible way. */ #define TOXES_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 TOXES_VERSION_MINOR 0u /** * The patch or revision number. Incremented when bugfixes are applied without * changing any functionality or API or ABI. */ #define TOXES_VERSION_PATCH 0u /** * A macro to check at preprocessing time whether the client code is compatible * with the installed version of ToxAV. */ #define TOXES_VERSION_IS_API_COMPATIBLE(MAJOR, MINOR, PATCH) \ (TOXES_VERSION_MAJOR == MAJOR && \ (TOXES_VERSION_MINOR > MINOR || \ (TOXES_VERSION_MINOR == MINOR && \ TOXES_VERSION_PATCH >= PATCH))) /** * A macro to make compilation fail if the client code is not compatible with * the installed version of ToxAV. */ #define TOXES_VERSION_REQUIRE(MAJOR, MINOR, PATCH) \ typedef char toxes_required_version[TOXES_IS_COMPATIBLE(MAJOR, MINOR, PATCH) ? 1 : -1] /** * A convenience macro to call toxES_version_is_compatible with the currently * compiling API version. */ #define TOXES_VERSION_IS_ABI_COMPATIBLE() \ toxes_version_is_compatible(TOXES_VERSION_MAJOR, TOXES_VERSION_MINOR, TOXES_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 toxes_version_major(void); /** * Return the minor version number of the library. */ uint32_t toxes_version_minor(void); /** * Return the patch number of the library. */ uint32_t toxes_version_patch(void); /** * Return whether the compiled library version is compatible with the passed * version numbers. */ bool toxes_version_is_compatible(uint32_t major, uint32_t minor, uint32_t patch); /* This module is conceptually organized into two parts. The first part are the functions * with "key" in the name. To use these functions, first derive an encryption key * from a password with tox_derive_key_from_pass, and use the returned key to * encrypt the data. The second part takes the password itself instead of the key, * and then delegates to the first part to derive the key before de/encryption, * which can simplify client code; however, key derivation is very expensive * compared to the actual encryption, so clients that do a lot of encryption should * favor using the first part intead of the second part. * * The encrypted data is prepended with a magic number, to aid validity checking * (no guarantees are made of course). Any data to be decrypted must start with * the magic number. * * Clients should consider alerting their users that, unlike plain data, if even one bit * becomes corrupted, the data will be entirely unrecoverable. * Ditto if they forget their password, there is no way to recover the data. */ /* Since apparently no one actually bothered to learn about the module previously, * the recently removed functions tox_encrypted_new and tox_get_encrypted_savedata * may be trivially replaced by calls to tox_pass_decrypt -> tox_new or * tox_get_savedata -> tox_pass_encrypt as appropriate. The removed functions * were never more than 5 line wrappers of the other public API functions anyways. * (As has always been, tox_pass_decrypt and tox_pass_encrypt are interchangeable * with tox_pass_key_decrypt and tox_pass_key_encrypt, as the client program requires.) */ typedef enum TOX_ERR_KEY_DERIVATION { TOX_ERR_KEY_DERIVATION_OK, /** * Some input data, or maybe the output pointer, was null. */ TOX_ERR_KEY_DERIVATION_NULL, /** * The crypto lib was unable to derive a key from the given passphrase, * which is usually a lack of memory issue. The functions accepting keys * do not produce this error. */ TOX_ERR_KEY_DERIVATION_FAILED } TOX_ERR_KEY_DERIVATION; typedef enum TOX_ERR_ENCRYPTION { TOX_ERR_ENCRYPTION_OK, /** * Some input data, or maybe the output pointer, was null. */ TOX_ERR_ENCRYPTION_NULL, /** * The crypto lib was unable to derive a key from the given passphrase, * which is usually a lack of memory issue. The functions accepting keys * do not produce this error. */ TOX_ERR_ENCRYPTION_KEY_DERIVATION_FAILED, /** * The encryption itself failed. */ TOX_ERR_ENCRYPTION_FAILED } TOX_ERR_ENCRYPTION; typedef enum TOX_ERR_DECRYPTION { TOX_ERR_DECRYPTION_OK, /** * Some input data, or maybe the output pointer, was null. */ TOX_ERR_DECRYPTION_NULL, /** * The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes */ TOX_ERR_DECRYPTION_INVALID_LENGTH, /** * The input data is missing the magic number (i.e. wasn't created by this * module, or is corrupted) */ TOX_ERR_DECRYPTION_BAD_FORMAT, /** * The crypto lib was unable to derive a key from the given passphrase, * which is usually a lack of memory issue. The functions accepting keys * do not produce this error. */ TOX_ERR_DECRYPTION_KEY_DERIVATION_FAILED, /** * The encrypted byte array could not be decrypted. Either the data was * corrupt or the password/key was incorrect. */ TOX_ERR_DECRYPTION_FAILED } TOX_ERR_DECRYPTION; /******************************* BEGIN PART 2 ******************************* * For simplicty, the second part of the module is presented first. The API for * the first part is analgous, with some extra functions for key handling. If * your code spends too much time using these functions, consider using the part * 1 functions instead. */ /* Encrypts the given data with the given passphrase. The output array must be * at least data_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes long. This delegates * to tox_derive_key_from_pass and tox_pass_key_encrypt. * * returns true on success */ bool tox_pass_encrypt(const uint8_t *data, size_t data_len, const uint8_t *passphrase, size_t pplength, uint8_t *out, TOX_ERR_ENCRYPTION *error); /* Decrypts the given data with the given passphrase. The output array must be * at least data_len - TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes long. This delegates * to tox_pass_key_decrypt. * * the output data has size data_length - TOX_PASS_ENCRYPTION_EXTRA_LENGTH * * returns true on success */ bool tox_pass_decrypt(const uint8_t *data, size_t length, const uint8_t *passphrase, size_t pplength, uint8_t *out, TOX_ERR_DECRYPTION *error); /******************************* BEGIN PART 1 ******************************* * And now part "1", which does the actual encryption, and is rather less cpu * intensive than part one. The first 3 functions are for key handling. */ /* This key structure's internals should not be used by any client program, even * if they are straightforward here. */ typedef struct { uint8_t salt[TOX_PASS_SALT_LENGTH]; uint8_t key[TOX_PASS_KEY_LENGTH]; } TOX_PASS_KEY; /* Generates a secret symmetric key from the given passphrase. out_key must be at least * TOX_PASS_KEY_LENGTH bytes long. * Be sure to not compromise the key! Only keep it in memory, do not write to disk. * The password is zeroed after key derivation. * The key should only be used with the other functions in this module, as it * includes a salt. * Note that this function is not deterministic; to derive the same key from a * password, you also must know the random salt that was used. See below. * * returns true on success */ bool tox_derive_key_from_pass(const uint8_t *passphrase, size_t pplength, TOX_PASS_KEY *out_key, TOX_ERR_KEY_DERIVATION *error); /* Same as above, except use the given salt for deterministic key derivation. * The salt must be TOX_PASS_SALT_LENGTH bytes in length. */ bool tox_derive_key_with_salt(const uint8_t *passphrase, size_t pplength, const uint8_t *salt, TOX_PASS_KEY *out_key, TOX_ERR_KEY_DERIVATION *error); /* This retrieves the salt used to encrypt the given data, which can then be passed to * derive_key_with_salt to produce the same key as was previously used. Any encrpyted * data with this module can be used as input. * * returns true if magic number matches * success does not say anything about the validity of the data, only that data of * the appropriate size was copied */ bool tox_get_salt(const uint8_t *data, uint8_t *salt); /* Now come the functions that are analogous to the part 2 functions. */ /* Encrypt arbitrary with a key produced by tox_derive_key_*. The output * array must be at least data_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes long. * key must be TOX_PASS_KEY_LENGTH bytes. * If you already have a symmetric key from somewhere besides this module, simply * call encrypt_data_symmetric in toxcore/crypto_core directly. * * returns true on success */ bool tox_pass_key_encrypt(const uint8_t *data, size_t data_len, const TOX_PASS_KEY *key, uint8_t *out, TOX_ERR_ENCRYPTION *error); /* This is the inverse of tox_pass_key_encrypt, also using only keys produced by * tox_derive_key_from_pass. * * the output data has size data_length - TOX_PASS_ENCRYPTION_EXTRA_LENGTH * * returns true on success */ bool tox_pass_key_decrypt(const uint8_t *data, size_t length, const TOX_PASS_KEY *key, uint8_t *out, TOX_ERR_DECRYPTION *error); /* Determines whether or not the given data is encrypted (by checking the magic number) */ bool tox_is_data_encrypted(const uint8_t *data); #ifdef __cplusplus } #endif #endif