diff options
author | Kirill Volinsky <mataes2007@gmail.com> | 2014-08-09 18:03:54 +0000 |
---|---|---|
committer | Kirill Volinsky <mataes2007@gmail.com> | 2014-08-09 18:03:54 +0000 |
commit | fc6d64207886ac6921d8f13587c158bfaae76839 (patch) | |
tree | 3a8e243ae439ed9a7d1f16b16e159e452e058509 | |
parent | ab807bf3e87863ef97412faf83dc2cac697d6a23 (diff) |
toxcore sources added
git-svn-id: http://svn.miranda-ng.org/main/trunk@10139 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c
127 files changed, 39931 insertions, 0 deletions
diff --git a/protocols/Tox/toxcore/.gitignore b/protocols/Tox/toxcore/.gitignore new file mode 100644 index 0000000000..757143884a --- /dev/null +++ b/protocols/Tox/toxcore/.gitignore @@ -0,0 +1,70 @@ +# 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-* diff --git a/protocols/Tox/toxcore/.travis.yml b/protocols/Tox/toxcore/.travis.yml new file mode 100644 index 0000000000..ca14c88457 --- /dev/null +++ b/protocols/Tox/toxcore/.travis.yml @@ -0,0 +1,60 @@ +language: c +compiler: + - gcc + - clang + +before_script: + #installing libsodium, needed for Core + - git clone git://github.com/jedisct1/libsodium.git > /dev/null + - cd libsodium + - git checkout tags/0.4.2 > /dev/null + - ./autogen.sh > /dev/null + - ./configure > /dev/null + - make check -j3 > /dev/null + - sudo make install >/dev/null + - cd .. + #installing yasm, needed for compiling vpx + - sudo apt-get install yasm > /dev/null + #installing libconfig, needed for DHT_bootstrap_daemon + - wget http://www.hyperrealm.com/libconfig/libconfig-1.4.9.tar.gz > /dev/null + - tar -xvzf libconfig-1.4.9.tar.gz > /dev/null + - cd libconfig-1.4.9 + - ./configure > /dev/null + - make -j3 > /dev/null + - sudo make install > /dev/null + - cd .. + #installing libopus, needed for audio encoding/decoding + - wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz > /dev/null + - tar xzf opus-1.0.3.tar.gz > /dev/null + - cd opus-1.0.3 + - ./configure > /dev/null + - make -j3 > /dev/null + - sudo make install > /dev/null + - cd .. + #installing vpx + - git clone http://git.chromium.org/webm/libvpx.git > /dev/null + - cd libvpx + - ./configure --enable-shared > /dev/null + - make -j3 >/dev/null + - sudo make install > /dev/null + - cd .. + #creating libraries links and updating cache + - sudo ldconfig > /dev/null + #installing check, needed for unit tests + - sudo apt-get install check > /dev/null + +script: + - autoreconf -i + - CFLAGS="-Ofast -Wall -Wextra" ./configure --enable-daemon --enable-ntox + - make -j3 + - make check + - make dist + +notifications: + email: false + + irc: + channels: + - "chat.freenode.net#tox-dev" + on_success: always + on_failure: always diff --git a/protocols/Tox/toxcore/AUTHORS b/protocols/Tox/toxcore/AUTHORS new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/protocols/Tox/toxcore/AUTHORS diff --git a/protocols/Tox/toxcore/COPYING b/protocols/Tox/toxcore/COPYING new file mode 100644 index 0000000000..f9e860fea8 --- /dev/null +++ b/protocols/Tox/toxcore/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + 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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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 <http://www.gnu.org/licenses/>. + +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: + + <program> Copyright (C) <year> <name of author> + 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 +<http://www.gnu.org/licenses/>. + + 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 +<http://www.gnu.org/philosophy/why-not-lgpl.html>.
\ No newline at end of file diff --git a/protocols/Tox/toxcore/ChangeLog b/protocols/Tox/toxcore/ChangeLog new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/protocols/Tox/toxcore/ChangeLog diff --git a/protocols/Tox/toxcore/INSTALL b/protocols/Tox/toxcore/INSTALL new file mode 100644 index 0000000000..007e9396d0 --- /dev/null +++ b/protocols/Tox/toxcore/INSTALL @@ -0,0 +1,370 @@ +Installation Instructions +************************* + +Copyright (C) 1994-1996, 1999-2002, 2004-2013 Free Software Foundation, +Inc. + + Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. + +Basic Installation +================== + + Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. Some packages provide this +`INSTALL' file but do not implement all of the features documented +below. The lack of an optional feature in a given package is not +necessarily a bug. More recommendations for GNU packages can be found +in *note Makefile Conventions: (standards)Makefile Conventions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + + The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package, generally using the just-built uninstalled binaries. + + 4. Type `make install' to install the programs and any data files and + documentation. When installing into a prefix owned by root, it is + recommended that the package be configured and built as a regular + user, and only the `make install' phase executed with root + privileges. + + 5. Optionally, type `make installcheck' to repeat any self-tests, but + this time using the binaries in their final installed location. + This target does not install anything. Running this target as a + regular user, particularly if the prior `make install' required + root privileges, verifies that the installation completed + correctly. + + 6. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 7. Often, you can also type `make uninstall' to remove the installed + files again. In practice, not all packages have tested that + uninstallation works correctly, even though it is required by the + GNU Coding Standards. + + 8. Some packages, particularly those that use Automake, provide `make + distcheck', which can by used by developers to test that all other + targets like `make install' and `make uninstall' work correctly. + This target is generally not run by end users. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. This +is known as a "VPATH" build. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple `-arch' options to the +compiler but only a single `-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the `lipo' tool if you have problems. + +Installation Names +================== + + By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX', where PREFIX must be an +absolute file name. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. In general, the +default for these options is expressed in terms of `${prefix}', so that +specifying just `--prefix' will affect all of the other directory +specifications that were not explicitly provided. + + The most portable way to affect installation locations is to pass the +correct locations to `configure'; however, many packages provide one or +both of the following shortcuts of passing variable assignments to the +`make install' command line to change installation locations without +having to reconfigure or recompile. + + The first method involves providing an override variable for each +affected directory. For example, `make install +prefix=/alternate/directory' will choose an alternate location for all +directory configuration variables that were expressed in terms of +`${prefix}'. Any directories that were specified during `configure', +but not in terms of `${prefix}', must each be overridden at install +time for the entire installation to be relocated. The approach of +makefile variable overrides for each directory variable is required by +the GNU Coding Standards, and ideally causes no recompilation. +However, some platforms have known limitations with the semantics of +shared libraries that end up requiring recompilation when using this +method, particularly noticeable in packages that use GNU Libtool. + + The second method involves providing the `DESTDIR' variable. For +example, `make install DESTDIR=/alternate/directory' will prepend +`/alternate/directory' before all installation names. The approach of +`DESTDIR' overrides is not required by the GNU Coding Standards, and +does not work on platforms that have drive letters. On the other hand, +it does better at avoiding recompilation issues, and works well even +when some directory options were not specified in terms of `${prefix}' +at `configure' time. + +Optional Features +================= + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + + Some packages offer the ability to configure how verbose the +execution of `make' will be. For these packages, running `./configure +--enable-silent-rules' sets the default to minimal output, which can be +overridden with `make V=1'; while running `./configure +--disable-silent-rules' sets the default to verbose, which can be +overridden with `make V=0'. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU +CC is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + HP-UX `make' updates targets which have the same time stamps as +their prerequisites, which makes it generally unusable when shipped +generated files such as `configure' are involved. Use GNU `make' +instead. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its `<wchar.h>' header file. The option `-nodtk' can be used as +a workaround. If GNU CC is not installed, it is therefore recommended +to try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put `/usr/ucb' early in your `PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in `/usr/bin'. So, if you need `/usr/ucb' +in your `PATH', put it _after_ `/usr/bin'. + + On Haiku, software installed for all users goes in `/boot/common', +not `/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf limitation. Until the limitation is lifted, you can use +this workaround: + + CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of all of the options to `configure', and exit. + +`--help=short' +`--help=recursive' + Print a summary of the options unique to this package's + `configure', and exit. The `short' variant lists options used + only in the top level, while the `recursive' variant lists options + also present in any nested packages. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--prefix=DIR' + Use DIR as the installation prefix. *note Installation Names:: + for more details, including other options available for fine-tuning + the installation locations. + +`--no-create' +`-n' + Run the configure checks, but stop before creating any output + files. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. diff --git a/protocols/Tox/toxcore/INSTALL.md b/protocols/Tox/toxcore/INSTALL.md new file mode 100644 index 0000000000..41aa5afd1b --- /dev/null +++ b/protocols/Tox/toxcore/INSTALL.md @@ -0,0 +1,522 @@ +#Install Instructions + +- [Installation](#installation) + - [Unix like](#unix) + - [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) + +<a name="installation" /> +##Installation + +<a name="unix" /> +###Most Unix like OSes: + +Build dependencies: + +Note: package fetching commands may vary by OS. + +On Ubuntu: + +```bash +sudo apt-get install build-essential libtool autotools-dev automake checkinstall check git yasm +``` + +On Fedora: + +```bash +yum groupinstall "Development Tools" +yum install libtool autoconf automake check check-devel +``` + +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. + +You should get and install [libsodium](https://github.com/jedisct1/libsodium): +```bash +git clone git://github.com/jedisct1/libsodium.git +cd libsodium +git checkout tags/0.5.0 +./autogen.sh +./configure && make check +sudo checkinstall --install --pkgname libsodium --pkgversion 0.5.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 git://github.com/jedisct1/libsodium.git +cd libsodium +git checkout tags/0.5.0 +./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: +``` +echo '/usr/local/lib/' | sudo tee -a /etc/ld.so.conf.d/locallib.conf +sudo ldconfig +``` + +Then clone this repo and generate makefile: +```bash +git clone git://github.com/irungentoo/ProjectTox-Core.git +cd ProjectTox-Core +autoreconf -i +./configure +make +sudo make install +``` + + +<a name="osx" /> +###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. + +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. +<a name="homebrew" /> +####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 git://github.com/irungentoo/ProjectTox-Core.git +cd ProjectTox-Core +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/0.4.5/include/ --with-libsodium-libs=/usr/local/Cellar/libsodium/0.4.5/lib/ +make +make install +``` + + +<a name="non-homebrew" /> +####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 + * https://code.google.com/p/webm/downloads/list + * http://www.opus-codec.org/downloads/ + * http://www.freedesktop.org/wiki/Software/pkg-config/ + +You must install yasm before installing libvpx, otherwise libvpx will fail to make correctly. + +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 ProjectTox-Core +autoreconf -i +./configure +make +make install +``` + +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 +``` + +<a name="windows" /> +###Windows: + +<a name="windows-cross-compile" /> + +####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. + +<a name="windows-cross-compile-vm" /> +#####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. + +<a name="windows-cross-compile-environment" /> +#####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 +``` + +<a name="windows-cross-compile-compiling" /> +#####Compiling + +Now we will build libraries needed for audio/video: VPX and Opus. + +VPX: +```bash +git clone http://git.chromium.org/webm/libvpx.git +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/0.4.5 +./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-gcc -Wl,--export-all-symbols -Wl,--out-implib=libtox.dll.a -shared -o libtox.dll *.o ../lib/*.a /usr/$WINDOWS_TOOLCHAIN/lib/libwinpthread.a -lws2_32 -static-libgcc +``` + +And we will copy it over to the `toxbuild` directory: +```bash +mkdir -p /media/sf_toxbuild/release/lib +mv libtox.dll* /media/sf_toxbuild/release/lib +mkdir -p /media/sf_toxbuild/release/include +mv ../include/tox /media/sf_toxbuild/release/include +``` + +That's it. Now you should have `release/lib/libtox.dll` and `release/include/tox/<headers>` in your `toxbuild` directory on the Windows system. + +<a name="windows-native" /> +####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-0.5.0.tar.gz release: +```cmd +tar -zxvf libsodium-0.5.0.tar.gz +cd libsodium-0.5.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 ProjectTox-Core library, should either clone this repo by using git, or just download a [zip of current Master branch](https://github.com/irungentoo/ProjectTox-Core/archive/master.zip) and extract it somewhere. + +Assuming that you now have the sources in the ProjectTox-Core directory: + +```cmd +cd ProjectTox-Core +autoreconf -i +./configure +make +make install +``` + +<a name="Clients" /> +####Clients: +While [Toxic](https://github.com/tox/toxic) is no longer in core, a list of Tox clients are located in our [wiki](http://wiki.tox.im/client) + + + + + +<a name="additional" /> +##Additional + + + +<a name="aconf" /> +###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 name="av" /> +###A/V support: + +<a name="libtoxav" /> +####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 libopus-devel libvpx-devel +``` + +Install on ubuntu: +```bash +sudo apt-get install libopus-dev libvpx-dev +``` +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 http://git.chromium.org/webm/libvpx.git +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 .. +``` + +<a name="bootstrapd" /> +###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. + + +<a name="ntox" /> +###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 +``` diff --git a/protocols/Tox/toxcore/Makefile.am b/protocols/Tox/toxcore/Makefile.am new file mode 100644 index 0000000000..6b28280e5f --- /dev/null +++ b/protocols/Tox/toxcore/Makefile.am @@ -0,0 +1,35 @@ +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 = \ + libtoxcore.pc.in \ + 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)/tools/README \ + $(top_srcdir)/tools/astylerc \ + $(top_srcdir)/tools/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 diff --git a/protocols/Tox/toxcore/NEWS b/protocols/Tox/toxcore/NEWS new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/protocols/Tox/toxcore/NEWS diff --git a/protocols/Tox/toxcore/README b/protocols/Tox/toxcore/README new file mode 100644 index 0000000000..42061c01a1 --- /dev/null +++ b/protocols/Tox/toxcore/README @@ -0,0 +1 @@ +README.md
\ No newline at end of file diff --git a/protocols/Tox/toxcore/README.md b/protocols/Tox/toxcore/README.md new file mode 100644 index 0000000000..d26ea704f4 --- /dev/null +++ b/protocols/Tox/toxcore/README.md @@ -0,0 +1,43 @@ +![Project Tox](https://raw.github.com/irungentoo/toxcore/master/other/tox.png "Project Tox") +*** + +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.<br /> <br /> + +[**Website**](https://tox.im) **|** [**Download**](https://github.com/irungentoo/toxcore/wiki/Downloads) **|** [**Wiki**](https://wiki.tox.im/) **|** [**Blog**](https://blog.libtoxcore.so/) **|** [**FAQ**](http://wiki.tox.im/FAQ) **|** [**Binaries**](https://wiki.tox.im/Binaries) **|** [**Clients**](https://wiki.tox.im/Client) **|** [**Compiling**](/INSTALL.md) **|** +[**API**](https://libtoxcore.so/) **|** +**IRC:** #tox@freenode + + +## 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][String] (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 of 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 without any account configuration will be capable of adding people to his or her's friends list and start conversing with them. There are many so-called Skype replacements and all of them are either hard to configure for the normal user or suffer from being way too centralized. + +## TODO: +- [TODO](/docs/TODO) + + +## Documentation: + +- [Compiling](/INSTALL.md) +- [DHT Protocol](/docs/updates/DHT.md)<br /> +- [Crypto](/docs/updates/Crypto.md)<br /> +- [Ideas](https://wiki.tox.im/index.php/Ideas) + +[String]: https://en.wikipedia.org/wiki/String_(computer_science) diff --git a/protocols/Tox/toxcore/auto_tests/Makefile.inc b/protocols/Tox/toxcore/auto_tests/Makefile.inc new file mode 100644 index 0000000000..ecf3f31f7a --- /dev/null +++ b/protocols/Tox/toxcore/auto_tests/Makefile.inc @@ -0,0 +1,95 @@ +if BUILD_TESTS + +TESTS = messenger_autotest crypto_test network_test assoc_test onion_test TCP_test tox_test +check_PROGRAMS = messenger_autotest crypto_test network_test assoc_test onion_test TCP_test tox_test + +AUTOTEST_CFLAGS = \ + $(LIBSODIUM_CFLAGS) \ + $(NACL_CFLAGS) \ + $(CHECK_CFLAGS) + +AUTOTEST_LDADD = \ + $(LIBSODIUM_LDFLAGS) \ + $(NACL_LDFLAGS) \ + libtoxcore.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) + + + +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 + +EXTRA_DIST += $(top_srcdir)/auto_tests/friends_test.c
\ No newline at end of file diff --git a/protocols/Tox/toxcore/auto_tests/TCP_test.c b/protocols/Tox/toxcore/auto_tests/TCP_test.c new file mode 100644 index 0000000000..8e75fae0c7 --- /dev/null +++ b/protocols/Tox/toxcore/auto_tests/TCP_test.c @@ -0,0 +1,525 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> +#include <stdint.h> +#include <string.h> +#include <check.h> +#include <stdlib.h> +#include <time.h> + +#include "../toxcore/TCP_server.h" +#include "../toxcore/TCP_client.h" + +#include "../toxcore/util.h" + +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) +#define c_sleep(x) Sleep(1*x) +#else +#include <unistd.h> +#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_public_key, 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(memcmp(packet_resp_plain + 2, f_public_key, crypto_box_PUBLICKEYBYTES) == 0, "key in packet wrong"); +} +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; +} + +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_public_key, 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(memcmp(data + 2, con3->public_key, crypto_box_PUBLICKEYBYTES) == 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(memcmp(data + 2, con1->public_key, crypto_box_PUBLICKEYBYTES) == 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"); +} +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 (memcmp(public_key, oob_pubkey, crypto_box_PUBLICKEYBYTES) != 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_public_key, 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); + 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); + TCP_Client_Connection *conn2 = new_TCP_connection(ip_port_tcp_s, self_public_key, f2_public_key, f2_secret_key); + 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(memcmp(response_callback_public_key, f2_public_key, crypto_box_PUBLICKEYBYTES) == 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"); +} +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); + 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); +} +END_TEST + +#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); +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); + 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; +} diff --git a/protocols/Tox/toxcore/auto_tests/assoc_test.c b/protocols/Tox/toxcore/auto_tests/assoc_test.c new file mode 100644 index 0000000000..5f496ececb --- /dev/null +++ b/protocols/Tox/toxcore/auto_tests/assoc_test.c @@ -0,0 +1,168 @@ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define AUTO_TEST +#include "../toxcore/DHT.h" +#include "../toxcore/assoc.h" +#include "../toxcore/util.h" + +#include <sys/types.h> +#include <stdint.h> +#include <string.h> + +#include <check.h> + +START_TEST(test_basics) +{ + /* TODO: real test */ + uint8_t id[CLIENT_ID_SIZE]; + 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); +} +END_TEST + +START_TEST(test_fillup) +{ + /* TODO: real test */ + int i, j; + uint8_t id[CLIENT_ID_SIZE]; + //uint32_t a = current_time(); + uint32_t a = 2710106197; + srand(a); + + for (i = 0; i < CLIENT_ID_SIZE; ++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[CLIENT_ID_SIZE]; + 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 < CLIENT_ID_SIZE; ++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, CLIENT_ID_SIZE - 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]->client_id, 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)); +} +END_TEST + +#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); + +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; +} diff --git a/protocols/Tox/toxcore/auto_tests/crypto_test.c b/protocols/Tox/toxcore/auto_tests/crypto_test.c new file mode 100644 index 0000000000..252f727591 --- /dev/null +++ b/protocols/Tox/toxcore/auto_tests/crypto_test.c @@ -0,0 +1,310 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "../toxcore/net_crypto.h" +#include <sys/types.h> +#include <stdint.h> +#include <string.h> +#include <check.h> +#include <stdlib.h> +#include <time.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 + + +#define DEFTESTCASE(NAME) \ + TCase *NAME = tcase_create(#NAME); \ + tcase_add_test(NAME, test_##NAME); \ + suite_add_tcase(s, NAME); + +#define DEFTESTCASE_SLOW(NAME, TIMEOUT) \ + DEFTESTCASE(NAME) \ + tcase_set_timeout(NAME, TIMEOUT); + +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); + + 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; +} diff --git a/protocols/Tox/toxcore/auto_tests/friends_test.c b/protocols/Tox/toxcore/auto_tests/friends_test.c new file mode 100644 index 0000000000..2448f97c34 --- /dev/null +++ b/protocols/Tox/toxcore/auto_tests/friends_test.c @@ -0,0 +1,238 @@ +/* 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 <assert.h> +#include <unistd.h> +#include <sys/mman.h> +#include <signal.h> +#include <sys/wait.h> + +#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; +} diff --git a/protocols/Tox/toxcore/auto_tests/messenger_test.c b/protocols/Tox/toxcore/auto_tests/messenger_test.c new file mode 100644 index 0000000000..7ab7d67452 --- /dev/null +++ b/protocols/Tox/toxcore/auto_tests/messenger_test.c @@ -0,0 +1,366 @@ +/* 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 <sys/types.h> +#include <stdint.h> +#include <string.h> +#include <check.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_sendmessage(m, -1, (uint8_t *)message, good_len) == 0); + ck_assert(m_sendmessage(m, REALLY_BIG_NUMBER, (uint8_t *)message, good_len) == 0); + ck_assert(m_sendmessage(m, 17, (uint8_t *)message, good_len) == 0); + ck_assert(m_sendmessage(m, friend_id_num, (uint8_t *)message, bad_len) == 0); +} +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!\n" + "(this can be caused by the error of m_addfriend_norequest" + " in the beginning of the suite)\n"); +} +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 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 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 + +#define DEFTESTCASE(NAME) \ + TCase *tc_##NAME = tcase_create(#NAME); \ + tcase_add_test(tc_##NAME, test_##NAME); \ + suite_add_tcase(s, tc_##NAME); + +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 */ + m = new_messenger(TOX_ENABLE_IPV6_DEFAULT); + + /* 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; +} diff --git a/protocols/Tox/toxcore/auto_tests/network_test.c b/protocols/Tox/toxcore/auto_tests/network_test.c new file mode 100644 index 0000000000..afd90e7a06 --- /dev/null +++ b/protocols/Tox/toxcore/auto_tests/network_test.c @@ -0,0 +1,174 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> +#include <stdint.h> +#include <string.h> +#include <check.h> +#include <stdlib.h> +#include <time.h> + +#include "../toxcore/network.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); + + 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); + res = addr_resolve(localhost, &ip, NULL); + + if (res < 1) { + 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, got %u.", 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); + 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, got %u.", 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, got %u.", 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 + +START_TEST(test_struct_sizes) +{ + ck_assert_msg(sizeof(IP4) == 4, "sizeof(IP4): expected result 4, got %u.", sizeof(IP4)); + ck_assert_msg(sizeof(IP6) == 16, "sizeof(IP6): expected result 16, got %u.", sizeof(IP6)); + ck_assert_msg(sizeof(IP) == 17, "sizeof(IP): expected result 17, got %u.", sizeof(IP)); + ck_assert_msg(sizeof(IP_Port) == 19, "sizeof(IP_Port): expected result 19, got %u.", sizeof(IP_Port)); +} +END_TEST + +#define DEFTESTCASE(NAME) \ + TCase *tc_##NAME = tcase_create(#NAME); \ + tcase_add_test(tc_##NAME, test_##NAME); \ + suite_add_tcase(s, tc_##NAME); + +Suite *network_suite(void) +{ + Suite *s = suite_create("Network"); + + DEFTESTCASE(addr_resolv_localhost); + DEFTESTCASE(ip_equal); + DEFTESTCASE(struct_sizes); + + return s; +} + +int main(int argc, char *argv[]) +{ + 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; +} diff --git a/protocols/Tox/toxcore/auto_tests/onion_test.c b/protocols/Tox/toxcore/auto_tests/onion_test.c new file mode 100644 index 0000000000..2394e03daf --- /dev/null +++ b/protocols/Tox/toxcore/auto_tests/onion_test.c @@ -0,0 +1,369 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> +#include <stdint.h> +#include <string.h> +#include <check.h> +#include <stdlib.h> +#include <time.h> + +#include "../toxcore/onion.h" +#include "../toxcore/onion_announce.h" +#include "../toxcore/onion_client.h" +#include "../toxcore/util.h" + +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) +#define c_sleep(x) Sleep(1*x) +#else +#include <unistd.h> +#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, uint32_t length) +{ + Onion *onion = object; + + if (memcmp(packet, "Install Gentoo", sizeof("Install Gentoo")) != 0) + return 1; + + if (send_onion_response(onion->net, source, (uint8_t *)"install gentoo", sizeof("install gentoo"), + packet + sizeof("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, uint32_t length) +{ + if (length != sizeof("install Gentoo")) + return 1; + + if (memcmp(packet, (uint8_t *)"install gentoo", sizeof("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, uint32_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, uint32_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, 'I', &handle_test_1, onion2); + + IP_Port on1 = {ip, onion1->net->port}; + Node_format n1; + memcpy(n1.client_id, onion1->dht->self_public_key, crypto_box_PUBLICKEYBYTES); + n1.ip_port = on1; + + IP_Port on2 = {ip, onion2->net->port}; + Node_format n2; + memcpy(n2.client_id, 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 *)"Install Gentoo", + sizeof("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, 'i', &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].client_id, 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); + } +} +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); + on->onion_c = new_onion_client(new_net_crypto(dht)); + + 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); +} + +#define NUM_ONIONS 50 + +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); + } + + for (i = 0; i < 25 * 2; ++i) { + for (j = 0; j < NUM_ONIONS; ++j) { + do_onions(onions[j]); + } + + c_sleep(50); + } + + onion_addfriend(onions[7]->onion_c, onions[37]->onion_c->c->self_public_key); + int frnum = onion_addfriend(onions[37]->onion_c, onions[7]->onion_c->c->self_public_key); + + int ok = -1; + + IP_Port ip_port; + + while (ok == -1) { + for (i = 0; i < NUM_ONIONS; ++i) { + networking_poll(onions[i]->onion->net); + do_onion_client(onions[i]->onion_c); + } + + ok = onion_getfriendip(onions[37]->onion_c, frnum, &ip_port); + + c_sleep(50); + } + + printf("id discovered\n"); + + while (ok != 1) { + for (i = 0; i < NUM_ONIONS; ++i) { + do_onions(onions[i]); + } + + ok = onion_getfriendip(onions[37]->onion_c, frnum, &ip_port); + + c_sleep(50); + } + + ck_assert_msg(ip_port.port == onions[7]->onion->net->port, "Port in returned ip not correct."); +} +END_TEST + +#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); +Suite *onion_suite(void) +{ + Suite *s = suite_create("Onion"); + + DEFTESTCASE_SLOW(basic, 5); + DEFTESTCASE_SLOW(announce, 200); + 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; +} diff --git a/protocols/Tox/toxcore/auto_tests/skeleton_test.c b/protocols/Tox/toxcore/auto_tests/skeleton_test.c new file mode 100644 index 0000000000..89ef1b8bd6 --- /dev/null +++ b/protocols/Tox/toxcore/auto_tests/skeleton_test.c @@ -0,0 +1,53 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> +#include <stdint.h> +#include <string.h> +#include <check.h> +#include <stdlib.h> +#include <time.h> + +/* +#include "../<stuff to test>" +*/ + +START_TEST(test_creativetestnamegoeshere) +{ + uint8_t test = 0; + ck_assert_msg(test == 0, "test: expected result 0, got %u.", test); +} +END_TEST + + +#define DEFTESTCASE(NAME) \ + TCase *tc_##NAME = tcase_create(#NAME); \ + tcase_add_test(tc_##NAME, test_##NAME); \ + suite_add_tcase(s, tc_##NAME); + +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; +} + diff --git a/protocols/Tox/toxcore/auto_tests/tox_test.c b/protocols/Tox/toxcore/auto_tests/tox_test.c new file mode 100644 index 0000000000..43fb7a1cd2 --- /dev/null +++ b/protocols/Tox/toxcore/auto_tests/tox_test.c @@ -0,0 +1,395 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#include <check.h> +#include <stdlib.h> +#include <time.h> + +#include "../toxcore/tox.h" + +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) +#define c_sleep(x) Sleep(1*x) +#else +#include <unistd.h> +#define c_sleep(x) usleep(1000*x) +#endif + +void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, uint16_t length, void *userdata) +{ + if (*((uint32_t *)userdata) != 974536) + return; + + if (length == 7 && memcmp("Gentoo", data, 7) == 0) { + tox_add_friend_norequest(m, public_key); + } +} +uint32_t messages_received; + +void print_message(Tox *m, int friendnumber, const uint8_t *string, uint16_t length, void *userdata) +{ + if (*((uint32_t *)userdata) != 974536) + return; + + 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, int friendnumber, const uint8_t *string, uint16_t length, void *userdata) +{ + if (*((uint32_t *)userdata) != 974536) + return; + + if (length == sizeof("Gentoo") && memcmp(string, "Gentoo", sizeof("Gentoo")) == 0) + ++name_changes; +} + +uint32_t typing_changes; + +void print_typingchange(Tox *m, int friendnumber, uint8_t typing, void *userdata) +{ + if (*((uint32_t *)userdata) != 974536) + return; + + if (!typing) + typing_changes = 1; + else + typing_changes = 2; +} + +uint8_t filenum; +uint32_t file_accepted; +uint64_t file_size; +void file_request_accept(Tox *m, int friendnumber, uint8_t filenumber, uint64_t filesize, const uint8_t *filename, + uint16_t filename_length, void *userdata) +{ + if (*((uint32_t *)userdata) != 974536) + return; + + if (filename_length == sizeof("Gentoo.exe") && memcmp(filename, "Gentoo.exe", sizeof("Gentoo.exe")) == 0) + ++file_accepted; + + file_size = filesize; + tox_file_send_control(m, friendnumber, 1, filenumber, TOX_FILECONTROL_ACCEPT, NULL, 0); +} + +uint32_t file_sent; +uint32_t sendf_ok; +void file_print_control(Tox *m, int friendnumber, uint8_t receive_send, uint8_t filenumber, uint8_t control_type, + const uint8_t *data, uint16_t length, void *userdata) +{ + if (*((uint32_t *)userdata) != 974536) + return; + + if (receive_send == 0 && control_type == TOX_FILECONTROL_FINISHED) + tox_file_send_control(m, friendnumber, 1, filenumber, TOX_FILECONTROL_FINISHED, NULL, 0); + + if (receive_send == 1 && control_type == TOX_FILECONTROL_FINISHED) + file_sent = 1; + + if (receive_send == 1 && control_type == TOX_FILECONTROL_ACCEPT) + sendf_ok = 1; + +} + +uint64_t size_recv; +uint8_t num; +void write_file(Tox *m, int friendnumber, uint8_t filenumber, const uint8_t *data, uint16_t length, void *userdata) +{ + if (*((uint32_t *)userdata) != 974536) + return; + + uint8_t *f_data = malloc(length); + memset(f_data, num, length); + ++num; + + if (memcmp(f_data, data, length) == 0) { + size_recv += length; + } else { + printf("FILE_CORRUPTED\n"); + } +} + +START_TEST(test_few_clients) +{ + long long unsigned int con_time, cur_time = time(NULL); + Tox *tox1 = tox_new(TOX_ENABLE_IPV6_DEFAULT); + Tox *tox2 = tox_new(TOX_ENABLE_IPV6_DEFAULT); + Tox *tox3 = tox_new(TOX_ENABLE_IPV6_DEFAULT); + ck_assert_msg(tox1 || tox2 || tox3, "Failed to create 3 tox instances"); + uint32_t to_compare = 974536; + tox_callback_friend_request(tox2, accept_friend_request, &to_compare); + uint8_t address[TOX_FRIEND_ADDRESS_SIZE]; + tox_get_address(tox2, address); + int test = tox_add_friend(tox3, address, (uint8_t *)"Gentoo", 7); + ck_assert_msg(test == 0, "Failed to add friend error code: %i", test); + + uint8_t off = 1; + + while (1) { + tox_do(tox1); + tox_do(tox2); + tox_do(tox3); + + if (tox_isconnected(tox1) && tox_isconnected(tox2) && tox_isconnected(tox3) && off) { + printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time); + con_time = time(NULL); + off = 0; + } + + + if (tox_get_friend_connection_status(tox2, 0) == 1 && tox_get_friend_connection_status(tox3, 0) == 1) + break; + + c_sleep(50); + } + + 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)); + ck_assert_msg(tox_send_message(tox2, 0, msgs, TOX_MAX_MESSAGE_LENGTH + 1) == 0, + "TOX_MAX_MESSAGE_LENGTH is too small\n"); + ck_assert_msg(tox_send_message(tox2, 0, msgs, TOX_MAX_MESSAGE_LENGTH) != 0, "TOX_MAX_MESSAGE_LENGTH is too big\n"); + + while (1) { + messages_received = 0; + tox_do(tox1); + tox_do(tox2); + tox_do(tox3); + + if (messages_received) + break; + + c_sleep(50); + } + + printf("tox clients messaging succeeded\n"); + + tox_callback_name_change(tox3, print_nickchange, &to_compare); + tox_set_name(tox2, (uint8_t *)"Gentoo", sizeof("Gentoo")); + + while (1) { + name_changes = 0; + tox_do(tox1); + tox_do(tox2); + tox_do(tox3); + + if (name_changes) + break; + + c_sleep(50); + } + + uint8_t temp_name[sizeof("Gentoo")]; + tox_get_name(tox3, 0, temp_name); + ck_assert_msg(memcmp(temp_name, "Gentoo", sizeof("Gentoo")) == 0, "Name not correct"); + + tox_callback_typing_change(tox2, &print_typingchange, &to_compare); + tox_set_user_is_typing(tox3, 0, 1); + + while (1) { + typing_changes = 0; + tox_do(tox1); + tox_do(tox2); + tox_do(tox3); + + + if (typing_changes == 2) + break; + else + ck_assert_msg(typing_changes == 0, "Typing fail"); + + c_sleep(50); + } + + ck_assert_msg(tox_get_is_typing(tox2, 0) == 1, "Typing fail"); + tox_set_user_is_typing(tox3, 0, 0); + + while (1) { + typing_changes = 0; + tox_do(tox1); + tox_do(tox2); + tox_do(tox3); + + if (typing_changes == 1) + break; + else + ck_assert_msg(typing_changes == 0, "Typing fail"); + + c_sleep(50); + } + + ck_assert_msg(tox_get_is_typing(tox2, 0) == 0, "Typing fail"); + + filenum = file_accepted = file_size = file_sent = sendf_ok = size_recv = 0; + long long unsigned int f_time = time(NULL); + tox_callback_file_data(tox3, write_file, &to_compare); + tox_callback_file_control(tox2, file_print_control, &to_compare); + tox_callback_file_control(tox3, file_print_control, &to_compare); + tox_callback_file_send_request(tox3, file_request_accept, &to_compare); + uint64_t totalf_size = 100 * 1024 * 1024; + int fnum = tox_new_file_sender(tox2, 0, totalf_size, (uint8_t *)"Gentoo.exe", sizeof("Gentoo.exe")); + ck_assert_msg(fnum != -1, "tox_new_file_sender fail"); + int fpiece_size = tox_file_data_size(tox2, 0); + uint8_t *f_data = malloc(fpiece_size); + uint8_t num = 0; + memset(f_data, num, fpiece_size); + + while (1) { + file_sent = 0; + tox_do(tox1); + tox_do(tox2); + tox_do(tox3); + + if (sendf_ok) + while (tox_file_send_data(tox2, 0, fnum, f_data, fpiece_size < totalf_size ? fpiece_size : totalf_size) == 0) { + if (totalf_size <= fpiece_size) { + sendf_ok = 0; + tox_file_send_control(tox2, 0, 0, fnum, TOX_FILECONTROL_FINISHED, NULL, 0); + } + + ++num; + memset(f_data, num, fpiece_size); + + totalf_size -= fpiece_size; + } + + if (file_sent && size_recv == file_size) + break; + + uint32_t tox1_interval = tox_do_interval(tox1); + uint32_t tox2_interval = tox_do_interval(tox2); + uint32_t tox3_interval = tox_do_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("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 66 +#define NUM_FRIENDS 20 + +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(TOX_ENABLE_IPV6_DEFAULT); + ck_assert_msg(toxes[i] != 0, "Failed to create tox instances %u", i); + tox_callback_friend_request(toxes[i], accept_friend_request, &to_comp); + } + + struct { + uint16_t tox1; + uint16_t tox2; + } pairs[NUM_FRIENDS]; + + uint8_t address[TOX_FRIEND_ADDRESS_SIZE]; + + 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_get_address(toxes[pairs[i].tox1], address); + int test = tox_add_friend(toxes[pairs[i].tox2], address, (uint8_t *)"Gentoo", 7); + + if (test == TOX_FAERR_ALREADYSENT) { + goto loop_top; + } + + ck_assert_msg(test >= 0, "Failed to add friend error code: %i", test); + } + + while (1) { + uint16_t counter = 0; + + for (i = 0; i < NUM_TOXES; ++i) { + for (j = 0; j < tox_count_friendlist(toxes[i]); ++j) + if (tox_get_friend_connection_status(toxes[i], j) == 1) + ++counter; + } + + if (counter == NUM_FRIENDS * 2) { + break; + } + + for (i = 0; i < NUM_TOXES; ++i) { + tox_do(toxes[i]); + } + + c_sleep(50); + } + + printf("test_many_clients succeeded, took %llu seconds\n", time(NULL) - cur_time); + + for (i = 0; i < NUM_TOXES; ++i) { + tox_kill(toxes[i]); + } +} +END_TEST + +#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); +Suite *tox_suite(void) +{ + Suite *s = suite_create("Tox"); + + DEFTESTCASE_SLOW(few_clients, 50); + DEFTESTCASE_SLOW(many_clients, 150); + 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; +} + diff --git a/protocols/Tox/toxcore/auto_tests/toxav_basic_test.c b/protocols/Tox/toxcore/auto_tests/toxav_basic_test.c new file mode 100644 index 0000000000..57685bfca7 --- /dev/null +++ b/protocols/Tox/toxcore/auto_tests/toxav_basic_test.c @@ -0,0 +1,599 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#include <check.h> +#include <stdlib.h> +#include <time.h> +#include <assert.h> + +#include "../toxcore/tox.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 <unistd.h> +#define c_sleep(x) usleep(1000*x) +#endif + + + +typedef enum _CallStatus { + none, + InCall, + Ringing, + Ended, + Rejected, + Cancel, + TimedOut + +} CallStatus; + +typedef struct _Party { + CallStatus status; + ToxAv *av; + time_t *CallStarted; + int call_index; +} Party; + +typedef struct _Status { + Party Alice; + Party Bob; +} Status; + +/* My default settings */ +static ToxAvCSettings muhcaps; + +void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, uint16_t length, void *userdata) +{ + if (length == 7 && memcmp("gentoo", data, 7) == 0) { + tox_add_friend_norequest(m, public_key); + } +} + + +/******************************************************************************/ +void callback_recv_invite ( void *av, int32_t call_index, void *_arg ) +{ + Status *cast = _arg; + + /* Bob always receives invite */ + cast->Bob.status = Ringing; + cast->Bob.call_index = call_index; +} +void callback_recv_ringing ( void *av, int32_t call_index, void *_arg ) +{ + Status *cast = _arg; + + /* Alice always sends invite */ + cast->Alice.status = Ringing; +} +void callback_recv_starting ( void *av, int32_t call_index, void *_arg ) +{ + Status *cast = _arg; + + /* Alice always sends invite */ + printf("Call started on Alice side...\n"); + cast->Alice.status = InCall; + toxav_prepare_transmission(av, call_index, av_jbufdc, av_VADd, 1); +} +void callback_recv_ending ( void *av, int32_t call_index, void *_arg ) +{ + Status *cast = _arg; + + if ( cast->Alice.status == Rejected) { + printf ( "Call ended for Bob!\n" ); + cast->Bob.status = Ended; + } else { + printf ( "Call ended for Alice!\n" ); + cast->Alice.status = Ended; + } +} + + +void callback_call_started ( void *av, int32_t call_index, void *_arg ) +{ + Status *cast = _arg; + + /* Alice always sends invite */ + printf("Call started on Bob side...\n"); + cast->Bob.status = InCall; + toxav_prepare_transmission(av, call_index, av_jbufdc, av_VADd, 1); +} +void callback_call_canceled ( void *av, int32_t call_index, void *_arg ) +{ + Status *cast = _arg; + + printf ( "Call Canceled for Bob!\n" ); + cast->Bob.status = Cancel; +} +void callback_call_rejected ( void *av, int32_t call_index, void *_arg ) +{ + Status *cast = _arg; + + printf ( "Call rejected by Bob!\n" + "Call ended for Alice!\n" ); + /* If Bob rejects, call is ended for alice and she sends ending */ + cast->Alice.status = Rejected; +} +void callback_call_ended ( void *av, int32_t call_index, void *_arg ) +{ + Status *cast = _arg; + + printf ( "Call ended for Bob!\n" ); + cast->Bob.status = Ended; +} + +void callback_call_type_change ( void *av, int32_t call_index, void *_arg ) +{ + ToxAvCSettings csettings; + toxav_get_peer_csettings(av, call_index, 0, &csettings); + + printf("New settings: \n" + "Type: %u \n" + "Video bitrate: %u \n" + "Video height: %u \n" + "Video width: %u \n" + "Audio bitrate: %u \n" + "Audio framedur: %u \n" + "Audio sample rate: %u \n" + "Audio channels: %u \n", + csettings.call_type, + csettings.video_bitrate, + csettings.max_video_height, + csettings.max_video_width, + csettings.audio_bitrate, + csettings.audio_frame_duration, + csettings.audio_sample_rate, + csettings.audio_channels + ); +} + +void callback_requ_timeout ( void *av, int32_t call_index, void *_arg ) +{ + Status *cast = _arg; + printf("Call timed-out!\n"); + cast->Alice.status = TimedOut; +} + +static void callback_audio(ToxAv *av, int32_t call_index, int16_t *data, int length, void *userdata) +{ +} + +static void callback_video(ToxAv *av, int32_t call_index, vpx_image_t *img, void *userdata) +{ +} + +void register_callbacks(ToxAv *av, void *data) +{ + toxav_register_callstate_callback(av, callback_call_started, av_OnStart, data); + toxav_register_callstate_callback(av, callback_call_canceled, av_OnCancel, data); + toxav_register_callstate_callback(av, callback_call_rejected, av_OnReject, data); + toxav_register_callstate_callback(av, callback_call_ended, av_OnEnd, data); + toxav_register_callstate_callback(av, callback_recv_invite, av_OnInvite, data); + + toxav_register_callstate_callback(av, callback_recv_ringing, av_OnRinging, data); + toxav_register_callstate_callback(av, callback_recv_starting, av_OnStarting, data); + toxav_register_callstate_callback(av, callback_recv_ending, av_OnEnding, data); + + toxav_register_callstate_callback(av, callback_requ_timeout, av_OnRequestTimeout, data); + toxav_register_callstate_callback(av, callback_call_type_change, av_OnMediaChange, data); + + + toxav_register_audio_recv_callback(av, callback_audio, NULL); + toxav_register_video_recv_callback(av, callback_video, NULL); +} + + +/*************************************************************************************************/ + +/* Alice calls bob and the call starts. + * What happens during the call is defined after. To quit the loop use: step++; + */ +#define CALL_AND_START_LOOP(AliceCallType, BobCallType) \ +{ int step = 0, running = 1; while (running) {\ + tox_do(bootstrap_node); tox_do(Alice); tox_do(Bob); \ + switch ( step ) {\ + case 0: /* Alice */ printf("Alice is calling...\n");\ + toxav_call(status_control.Alice.av, &status_control.Alice.call_index, 0, &muhcaps, 10); step++; break;\ + case 1: /* Bob */ if (status_control.Bob.status == Ringing) { printf("Bob answers...\n");\ + cur_time = time(NULL); toxav_answer(status_control.Bob.av, status_control.Bob.call_index, &muhcaps); step++; } break; \ + case 2: /* Rtp transmission */ \ + if (status_control.Bob.status == InCall && status_control.Alice.status == InCall) + + +#define TERMINATE_SCOPE() break;\ +case 3: /* Wait for Both to have status ended */\ +if (status_control.Alice.status == Ended && status_control.Bob.status == Ended) running = 0; break; } c_sleep(20); } } printf("\n"); + +START_TEST(test_AV_flows) +// int test_AV_flows() +{ + long long unsigned int cur_time = time(NULL); + Tox *bootstrap_node = tox_new(0); + Tox *Alice = tox_new(0); + Tox *Bob = tox_new(0); + + ck_assert_msg(bootstrap_node || Alice || Bob, "Failed to create 3 tox instances"); + + uint32_t to_compare = 974536; + tox_callback_friend_request(Alice, accept_friend_request, &to_compare); + uint8_t address[TOX_FRIEND_ADDRESS_SIZE]; + tox_get_address(Alice, address); + int test = tox_add_friend(Bob, address, (uint8_t *)"gentoo", 7); + + ck_assert_msg(test == 0, "Failed to add friend error code: %i", test); + + uint8_t off = 1; + + while (1) { + tox_do(bootstrap_node); + tox_do(Alice); + tox_do(Bob); + + if (tox_isconnected(bootstrap_node) && tox_isconnected(Alice) && tox_isconnected(Bob) && off) { + printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time); + off = 0; + } + + if (tox_get_friend_connection_status(Alice, 0) == 1 && tox_get_friend_connection_status(Bob, 0) == 1) + break; + + c_sleep(20); + } + + printf("All set after %llu seconds! Starting call...\n", time(NULL) - cur_time); + + muhcaps = av_DefaultSettings; + muhcaps.max_video_height = muhcaps.max_video_width = 128; + + Status status_control = { + {none, toxav_new(Alice, 1), NULL, -1}, + {none, toxav_new(Bob, 1), NULL, -1}, + }; + + ck_assert_msg(status_control.Alice.av || status_control.Bob.av, "Failed to create 2 toxav instances"); + + + register_callbacks(status_control.Alice.av, &status_control); + register_callbacks(status_control.Bob.av, &status_control); + + const int frame_size = (av_DefaultSettings.audio_sample_rate * av_DefaultSettings.audio_frame_duration / 1000); + int16_t sample_payload[frame_size]; + randombytes((uint8_t *)sample_payload, sizeof(int16_t) * frame_size); + + uint8_t prepared_payload[RTP_PAYLOAD_SIZE]; + int payload_size; + + vpx_image_t *sample_image = vpx_img_alloc(NULL, VPX_IMG_FMT_I420, 128, 128, 1); + + memcpy(sample_image->planes[VPX_PLANE_Y], sample_payload, 10); + memcpy(sample_image->planes[VPX_PLANE_U], sample_payload, 10); + memcpy(sample_image->planes[VPX_PLANE_V], sample_payload, 10); + + + /************************************************************************************************* + * Successful flows (when call starts) + */ + + /* + * Call with audio only on both sides. Alice calls Bob. + */ + + + CALL_AND_START_LOOP(TypeAudio, TypeAudio) { + /* Both send */ + payload_size = toxav_prepare_audio_frame(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, + 1000, sample_payload, frame_size); + + if ( payload_size < 0 ) { + ck_assert_msg ( 0, "Failed to encode payload" ); + } + + toxav_send_audio(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, payload_size); + + payload_size = toxav_prepare_audio_frame(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, 1000, + sample_payload, frame_size); + + if ( payload_size < 0 ) { + ck_assert_msg ( 0, "Failed to encode payload" ); + } + + toxav_send_audio(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, payload_size); + + if (time(NULL) - cur_time > 10) { /* Transmit for 10 seconds */ + step++; /* This terminates the loop */ + toxav_kill_transmission(status_control.Alice.av, status_control.Alice.call_index); + toxav_kill_transmission(status_control.Bob.av, status_control.Bob.call_index); + + /* Call over Alice hangs up */ + toxav_hangup(status_control.Alice.av, status_control.Alice.call_index); + } + } + TERMINATE_SCOPE() + + + /* + * Call with audio on both sides and video on one side. Alice calls Bob. + */ + CALL_AND_START_LOOP(TypeAudio, TypeVideo) { + /* Both send */ + payload_size = toxav_prepare_audio_frame(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, + 1000, sample_payload, frame_size); + + if ( payload_size < 0 ) { + ck_assert_msg ( 0, "Failed to encode payload" ); + } + + toxav_send_audio(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, payload_size); + + payload_size = toxav_prepare_audio_frame(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, 1000, + sample_payload, frame_size); + + if ( payload_size < 0 ) { + ck_assert_msg ( 0, "Failed to encode payload" ); + } + + toxav_send_audio(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, payload_size); + +// toxav_send_video(status_control.Bob.av, status_control.Bob.call_index, sample_image); + + if (time(NULL) - cur_time > 10) { /* Transmit for 10 seconds */ + step++; /* This terminates the loop */ + toxav_kill_transmission(status_control.Alice.av, status_control.Alice.call_index); + toxav_kill_transmission(status_control.Bob.av, status_control.Bob.call_index); + + /* Call over Alice hangs up */ + toxav_hangup(status_control.Alice.av, status_control.Alice.call_index); + } + } + TERMINATE_SCOPE() + + + /* + * Call with audio and video on both sides. Alice calls Bob. + */ + CALL_AND_START_LOOP(TypeVideo, TypeVideo) { + /* Both send */ + + payload_size = toxav_prepare_audio_frame(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, + 1000, sample_payload, frame_size); + + if ( payload_size < 0 ) { + ck_assert_msg ( 0, "Failed to encode payload" ); + } + + toxav_send_audio(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, payload_size); + + payload_size = toxav_prepare_audio_frame(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, 1000, + sample_payload, frame_size); + + if ( payload_size < 0 ) { + ck_assert_msg ( 0, "Failed to encode payload" ); + } + + toxav_send_audio(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, payload_size); + +// toxav_send_video(status_control.Alice.av, status_control.Alice.call_index, sample_image); +// toxav_send_video(status_control.Bob.av, status_control.Bob.call_index, sample_image); + + + if (time(NULL) - cur_time > 10) { /* Transmit for 10 seconds */ + step++; /* This terminates the loop */ + toxav_kill_transmission(status_control.Alice.av, status_control.Alice.call_index); + toxav_kill_transmission(status_control.Bob.av, status_control.Bob.call_index); + + /* Call over Alice hangs up */ + toxav_hangup(status_control.Alice.av, status_control.Alice.call_index); + } + } + TERMINATE_SCOPE() + + + uint64_t times_they_are_a_changin = time(NULL); + /* Media change */ + CALL_AND_START_LOOP(TypeAudio, TypeAudio) { + /* Both send */ + payload_size = toxav_prepare_audio_frame(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, + 1000, sample_payload, frame_size); + + if ( payload_size < 0 ) { + ck_assert_msg ( 0, "Failed to encode payload" ); + } + + toxav_send_audio(status_control.Alice.av, status_control.Alice.call_index, prepared_payload, payload_size); + + payload_size = toxav_prepare_audio_frame(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, 1000, + sample_payload, frame_size); + + if ( payload_size < 0 ) { + ck_assert_msg ( 0, "Failed to encode payload" ); + } + + toxav_send_audio(status_control.Bob.av, status_control.Bob.call_index, prepared_payload, payload_size); + + /* Wait 2 seconds and change transmission type */ + if (time(NULL) - times_they_are_a_changin > 2) { + times_they_are_a_changin = time(NULL); + muhcaps.audio_bitrate ++; + toxav_change_settings(status_control.Alice.av, status_control.Alice.call_index, &muhcaps); + } + + if (time(NULL) - cur_time > 10) { /* Transmit for 10 seconds */ + step++; /* This terminates the loop */ + toxav_kill_transmission(status_control.Alice.av, status_control.Alice.call_index); + toxav_kill_transmission(status_control.Bob.av, status_control.Bob.call_index); + + /* Call over Alice hangs up */ + toxav_hangup(status_control.Alice.av, status_control.Alice.call_index); + } + } + TERMINATE_SCOPE() + + + /************************************************************************************************* + * Other flows + */ + + /* + * Call and reject + */ + { + int step = 0; + int running = 1; + + while (running) { + tox_do(bootstrap_node); + tox_do(Alice); + tox_do(Bob); + + switch ( step ) { + case 0: /* Alice */ + printf("Alice is calling...\n"); + toxav_call(status_control.Alice.av, &status_control.Alice.call_index, 0, &muhcaps, 10); + step++; + break; + + case 1: /* Bob */ + if (status_control.Bob.status == Ringing) { + printf("Bob rejects...\n"); + toxav_reject(status_control.Bob.av, status_control.Bob.call_index, "Who likes D's anyway?"); + step++; + } + + break; + + case 2: /* Wait for Both to have status ended */ + if (status_control.Alice.status == Rejected && status_control.Bob.status == Ended) running = 0; + + break; + } + + c_sleep(20); + } + + printf("\n"); + } + + + /* + * Call and cancel + */ + { + int step = 0; + int running = 1; + + while (running) { + tox_do(bootstrap_node); + tox_do(Alice); + tox_do(Bob); + + switch ( step ) { + case 0: /* Alice */ + printf("Alice is calling...\n"); + toxav_call(status_control.Alice.av, &status_control.Alice.call_index, 0, &muhcaps, 10); + step++; + break; + + + case 1: /* Alice again */ + if (status_control.Bob.status == Ringing) { + printf("Alice cancels...\n"); + toxav_cancel(status_control.Alice.av, status_control.Alice.call_index, 0, "Who likes D's anyway?"); + step++; + } + + break; + + case 2: /* Wait for Both to have status ended */ + if (status_control.Bob.status == Cancel) running = 0; + + break; + } + + c_sleep(20); + } + + printf("\n"); + } + + /* + * Timeout + */ + { + int step = 0; + int running = 1; + + while (running) { + tox_do(bootstrap_node); + tox_do(Alice); + tox_do(Bob); + + switch ( step ) { + case 0: + printf("Alice is calling...\n"); + toxav_call(status_control.Alice.av, &status_control.Alice.call_index, 0, &muhcaps, 10); + step++; + break; + + case 1: + if (status_control.Alice.status == TimedOut) running = 0; + + break; + } + + c_sleep(20); + } + + printf("\n"); + } + + + + + printf("Calls ended!\n"); +} +END_TEST + +/*************************************************************************************************/ + + +/*************************************************************************************************/ + +/*************************************************************************************************/ + + +Suite *tox_suite(void) +{ + Suite *s = suite_create("ToxAV"); + + TCase *tc_av_flows = tcase_create("AV_flows"); + tcase_add_test(tc_av_flows, test_AV_flows); + tcase_set_timeout(tc_av_flows, 200); + suite_add_tcase(s, tc_av_flows); + + return s; +} +int main(int argc, char *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; + +// return test_AV_flows(); +} diff --git a/protocols/Tox/toxcore/auto_tests/toxav_many_test.c b/protocols/Tox/toxcore/auto_tests/toxav_many_test.c new file mode 100644 index 0000000000..d9c588f728 --- /dev/null +++ b/protocols/Tox/toxcore/auto_tests/toxav_many_test.c @@ -0,0 +1,402 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#include <check.h> +#include <stdlib.h> +#include <time.h> +#include <assert.h> + +#include "../toxcore/tox.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 <unistd.h> +#include <pthread.h> +#define c_sleep(x) usleep(1000*x) +#endif + +pthread_mutex_t muhmutex; + +typedef enum _CallStatus { + none, + InCall, + Ringing, + Ended, + Rejected, + Cancel + +} CallStatus; + +typedef struct _Party { + CallStatus status; + ToxAv *av; + int id; +} Party; + +typedef struct _ACall { + pthread_t tid; + int idx; + + Party Caller; + Party Callee; +} ACall; + +typedef struct _Status { + ACall calls[3]; /* Make 3 calls for this test */ +} Status; + +Status status_control; + +void accept_friend_request(Tox *m, const uint8_t *public_key, const uint8_t *data, uint16_t length, void *userdata) +{ + if (length == 7 && memcmp("gentoo", data, 7) == 0) { + tox_add_friend_norequest(m, public_key); + } +} + + +/******************************************************************************/ +void callback_recv_invite ( void *av, int32_t call_index, void *_arg ) +{ + /* + Status *cast = _arg; + + cast->calls[call_index].Callee.status = Ringing;*/ +} +void callback_recv_ringing ( void *av, int32_t call_index, void *_arg ) +{ + Status *cast = _arg; + cast->calls[call_index].Caller.status = Ringing; +} +void callback_recv_starting ( void *av, int32_t call_index, void *_arg ) +{ + Status *cast = _arg; + cast->calls[call_index].Caller.status = InCall; +} +void callback_recv_ending ( void *av, int32_t call_index, void *_arg ) +{ + Status *cast = _arg; + cast->calls[call_index].Caller.status = Ended; +} + +void callback_call_started ( void *av, int32_t call_index, void *_arg ) +{ + /* + Status *cast = _arg; + + cast->calls[call_index].Callee.status = InCall;*/ +} +void callback_call_canceled ( void *av, int32_t call_index, void *_arg ) +{ + /* + Status *cast = _arg; + + cast->calls[call_index].Callee.status = Cancel;*/ +} +void callback_call_rejected ( void *av, int32_t call_index, void *_arg ) +{ + Status *cast = _arg; + cast->calls[call_index].Caller.status = Rejected; +} +void callback_call_ended ( void *av, int32_t call_index, void *_arg ) +{ + /* + Status *cast = _arg; + + cast->calls[call_index].Callee.status = Ended;*/ +} + +void callback_requ_timeout ( void *av, int32_t call_index, void *_arg ) +{ + //ck_assert_msg(0, "No answer!"); +} + +static void callback_audio(ToxAv *av, int32_t call_index, int16_t *data, int length, void *userdata) +{ +} + +static void callback_video(ToxAv *av, int32_t call_index, vpx_image_t *img, void *userdata) +{ +} + +void register_callbacks(ToxAv *av, void *data) +{ + toxav_register_callstate_callback(av, callback_call_started, av_OnStart, data); + toxav_register_callstate_callback(av, callback_call_canceled, av_OnCancel, data); + toxav_register_callstate_callback(av, callback_call_rejected, av_OnReject, data); + toxav_register_callstate_callback(av, callback_call_ended, av_OnEnd, data); + toxav_register_callstate_callback(av, callback_recv_invite, av_OnInvite, data); + + toxav_register_callstate_callback(av, callback_recv_ringing, av_OnRinging, data); + toxav_register_callstate_callback(av, callback_recv_starting, av_OnStarting, data); + toxav_register_callstate_callback(av, callback_recv_ending, av_OnEnding, data); + + toxav_register_callstate_callback(av, callback_requ_timeout, av_OnRequestTimeout, data); + + + toxav_register_audio_recv_callback(av, callback_audio, NULL); + toxav_register_video_recv_callback(av, callback_video, NULL); +} +/*************************************************************************************************/ + +int call_running[3]; + +void *in_thread_call (void *arg) +{ +#define call_print(call, what, args...) printf("[%d] " what "\n", call, ##args) + + ACall *this_call = arg; + uint64_t start = 0; + int step = 0; + int call_idx; + + call_running[this_call->idx] = 1; + + const int frame_size = (av_DefaultSettings.audio_sample_rate * av_DefaultSettings.audio_frame_duration / 1000); + int16_t sample_payload[frame_size]; + randombytes((uint8_t *)sample_payload, sizeof(int16_t) * frame_size); + + uint8_t prepared_payload[RTP_PAYLOAD_SIZE]; + + register_callbacks(this_call->Caller.av, &status_control); + register_callbacks(this_call->Callee.av, arg); + + /* NOTE: CALLEE WILL ALWAHYS NEED CALL_IDX == 0 */ + while (call_running[this_call->idx]) { + + switch ( step ) { + case 0: /* CALLER */ + toxav_call(this_call->Caller.av, &call_idx, this_call->Callee.id, &av_DefaultSettings, 10); + call_print(call_idx, "Calling ..."); + step++; + break; + + case 1: /* CALLEE */ + if (this_call->Caller.status == Ringing) { + call_print(call_idx, "Callee answers ..."); + toxav_answer(this_call->Callee.av, 0, &av_DefaultSettings); + step++; + start = time(NULL); + } + + break; + + case 2: /* Rtp transmission */ + if (this_call->Caller.status == InCall) { /* I think this is okay */ + call_print(call_idx, "Sending rtp ..."); + + c_sleep(1000); /* We have race condition here */ + toxav_prepare_transmission(this_call->Callee.av, 0, 3, 0, 1); + toxav_prepare_transmission(this_call->Caller.av, call_idx, 3, 0, 1); + + int payload_size = toxav_prepare_audio_frame(this_call->Caller.av, call_idx, prepared_payload, RTP_PAYLOAD_SIZE, + sample_payload, frame_size); + + if ( payload_size < 0 ) { + //ck_assert_msg ( 0, "Failed to encode payload" ); + } + + + while (time(NULL) - start < 10) { /* 10 seconds */ + /* Both send */ + toxav_send_audio(this_call->Caller.av, call_idx, prepared_payload, payload_size); + + toxav_send_audio(this_call->Callee.av, 0, prepared_payload, payload_size); + + /* Both receive */ + int16_t storage[RTP_PAYLOAD_SIZE]; + int recved; + + c_sleep(20); + } + + step++; /* This terminates the loop */ + + pthread_mutex_lock(&muhmutex); + toxav_kill_transmission(this_call->Callee.av, 0); + toxav_kill_transmission(this_call->Caller.av, call_idx); + pthread_mutex_unlock(&muhmutex); + + /* Call over CALLER hangs up */ + toxav_hangup(this_call->Caller.av, call_idx); + call_print(call_idx, "Hanging up ..."); + } + + break; + + case 3: /* Wait for Both to have status ended */ + if (this_call->Caller.status == Ended) { + c_sleep(1000); /* race condition */ + this_call->Callee.status = Ended; + call_running[this_call->idx] = 0; + } + + break; + + } + + c_sleep(20); + } + + call_print(call_idx, "Call ended successfully!"); + pthread_exit(NULL); +} + + + + + +// START_TEST(test_AV_three_calls) +void test_AV_three_calls() +{ + long long unsigned int cur_time = time(NULL); + Tox *bootstrap_node = tox_new(0); + Tox *caller = tox_new(0); + Tox *callees[3] = { + tox_new(0), + tox_new(0), + tox_new(0), + }; + + + //ck_assert_msg(bootstrap_node != NULL, "Failed to create bootstrap node"); + + int i = 0; + + for (; i < 3; i ++) { + //ck_assert_msg(callees[i] != NULL, "Failed to create 3 tox instances"); + } + + for ( i = 0; i < 3; i ++ ) { + uint32_t to_compare = 974536; + tox_callback_friend_request(callees[i], accept_friend_request, &to_compare); + uint8_t address[TOX_FRIEND_ADDRESS_SIZE]; + tox_get_address(callees[i], address); + + int test = tox_add_friend(caller, address, (uint8_t *)"gentoo", 7); + //ck_assert_msg( test == i, "Failed to add friend error code: %i", test); + } + + uint8_t off = 1; + + while (1) { + tox_do(bootstrap_node); + tox_do(caller); + + for (i = 0; i < 3; i ++) { + tox_do(callees[i]); + } + + + if (tox_isconnected(bootstrap_node) && + tox_isconnected(caller) && + tox_isconnected(callees[0]) && + tox_isconnected(callees[1]) && + tox_isconnected(callees[2]) && off) { + printf("Toxes are online, took %llu seconds\n", time(NULL) - cur_time); + off = 0; + } + + + if (tox_get_friend_connection_status(caller, 0) == 1 && + tox_get_friend_connection_status(caller, 1) == 1 && + tox_get_friend_connection_status(caller, 2) == 1 ) + break; + + c_sleep(20); + } + + printf("All set after %llu seconds! Starting call...\n", time(NULL) - cur_time); + + ToxAv *uniqcallerav = toxav_new(caller, 3); + + for (i = 0; i < 3; i ++) { + status_control.calls[i].idx = i; + + status_control.calls[i].Caller.av = uniqcallerav; + status_control.calls[i].Caller.id = 0; + status_control.calls[i].Caller.status = none; + + status_control.calls[i].Callee.av = toxav_new(callees[i], 1); + status_control.calls[i].Callee.id = i; + status_control.calls[i].Callee.status = none; + } + + pthread_mutex_init(&muhmutex, NULL); + + for ( i = 0; i < 3; i++ ) + pthread_create(&status_control.calls[i].tid, NULL, in_thread_call, &status_control.calls[i]); + + /* Now start 3 calls and they'll run for 10 s */ + + for ( i = 0; i < 3; i++ ) + pthread_detach(status_control.calls[i].tid); + + while (call_running[0] || call_running[1] || call_running[2]) { + pthread_mutex_lock(&muhmutex); + + tox_do(bootstrap_node); + tox_do(caller); + tox_do(callees[0]); + tox_do(callees[1]); + tox_do(callees[2]); + + pthread_mutex_unlock(&muhmutex); + c_sleep(20); + } + + toxav_kill(status_control.calls[0].Caller.av); + toxav_kill(status_control.calls[0].Callee.av); + toxav_kill(status_control.calls[1].Callee.av); + toxav_kill(status_control.calls[2].Callee.av); + + tox_kill(bootstrap_node); + tox_kill(caller); + + for ( i = 0; i < 3; i ++) + tox_kill(callees[i]); + +} +// END_TEST + + + + +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[]) +{ +// 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; + + test_AV_three_calls(); + + return 0; +} diff --git a/protocols/Tox/toxcore/autogen.sh b/protocols/Tox/toxcore/autogen.sh new file mode 100644 index 0000000000..9de319ec3b --- /dev/null +++ b/protocols/Tox/toxcore/autogen.sh @@ -0,0 +1,6 @@ +#!/bin/sh -e + +echo 'Running autoreconf -if...' +( + autoreconf -if +) diff --git a/protocols/Tox/toxcore/build/Makefile.am b/protocols/Tox/toxcore/build/Makefile.am new file mode 100644 index 0000000000..c2667d5de7 --- /dev/null +++ b/protocols/Tox/toxcore/build/Makefile.am @@ -0,0 +1,13 @@ +bin_PROGRAMS = +noinst_PROGRAMS = +lib_LTLIBRARIES = +noinst_bindir = $(top_builddir)/build +EXTRA_DIST= + +include ../toxcore/Makefile.inc +include ../toxdns/Makefile.inc +include ../toxav/Makefile.inc +include ../other/Makefile.inc +include ../testing/Makefile.inc +include ../other/bootstrap_daemon/Makefile.inc +include ../auto_tests/Makefile.inc diff --git a/protocols/Tox/toxcore/configure.ac b/protocols/Tox/toxcore/configure.ac new file mode 100644 index 0000000000..e3e13c30be --- /dev/null +++ b/protocols/Tox/toxcore/configure.ac @@ -0,0 +1,691 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.65]) +AC_INIT([tox], [0.0.0], [https://tox.im]) +AC_CONFIG_AUX_DIR(configure_aux) +AC_CONFIG_SRCDIR([toxcore/net_crypto.c]) +AC_CONFIG_HEADERS([config.h]) +AM_INIT_AUTOMAKE([1.10 -Wall subdir-objects]) +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" + +LOGGING="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 + LOGGING="yes" + + AC_DEFINE([LOGGING], [], [If logging enabled]) + AC_DEFINE([LOGGER_LEVEL], [DEBUG], [LoggerLevel value]) + AC_DEFINE_UNQUOTED([LOGGER_OUTPUT_FILE], ["$LOGGING_OUTNAM"], [Output of logger]) + fi + ] +) + +AC_ARG_WITH(logger-level, + AC_HELP_STRING([--with-logger-level=LEVEL], + [Logger levels: INFO; DEBUG; WARNING; ERROR ]), + [ + if test "x$LOGGING" = "xno"; then + AC_MSG_WARN([Logging disabled!]) + else + if test "x$withval" = "xINFO"; then + AC_DEFINE([LOGGER_LEVEL], [INFO], [LoggerLevel value]) + + elif test "x$withval" = "xDEBUG"; then + AC_DEFINE([LOGGER_LEVEL], [DEBUG], [LoggerLevel value]) + + elif test "x$withval" = "xWARNING"; then + AC_DEFINE([LOGGER_LEVEL], [WARNING], [LoggerLevel value]) + + elif test "x$withval" = "xERROR"; then + AC_DEFINE([LOGGER_LEVEL], [ERROR], [LoggerLevel value]) + else + AC_MSG_WARN([Invalid logger level: $withval. Using default 'DEBUG']) + fi + fi + ] +) + +AC_ARG_WITH(logger-path, + AC_HELP_STRING([--with-logger-path=DIR], + [Path of logger output]), + [ + if test "x$LOGGING" = "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<F2> 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" + ;; + *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, randombytes_random, + [ + LIBSODIUM_LDFLAGS="-L$LIBSODIUM_SEARCH_LIBS" + LIBSODIUM_LIBS="-lsodium" + ], + [ + AC_MSG_ERROR([required library libsodium was not found in requested location $LIBSODIUM_SEARCH_LIBS]) + ] + ) + else + AC_CHECK_LIB(sodium, randombytes_random, + [], + [ + AC_MSG_ERROR([required library libsodium was not found on your system, please check http://download.libsodium.org/libsodium/releases/]) + ] + ) + 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$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]) + ] +) + +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 + ## What about pthread? + AV_LIBS="$OPUS_LIBS $VPX_LIBS -pthread" + 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(WIN32, test "x$WIN32" = "xyes") + +AC_CONFIG_FILES([Makefile + build/Makefile + libtoxcore.pc + ]) + +AM_COND_IF(BUILD_AV, + [ + AC_CONFIG_FILES([libtoxav.pc]) + ],) + +AC_OUTPUT diff --git a/protocols/Tox/toxcore/dist-build/android-arm.sh b/protocols/Tox/toxcore/dist-build/android-arm.sh new file mode 100644 index 0000000000..af736da320 --- /dev/null +++ b/protocols/Tox/toxcore/dist-build/android-arm.sh @@ -0,0 +1,3 @@ +#!/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" diff --git a/protocols/Tox/toxcore/dist-build/android-armv7.sh b/protocols/Tox/toxcore/dist-build/android-armv7.sh new file mode 100644 index 0000000000..838a62f914 --- /dev/null +++ b/protocols/Tox/toxcore/dist-build/android-armv7.sh @@ -0,0 +1,3 @@ +#!/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" diff --git a/protocols/Tox/toxcore/dist-build/android-build.sh b/protocols/Tox/toxcore/dist-build/android-build.sh new file mode 100644 index 0000000000..984a531db1 --- /dev/null +++ b/protocols/Tox/toxcore/dist-build/android-build.sh @@ -0,0 +1,63 @@ +#!/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-soname-versions \ + --disable-av \ + --disable-ntox \ + --disable-daemon \ + --disable-phone \ + --prefix="${PREFIX}" && \ + +make clean && \ +make -j3 install && \ +echo "libtoxcore has been installed into ${PREFIX}" diff --git a/protocols/Tox/toxcore/dist-build/android-mips.sh b/protocols/Tox/toxcore/dist-build/android-mips.sh new file mode 100644 index 0000000000..6e105c2fac --- /dev/null +++ b/protocols/Tox/toxcore/dist-build/android-mips.sh @@ -0,0 +1,3 @@ +#!/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" diff --git a/protocols/Tox/toxcore/dist-build/android-x86.sh b/protocols/Tox/toxcore/dist-build/android-x86.sh new file mode 100644 index 0000000000..6768b8871f --- /dev/null +++ b/protocols/Tox/toxcore/dist-build/android-x86.sh @@ -0,0 +1,3 @@ +#!/bin/sh +export CFLAGS="-Ofast" +TARGET_ARCH=x86 TOOLCHAIN_NAME=x86-4.8 HOST_COMPILER=i686-linux-android "$(dirname "$0")/android-build.sh" diff --git a/protocols/Tox/toxcore/docs/Group-Chats.md b/protocols/Tox/toxcore/docs/Group-Chats.md new file mode 100644 index 0000000000..5e00b550cc --- /dev/null +++ b/protocols/Tox/toxcore/docs/Group-Chats.md @@ -0,0 +1,71 @@ +Massive public group chats. + +Note that not all this document has been implemented: only private (invite only) group chats are currently implemented. + +Everyone generates a short term public private key pair right before joining +the chat. + +Note that for public group chats it is impossible to protect the chat from +being spied on by a very dedicated attacker, encryption is therefor used as a +form of spam/access control. + +## Joining the chats + + +## Protocol + + +Node format: +See DHT, currently uses the IPv6 Node_format. + +Get nodes (Request): +Packet contents: +``` +[char with a value of 48][Bob's (The receiver's) Public key (client_id) (32 bytes))][Alice's (The sender's) Public key (client_id) (32 bytes)][Random nonce (24 bytes)][Encrypted with the nonce, private key of the sender and public key of the receiver:[char with a value of 48][random 8 byte (ping_id)] +``` +Valid replies: a send_nodes packet + +Send_nodes (response): +``` +[char with a value of 48][Bob's (The receiver's) Public key (client_id) (32 bytes))][Alice's (The sender's) Public key (client_id) (32 bytes)][Random nonce (24 bytes)][Encrypted with the nonce, private key of the sender and public key of the receiver:[char with a value of 49][random 8 byte (ping_id)][Nodes in node format, length=40 * (number of nodes (maximum of 6 nodes)) bytes]] +``` + +Broadcast packet: +``` +[char with a value of 48][Bob's (The receiver's) Public key (client_id) (32 bytes))][Alice's (The sender's) Public key (client_id) (32 bytes)][nonce][Encrypted with the nonce, private key of the sender and public key of the receiver:[char with a value of 50][Data to send to everyone]] +``` + + +Data to send to everyone: +TODO: signing and spam control + permissions. +[client_id of sender][uint32_t message number][char with a value representing id of message][data] + +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. +[uint8_t public_key[public_key_len]] + +17 - ban_peer +Ban a peer +[uint8_t public_key[public_key_len]] + +18 - topic change +[uint8_t topic[topiclen]] + +48 - name change +[uint8_t name[namelen]] + +49 - status change +[uint8_t (status id)] + +64 - chat message +[uint8_t message[messagelen]] + +65 - action (/me) +[uint8_t message[messagelen]] diff --git a/protocols/Tox/toxcore/docs/Hardening.txt b/protocols/Tox/toxcore/docs/Hardening.txt new file mode 100644 index 0000000000..50ccfd53e0 --- /dev/null +++ b/protocols/Tox/toxcore/docs/Hardening.txt @@ -0,0 +1,60 @@ +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.) +... diff --git a/protocols/Tox/toxcore/docs/Hardening_docs.txt b/protocols/Tox/toxcore/docs/Hardening_docs.txt new file mode 100644 index 0000000000..53a6103418 --- /dev/null +++ b/protocols/Tox/toxcore/docs/Hardening_docs.txt @@ -0,0 +1,30 @@ +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). diff --git a/protocols/Tox/toxcore/docs/Prevent_Tracking.txt b/protocols/Tox/toxcore/docs/Prevent_Tracking.txt new file mode 100644 index 0000000000..d170103f0b --- /dev/null +++ b/protocols/Tox/toxcore/docs/Prevent_Tracking.txt @@ -0,0 +1,158 @@ +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, public key that must be used to send data packets if is_stored is not 0][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) + +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] diff --git a/protocols/Tox/toxcore/docs/TCP_Network.txt b/protocols/Tox/toxcore/docs/TCP_Network.txt new file mode 100644 index 0000000000..4bc2d433ac --- /dev/null +++ b/protocols/Tox/toxcore/docs/TCP_Network.txt @@ -0,0 +1,154 @@ +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. diff --git a/protocols/Tox/toxcore/docs/TODO b/protocols/Tox/toxcore/docs/TODO new file mode 100644 index 0000000000..dea513b25d --- /dev/null +++ b/protocols/Tox/toxcore/docs/TODO @@ -0,0 +1,62 @@ +TODO list. + +[IN PROGRESS] Add what is left to do to the TODO list. + +Networking: + [NOT STARTED] UPnP port forwarding. + [NOT STARTED] NAT-PMP port forwarding. + +DHT: + [ALMOST DONE] Metadata collection prevention. (docs/Prevent_Tracking.txt) + [IN PROGRESS] Hardening against attacks. + [IN PROGRESS] Optimizing the code. + +Lossless UDP: + [DONE] Increase data send/receive rates. + +[IN PROGRESS] Massive IRC like group chats (text only) + [DONE] Networking base. + [NOT STARTED] Syncing chat state between clients (nicknames, list of who is in chat, etc...) + [NOT STARTED] Make clients sign their messages so that peers can't modify them. + +[IN PROGRESS] Audio/Video + [DONE] encoding/streaming/decoding + [DONE] Call initiation + [DONE] Encryption + [IN PROGRESS] Auditing. + [NEEDS TESTING] Video packet splitting. + [IN PROGRESS] Prevent audio skew (seems to be easily solvable client side.) + [IN PROGRESS] Group chats. + +Friend_requests.c: + [NOT STARTED] What happens when a friend request is received needs to be changed. + [NOT STARTED] Add multiple nospam functionality. + +[DONE] File transfers +[NOT STARTED] Offline messaging +[NOT STARTED] Friends list syncing +[IN PROGRESS] IPV6 support + [DONE] Networking + [DONE] DHT + Messenger + [NOT STARTED] Group chats (They work with IPv6 but some things need to be tested.) + + +[IN PROGRESS] Make toxcore thread safe. + +[NOT STARTED] Make the core save/datafile portable across client versions/different processor architectures. + +[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) + +[NEEDS TESTING] Make the save made with tox_save_encrypted(...) harder to brute force. +See: (https://github.com/jencka/ProjectTox-libtoxdata) + +[IN PROGRESS] GUI (no official one chosen yet, a list of promising ones follows) +https://github.com/notsecure/uTox +https://github.com/naxuroqa/Venom +https://github.com/Impyy/Toxy +https://github.com/lehitoskin/blight +https://github.com/nurupo/ProjectTox-Qt-GUI +https://github.com/Astonex/Antox + +[NOT STARTED] Security audit from professionals diff --git a/protocols/Tox/toxcore/docs/Tox_middle_level_network_protocol.txt b/protocols/Tox/toxcore/docs/Tox_middle_level_network_protocol.txt new file mode 100644 index 0000000000..a84132b6ca --- /dev/null +++ b/protocols/Tox/toxcore/docs/Tox_middle_level_network_protocol.txt @@ -0,0 +1,120 @@ +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 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 anyways. 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 online. Of course something to protect +from evil "friends" framing relays must also be implemented. + +Detailed implementation details: + +cookie request packet: +[uint8_t 24][Senders DHT Public key (32 bytes)][Random nonce (24 +bytes)][Encrypted message containing: [Senders 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 DHT private key, recievers 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 DHT private key, recievers DHT +public key and the nonce. + +The Cookie should be basically: +[nonce][encrypted data:[uint64_t time][Senders real public key (32 +bytes)][Senders 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 private 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 the second, the nonce + 2 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. +to get the real packet numbers from this list take the recvbuffers buffer_start +from the packet, substract 1 to it and put it in packet_num then start from the +beggining 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. + + diff --git a/protocols/Tox/toxcore/docs/av_api.md b/protocols/Tox/toxcore/docs/av_api.md new file mode 100644 index 0000000000..2f536ade8b --- /dev/null +++ b/protocols/Tox/toxcore/docs/av_api.md @@ -0,0 +1,194 @@ +#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 diff --git a/protocols/Tox/toxcore/docs/updates/Crypto.md b/protocols/Tox/toxcore/docs/updates/Crypto.md new file mode 100644 index 0000000000..cee03f0aa9 --- /dev/null +++ b/protocols/Tox/toxcore/docs/updates/Crypto.md @@ -0,0 +1,54 @@ +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 friends list by adding his 32 byte public key (client_id) to her friends list. +2 cases: +case 1: Alice adds Bobs public key and bob waits for Alice to attempt to connect to him. +case 2: Bob and Alice add their respective public keys to their friends list at the same time. + +case 1: +Alice sends a onion data (see: Prevent_tracking.txt) packet to bob with the encrypted part containing the friends 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 Bobs friends list: case 2 +If Alice is not in Bob's friends list and the nospam is good: Bob is prompt to add Alice and is shown the message from her. +If Bobs accepts Alice's friends request he adds her public key to his friends list. + +case 2: +Bob and Alice both have the others public key in their friends 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's (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 Bobs 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. diff --git a/protocols/Tox/toxcore/docs/updates/DHT.md b/protocols/Tox/toxcore/docs/updates/DHT.md new file mode 100644 index 0000000000..17db70cecd --- /dev/null +++ b/protocols/Tox/toxcore/docs/updates/DHT.md @@ -0,0 +1,105 @@ +DHT protocol +============ + +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=1 to NODES_ENCRYPTED_MESSAGE_LENGTH 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 8 nodes)) bytes][Sendback data, length=1 to NODES_ENCRYPTED_MESSAGE_LENGTH bytes]] +``` diff --git a/protocols/Tox/toxcore/docs/updates/Spam-Prevention.md b/protocols/Tox/toxcore/docs/updates/Spam-Prevention.md new file mode 100644 index 0000000000..a0713fd1e6 --- /dev/null +++ b/protocols/Tox/toxcore/docs/updates/Spam-Prevention.md @@ -0,0 +1,12 @@ + +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. diff --git a/protocols/Tox/toxcore/docs/updates/Symmetric-NAT-Transversal.md b/protocols/Tox/toxcore/docs/updates/Symmetric-NAT-Transversal.md new file mode 100644 index 0000000000..490382160f --- /dev/null +++ b/protocols/Tox/toxcore/docs/updates/Symmetric-NAT-Transversal.md @@ -0,0 +1,43 @@ +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. diff --git a/protocols/Tox/toxcore/libtoxav.pc.in b/protocols/Tox/toxcore/libtoxav.pc.in new file mode 100644 index 0000000000..05493231d9 --- /dev/null +++ b/protocols/Tox/toxcore/libtoxav.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libtoxav +Description: Tox A/V library +Requires: +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -ltoxav @AV_LIBS@ +Cflags: -I${includedir}
\ No newline at end of file diff --git a/protocols/Tox/toxcore/libtoxcore.pc.in b/protocols/Tox/toxcore/libtoxcore.pc.in new file mode 100644 index 0000000000..a91a1fb745 --- /dev/null +++ b/protocols/Tox/toxcore/libtoxcore.pc.in @@ -0,0 +1,11 @@ +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 -ltoxcore @NACL_LIBS@ @LIBS@ @MATH_LDFLAGS@ +Cflags: -I${includedir} diff --git a/protocols/Tox/toxcore/m4/ax_have_epoll.m4 b/protocols/Tox/toxcore/m4/ax_have_epoll.m4 new file mode 100644 index 0000000000..07ceb49f15 --- /dev/null +++ b/protocols/Tox/toxcore/m4/ax_have_epoll.m4 @@ -0,0 +1,104 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_have_epoll.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_HAVE_EPOLL([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# AX_HAVE_EPOLL_PWAIT([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# +# DESCRIPTION +# +# This macro determines whether the system supports the epoll I/O event +# interface. A neat usage example would be: +# +# AX_HAVE_EPOLL( +# [AX_CONFIG_FEATURE_ENABLE(epoll)], +# [AX_CONFIG_FEATURE_DISABLE(epoll)]) +# AX_CONFIG_FEATURE( +# [epoll], [This platform supports epoll(7)], +# [HAVE_EPOLL], [This platform supports epoll(7).]) +# +# The epoll interface was added to the Linux kernel in version 2.5.45, and +# the macro verifies that a kernel newer than this is installed. This +# check is somewhat unreliable if <linux/version.h> doesn't match the +# running kernel, but it is necessary regardless, because glibc comes with +# stubs for the epoll_create(), epoll_wait(), etc. that allow programs to +# compile and link even if the kernel is too old; the problem would then +# be detected only at runtime. +# +# Linux kernel version 2.6.19 adds the epoll_pwait() call in addition to +# epoll_wait(). The availability of that function can be tested with the +# second macro. Generally speaking, it is safe to assume that +# AX_HAVE_EPOLL would succeed if AX_HAVE_EPOLL_PWAIT has, but not the +# other way round. +# +# LICENSE +# +# Copyright (c) 2008 Peter Simons <simons@cryp.to> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 10 + +AC_DEFUN([AX_HAVE_EPOLL], [dnl + ax_have_epoll_cppflags="${CPPFLAGS}" + AC_CHECK_HEADER([linux/version.h], [CPPFLAGS="${CPPFLAGS} -DHAVE_LINUX_VERSION_H"]) + AC_MSG_CHECKING([for Linux epoll(7) interface]) + AC_CACHE_VAL([ax_cv_have_epoll], [dnl + AC_LINK_IFELSE([dnl + AC_LANG_PROGRAM([dnl +#include <sys/epoll.h> +#ifdef HAVE_LINUX_VERSION_H +# include <linux/version.h> +# if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,45) +# error linux kernel version is too old to have epoll +# endif +#endif +], [dnl +int fd, rc; +struct epoll_event ev; +fd = epoll_create(128); +rc = epoll_wait(fd, &ev, 1, 0);])], + [ax_cv_have_epoll=yes], + [ax_cv_have_epoll=no])]) + CPPFLAGS="${ax_have_epoll_cppflags}" + AS_IF([test "${ax_cv_have_epoll}" = "yes"], + [AC_MSG_RESULT([yes]) +$1],[AC_MSG_RESULT([no]) +$2]) +])dnl + +AC_DEFUN([AX_HAVE_EPOLL_PWAIT], [dnl + ax_have_epoll_cppflags="${CPPFLAGS}" + AC_CHECK_HEADER([linux/version.h], + [CPPFLAGS="${CPPFLAGS} -DHAVE_LINUX_VERSION_H"]) + AC_MSG_CHECKING([for Linux epoll(7) interface with signals extension]) + AC_CACHE_VAL([ax_cv_have_epoll_pwait], [dnl + AC_LINK_IFELSE([dnl + AC_LANG_PROGRAM([dnl +#ifdef HAVE_LINUX_VERSION_H +# include <linux/version.h> +# if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19) +# error linux kernel version is too old to have epoll_pwait +# endif +#endif +#include <sys/epoll.h> +#include <signal.h> +], [dnl +int fd, rc; +struct epoll_event ev; +fd = epoll_create(128); +rc = epoll_wait(fd, &ev, 1, 0); +rc = epoll_pwait(fd, &ev, 1, 0, (sigset_t const *)(0));])], + [ax_cv_have_epoll_pwait=yes], + [ax_cv_have_epoll_pwait=no])]) + CPPFLAGS="${ax_have_epoll_cppflags}" + AS_IF([test "${ax_cv_have_epoll_pwait}" = "yes"], + [AC_MSG_RESULT([yes]) +$1],[AC_MSG_RESULT([no]) +$2]) +])dnl diff --git a/protocols/Tox/toxcore/m4/ax_pthread.m4 b/protocols/Tox/toxcore/m4/ax_pthread.m4 new file mode 100644 index 0000000000..6d400ed4e8 --- /dev/null +++ b/protocols/Tox/toxcore/m4/ax_pthread.m4 @@ -0,0 +1,317 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC to any special C compiler that is needed for +# multi-threaded programs (defaults to the value of CC otherwise). (This +# is necessary on AIX to use the special cc_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also link it with them as well. e.g. you should link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threads programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name +# (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the +# PTHREAD_PRIO_INHERIT symbol is defined when compiling with +# PTHREAD_CFLAGS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# Updated for Autoconf 2.68 by Daniel Richard G. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu> +# Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG> +# +# 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 <http://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 20 + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_LANG_PUSH([C]) +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on True64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) + AC_TRY_LINK_FUNC(pthread_join, ax_pthread_ok=yes) + AC_MSG_RESULT($ax_pthread_ok) + if test x"$ax_pthread_ok" = xno; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) +# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) +# -pthreads: Solaris/gcc +# -mthreads: Mingw32/gcc, Lynx/gcc +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads too; +# also defines -D_REENTRANT) +# ... -mt is also the pthreads flag for HP/aCC +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case ${host_os} in + solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (We need to link with -pthreads/-mt/ + # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather + # a function called by this macro, so we could check for that, but + # who knows whether they'll stub that too in a future libc.) So, + # we'll just look for -pthreads and -lpthread first: + + ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" + ;; + + darwin*) + ax_pthread_flags="-pthread $ax_pthread_flags" + ;; +esac + +if test x"$ax_pthread_ok" = xno; then +for flag in $ax_pthread_flags; do + + case $flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $flag]) + PTHREAD_CFLAGS="$flag" + ;; + + pthread-config) + AC_CHECK_PROG(ax_pthread_config, pthread-config, yes, no) + if test x"$ax_pthread_config" = xno; then continue; fi + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$flag]) + PTHREAD_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h> + static void routine(void *a) { a = 0; } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + AC_MSG_RESULT($ax_pthread_ok) + if test "x$ax_pthread_ok" = xyes; then + break; + fi + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$ax_pthread_ok" = xyes; then + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_MSG_CHECKING([for joinable pthread attribute]) + attr_name=unknown + for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>], + [int attr = $attr; return attr /* ; */])], + [attr_name=$attr; break], + []) + done + AC_MSG_RESULT($attr_name) + if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then + AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + fi + + AC_MSG_CHECKING([if more special flags are required for pthreads]) + flag=no + case ${host_os} in + aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";; + osf* | hpux*) flag="-D_REENTRANT";; + solaris*) + if test "$GCC" = "yes"; then + flag="-D_REENTRANT" + else + flag="-mt -D_REENTRANT" + fi + ;; + esac + AC_MSG_RESULT(${flag}) + if test "x$flag" != xno; then + PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" + fi + + AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], + ax_cv_PTHREAD_PRIO_INHERIT, [ + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([[#include <pthread.h>]], [[int i = PTHREAD_PRIO_INHERIT;]])], + [ax_cv_PTHREAD_PRIO_INHERIT=yes], + [ax_cv_PTHREAD_PRIO_INHERIT=no]) + ]) + AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"], + AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], 1, [Have PTHREAD_PRIO_INHERIT.])) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + # More AIX lossage: compile with *_r variant + if test "x$GCC" != xyes; then + case $host_os in + aix*) + AS_CASE(["x/$CC"], + [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], + [#handle absolute path differently from PATH based program lookup + AS_CASE(["x$CC"], + [x/*], + [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], + [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) + ;; + esac + fi +fi + +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" + +AC_SUBST(PTHREAD_LIBS) +AC_SUBST(PTHREAD_CFLAGS) +AC_SUBST(PTHREAD_CC) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test x"$ax_pthread_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_POP +])dnl AX_PTHREAD diff --git a/protocols/Tox/toxcore/m4/pkg.m4 b/protocols/Tox/toxcore/m4/pkg.m4 new file mode 100644 index 0000000000..260e1fb922 --- /dev/null +++ b/protocols/Tox/toxcore/m4/pkg.m4 @@ -0,0 +1,199 @@ +# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +# serial 1 (pkg-config-0.24) +# +# Copyright © 2004 Scott James Remnant <scott@netsplit.com>. +# +# 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 <http://pkg-config.freedesktop.org/>.])[]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 diff --git a/protocols/Tox/toxcore/other/DHT_bootstrap.c b/protocols/Tox/toxcore/other/DHT_bootstrap.c new file mode 100644 index 0000000000..462360c361 --- /dev/null +++ b/protocols/Tox/toxcore/other/DHT_bootstrap.c @@ -0,0 +1,202 @@ + +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#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.c" + +#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 <unistd.h> +#include <arpa/inet.h> +#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 (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_public_key, 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; +} diff --git a/protocols/Tox/toxcore/other/DHTnodes b/protocols/Tox/toxcore/other/DHTnodes new file mode 100644 index 0000000000..0abdbbd95d --- /dev/null +++ b/protocols/Tox/toxcore/other/DHTnodes @@ -0,0 +1,3 @@ +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 http://wiki.tox.im/nodes diff --git a/protocols/Tox/toxcore/other/Makefile.inc b/protocols/Tox/toxcore/other/Makefile.inc new file mode 100644 index 0000000000..368a32f2d5 --- /dev/null +++ b/protocols/Tox/toxcore/other/Makefile.inc @@ -0,0 +1,20 @@ +bin_PROGRAMS += DHT_bootstrap + +DHT_bootstrap_SOURCES = ../other/DHT_bootstrap.c \ + ../toxcore/DHT.h \ + ../toxcore/friend_requests.h + +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 diff --git a/protocols/Tox/toxcore/other/bootstrap_daemon/Makefile.inc b/protocols/Tox/toxcore/other/bootstrap_daemon/Makefile.inc new file mode 100644 index 0000000000..0bc02ef93a --- /dev/null +++ b/protocols/Tox/toxcore/other/bootstrap_daemon/Makefile.inc @@ -0,0 +1,27 @@ +if BUILD_DHT_BOOTSTRAP_DAEMON + +bin_PROGRAMS += tox_bootstrap_daemon + +tox_bootstrap_daemon_SOURCES = \ + ../other/bootstrap_daemon/tox_bootstrap_daemon.c + +tox_bootstrap_daemon_CFLAGS = \ + -I$(top_srcdir)/other/bootstrap_daemon \ + $(LIBSODIUM_CFLAGS) \ + $(NACL_CFLAGS) \ + $(LIBCONFIG_CFLAGS) + +tox_bootstrap_daemon_LDADD = \ + $(LIBSODIUM_LDFLAGS) \ + $(NACL_LDFLAGS) \ + libtoxcore.la \ + $(LIBCONFIG_LIBS) \ + $(LIBSODIUM_LIBS) \ + $(NACL_LIBS) + +endif + +EXTRA_DIST += \ + $(top_srcdir)/other/bootstrap_daemon/conf \ + $(top_srcdir)/other/bootstrap_daemon/tox_bootstrap_daemon.sh + diff --git a/protocols/Tox/toxcore/other/bootstrap_daemon/README.md b/protocols/Tox/toxcore/other/bootstrap_daemon/README.md new file mode 100644 index 0000000000..53a25cdbfb --- /dev/null +++ b/protocols/Tox/toxcore/other/bootstrap_daemon/README.md @@ -0,0 +1,62 @@ +##Instructions for Debian + +The following commands are to be executed as root: + +1. In `tox_bootstrap_daemon.sh` file change: + - `CFG` to where your config file (`conf`) will be; read rights required + - `DAEMON` to point to the executable + - `PIDFILE` to point to a pid file daemon would have rights to create + +2. Go over everything in `conf`. Make sure `pid_file_path` matches `PIDFILE` from `tox_bootstrap_daemon.sh` + +3. Execute: +``` +mv tox_bootstrap_daemon.sh /etc/init.d/tox_bootstrap_daemon +``` +*(note that we removed `.sh` ending)* + +4. Give the right permissions to this file: +``` +chmod 755 /etc/init.d/tox_bootstrap_daemon +``` + +5. Execute: +``` +update-rc.d tox_bootstrap_daemon defaults +``` + +6. Start the service: +``` +service tox_bootstrap_daemon start +``` + +7. Verify that the service is running: +``` +service tox_bootstrap_daemon status +``` + +-- + +You can see daemon's log with +``` +grep "tox_bootstrap_daemon" /var/log/syslog +``` + +**Note that system log is where you find your public key** + +-- + +###Troubleshooting: + +1. Check the log for errors with +``` +grep "tox_bootstrap_daemon" /var/log/syslog +``` + +2. Check that paths in the beginning of `/etc/init.d/tox_bootstrap_daemon` are valid + +3. Make sure that `PIDFILE` from `/etc/init.d/tox_bootstrap_daemon` matches with the `pid_file_path` from `conf` + +4. Make sure you have write permission to keys and pid files + +5. Make sure you have read permission for config file
\ No newline at end of file diff --git a/protocols/Tox/toxcore/other/bootstrap_daemon/conf b/protocols/Tox/toxcore/other/bootstrap_daemon/conf new file mode 100644 index 0000000000..c05beff1a7 --- /dev/null +++ b/protocols/Tox/toxcore/other/bootstrap_daemon/conf @@ -0,0 +1,54 @@ +// ProjectTox dht bootstrap node daemon configuration file. + +// Listening port. +port = 33445 + +// A key file is like a password, so keep it where no one can read it. +// The daemon should have permission to read/write to it. +// Remember to replace the provided example with your own path. +keys_file_path = "/home/tom/.tox_bootstrap_daemon/.tox_bootstrap_daemon.keys" + +// The PID file written to by daemon. +// Make sure that the user who runs the daemon has permissions to write to the +// PID file. +// Remember to replace the provided example with your own path. +pid_file_path = "/home/tom/.tox_bootstrap_daemon/.tox_bootstrap_daemon.pid" + +// Enable IPv6. +enable_ipv6 = false + +// Automatically bootstrap with nodes on local area network. +enable_lan_discovery = true + +enable_tcp_relay = true + +// Tox uses 443, 3389 and 33445 ports by default, so it's highly recommended to keep +// them. +tcp_relay_ports = [443, 3389, 33445] + +// It's planned to use message of the day as a convenient method of checking +// whether a node is up or not, though there are other methods of doing that. +enable_motd = true + +motd = "tox_bootstrap_daemon" + +// Any number of nodes the daemon will bootstrap itself from. +// 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. +// You may leave the list empty or remove "bootstrap_nodes" complitely, +// in both cases this will be interpreted as if you don't want to bootstrap +// from anyone. +bootstrap_nodes = ( + { // Node 1 + // Any ipv4 or ipv6, depending on whether `enable_ipv6` is set or not, and + // also any US-ASCII domain name. + address = "198.46.136.167" + port = 33445 + public_key = "728925473812C7AAC482BE7250BCCAD0B8CB9F737BF3D42ABD34459C1768F854" + }, + { // Node 2 + address = "example.org" + port = 33445 + public_key = "8CD5A9BF0A6CE358BA36F7A653F99FA6B258FF756E490F52C1F98CC420F78858" + } +) diff --git a/protocols/Tox/toxcore/other/bootstrap_daemon/tox_bootstrap_daemon.c b/protocols/Tox/toxcore/other/bootstrap_daemon/tox_bootstrap_daemon.c new file mode 100644 index 0000000000..5f8f9f76d8 --- /dev/null +++ b/protocols/Tox/toxcore/other/bootstrap_daemon/tox_bootstrap_daemon.c @@ -0,0 +1,685 @@ +/* tox_bootstrap_daemon.c + * + * Tox DHT bootstrap node daemon. + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +// system provided +#include <arpa/inet.h> +#include <syslog.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +// C +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +// 3rd party +#include <libconfig.h> + +// ./configure +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +// 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.c" +#include "../../testing/misc_tools.c" + + +#define DAEMON_NAME "tox_bootstrap_daemon" +#define DAEMON_VERSION_NUMBER 2014051800UL // yyyymmmddvv format: yyyy year, mm month, dd day, vv version change count for that day + +#define SLEEP_TIME_MILLISECONDS 30 +#define sleep usleep(1000*SLEEP_TIME_MILLISECONDS) + +#define DEFAULT_PID_FILE_PATH ".tox_bootstrap_daemon.pid" +#define DEFAULT_KEYS_FILE_PATH ".tox_bootstrap_daemon.keys" +#define DEFAULT_PORT 33445 +#define DEFAULT_ENABLE_IPV6 0 // 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 + +#define MIN_ALLOWED_PORT 1 +#define MAX_ALLOWED_PORT 65535 + + +// 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) { + 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"); + + 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; +} + +// 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) { + syslog(LOG_WARNING, "No '%s' setting in the configuration file.\n", NAME_TCP_RELAY_PORTS); + syslog(LOG_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 ++) { + syslog(LOG_WARNING, "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) { + syslog(LOG_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 + *tcp_relay_ports = realloc(*tcp_relay_ports, (*tcp_relay_port_count) * sizeof(uint16_t)); + + return; + } + + if (config_setting_is_array(ports_array) == CONFIG_FALSE) { + syslog(LOG_WARNING, "'%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) { + syslog(LOG_WARNING, "'%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 ealier) or if `i` is out of range, which should not be + syslog(LOG_WARNING, "Port #%d: Something went wrong while parsing the port. Stopping reading ports.\n", i); + break; + } + + if (config_setting_is_number(elem) == CONFIG_FALSE) { + syslog(LOG_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) { + syslog(LOG_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 + *tcp_relay_ports = realloc(*tcp_relay_ports, (*tcp_relay_port_count) * sizeof(uint16_t)); +} + +// Gets general config options +// +// Important: you are responsible for freeing `pid_file_path` and `keys_file_path` +// also, iff `tcp_relay_ports_count` > 0, then you are responsible for freeing `tcp_relay_ports` +// and also `motd` iff `enable_motd` is set +// +// returns 1 on success +// 0 on failure, doesn't modify any data pointed by arguments + +int get_general_config(char *cfg_file_path, char **pid_file_path, char **keys_file_path, int *port, int *enable_ipv6, + 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_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) { + syslog(LOG_ERR, "%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) { + syslog(LOG_WARNING, "No '%s' setting in configuration file.\n", NAME_PORT); + syslog(LOG_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) { + syslog(LOG_WARNING, "No '%s' setting in configuration file.\n", NAME_PID_FILE_PATH); + syslog(LOG_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) { + syslog(LOG_WARNING, "No '%s' setting in configuration file.\n", NAME_KEYS_FILE_PATH); + syslog(LOG_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) { + syslog(LOG_WARNING, "No '%s' setting in configuration file.\n", NAME_ENABLE_IPV6); + syslog(LOG_WARNING, "Using default '%s': %s\n", NAME_ENABLE_IPV6, DEFAULT_ENABLE_IPV6 ? "true" : "false"); + *enable_ipv6 = DEFAULT_ENABLE_IPV6; + } + + // Get LAN discovery option + if (config_lookup_bool(&cfg, NAME_ENABLE_LAN_DISCOVERY, enable_lan_discovery) == CONFIG_FALSE) { + syslog(LOG_WARNING, "No '%s' setting in configuration file.\n", NAME_ENABLE_LAN_DISCOVERY); + syslog(LOG_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) { + syslog(LOG_WARNING, "No '%s' setting in configuration file.\n", NAME_ENABLE_TCP_RELAY); + syslog(LOG_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) { + syslog(LOG_WARNING, "No '%s' setting in configuration file.\n", NAME_ENABLE_MOTD); + syslog(LOG_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) { + syslog(LOG_WARNING, "No '%s' setting in configuration file.\n", NAME_MOTD); + syslog(LOG_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); + + syslog(LOG_DEBUG, "Successfully read:\n"); + syslog(LOG_DEBUG, "'%s': %s\n", NAME_PID_FILE_PATH, *pid_file_path); + syslog(LOG_DEBUG, "'%s': %s\n", NAME_KEYS_FILE_PATH, *keys_file_path); + syslog(LOG_DEBUG, "'%s': %d\n", NAME_PORT, *port); + syslog(LOG_DEBUG, "'%s': %s\n", NAME_ENABLE_IPV6, *enable_ipv6 ? "true" : "false"); + syslog(LOG_DEBUG, "'%s': %s\n", NAME_ENABLE_LAN_DISCOVERY, *enable_lan_discovery ? "true" : "false"); + + syslog(LOG_DEBUG, "'%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) { + syslog(LOG_DEBUG, "No TCP ports could be read.\n"); + } else { + syslog(LOG_DEBUG, "Read %d TCP ports:\n", *tcp_relay_port_count); + int i; + + for (i = 0; i < *tcp_relay_port_count; i ++) { + syslog(LOG_DEBUG, "Port #%d: %u\n", i, (*tcp_relay_ports)[i]); + } + } + } + + syslog(LOG_DEBUG, "'%s': %s\n", NAME_ENABLE_MOTD, *enable_motd ? "true" : "false"); + + if (*enable_motd) { + syslog(LOG_DEBUG, "'%s': %s\n", NAME_MOTD, *motd); + } + + return 1; +} + +// Bootstraps nodes listed in the config file +// +// returns 1 on success, some or no bootstrap nodes were added +// 0 on failure, a error accured while parsing config file + +int bootstrap_from_config(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) { + syslog(LOG_ERR, "%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) { + syslog(LOG_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) { + syslog(LOG_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) { + syslog(LOG_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) { + syslog(LOG_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) { + syslog(LOG_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) { + syslog(LOG_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) { + syslog(LOG_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) { + syslog(LOG_WARNING, "Bootstrap node #%d: Invalid '%s': %s. Skipping the node.\n", i, NAME_ADDRESS, bs_address); + goto next; + } + + syslog(LOG_DEBUG, "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; +} + +// Prints public key + +void print_public_key(uint8_t *public_key) +{ + char buffer[2 * crypto_box_PUBLICKEYBYTES + 1]; + int index = 0; + + int i; + + for (i = 0; i < crypto_box_PUBLICKEYBYTES; i++) { + index += sprintf(buffer + index, "%02hhX", public_key[i]); + } + + syslog(LOG_INFO, "Public Key: %s\n", buffer); + + return; +} + +int main(int argc, char *argv[]) +{ + openlog(DAEMON_NAME, LOG_NOWAIT | LOG_PID, LOG_DAEMON); + + syslog(LOG_INFO, "Running \"%s\" version %lu.\n", DAEMON_NAME, DAEMON_VERSION_NUMBER); + + if (argc < 2) { + syslog(LOG_ERR, "Please specify a path to a configuration file as the first argument. Exiting.\n"); + return 1; + } + + char *cfg_file_path = argv[1]; + char *pid_file_path, *keys_file_path; + int port; + int enable_ipv6; + 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_lan_discovery, + &enable_tcp_relay, &tcp_relay_ports, &tcp_relay_port_count, &enable_motd, &motd)) { + syslog(LOG_DEBUG, "General config read successfully\n"); + } else { + syslog(LOG_ERR, "Couldn't read config file: %s. Exiting.\n", cfg_file_path); + return 1; + } + + if (port < MIN_ALLOWED_PORT || port > MAX_ALLOWED_PORT) { + syslog(LOG_ERR, "Invalid port: %d, should be in [%d, %d]. Exiting.\n", port, MIN_ALLOWED_PORT, MAX_ALLOWED_PORT); + return 1; + } + + // Check if the PID file exists + FILE *pid_file; + + if (pid_file = fopen(pid_file_path, "r")) { + syslog(LOG_ERR, "Another instance of the daemon is already running, PID file %s exists.\n", pid_file_path); + fclose(pid_file); + } + + IP ip; + ip_init(&ip, enable_ipv6); + + DHT *dht = new_DHT(new_networking(ip, port)); + + if (dht == NULL) { + syslog(LOG_ERR, "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)) { + syslog(LOG_ERR, "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) { + syslog(LOG_DEBUG, "Set MOTD successfully.\n"); + } else { + syslog(LOG_ERR, "Couldn't set MOTD: %s. Exiting.\n", motd); + return 1; + } + + free(motd); + } + + if (manage_keys(dht, keys_file_path)) { + syslog(LOG_DEBUG, "Keys are managed successfully.\n"); + } else { + syslog(LOG_ERR, "Couldn't read/write: %s. Exiting.\n", keys_file_path); + return 1; + } + + TCP_Server *tcp_server = NULL; + + if (enable_tcp_relay) { + if (tcp_relay_port_count == 0) { + syslog(LOG_ERR, "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_public_key, + dht->self_secret_key, onion); + + // tcp_relay_port_count != 0 at this point + free(tcp_relay_ports); + + if (tcp_server != NULL) { + syslog(LOG_DEBUG, "Initialized Tox TCP server successfully.\n"); + } else { + syslog(LOG_ERR, "Couldn't initialize Tox TCP server. Exiting.\n"); + return 1; + } + } + + if (bootstrap_from_config(cfg_file_path, dht, enable_ipv6)) { + syslog(LOG_DEBUG, "List of bootstrap nodes read successfully.\n"); + } else { + syslog(LOG_ERR, "Couldn't read list of bootstrap nodes in %s. Exiting.\n", cfg_file_path); + return 1; + } + + print_public_key(dht->self_public_key); + + // Write the PID file + FILE *pidf = fopen(pid_file_path, "a+"); + + if (pidf == NULL) { + syslog(LOG_ERR, "Couldn't open the PID file for writing: %s. Exiting.\n", pid_file_path); + return 1; + } + + free(pid_file_path); + free(keys_file_path); + + // Fork off from the parent process + pid_t pid = fork(); + + if (pid > 0) { + fprintf(pidf, "%d ", pid); + fclose(pidf); + syslog(LOG_DEBUG, "Forked successfully: PID: %d.\n", pid); + return 0; + } else { + fclose(pidf); + } + + if (pid < 0) { + syslog(LOG_ERR, "Forking failed. Exiting.\n"); + return 1; + } + + // Change the file mode mask + umask(0); + + // Create a new SID for the child process + if (setsid() < 0) { + syslog(LOG_ERR, "SID creation failure. Exiting.\n"); + return 1; + } + + // Change the current working directory + if ((chdir("/")) < 0) { + syslog(LOG_ERR, "Couldn't change working directory to '/'. Exiting.\n"); + return 1; + } + + // Go quiet + close(STDOUT_FILENO); + close(STDIN_FILENO); + close(STDERR_FILENO); + + uint64_t last_LANdiscovery = 0; + uint16_t htons_port = htons(port); + + int waiting_for_dht_connection = 1; + + if (enable_lan_discovery) { + LANdiscovery_init(dht); + syslog(LOG_DEBUG, "Initialized LAN discovery.\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)) { + syslog(LOG_DEBUG, "Connected to other bootstrap node successfully.\n"); + waiting_for_dht_connection = 0; + } + + sleep; + } + + return 1; +} diff --git a/protocols/Tox/toxcore/other/bootstrap_daemon/tox_bootstrap_daemon.sh b/protocols/Tox/toxcore/other/bootstrap_daemon/tox_bootstrap_daemon.sh new file mode 100644 index 0000000000..787498ecdc --- /dev/null +++ b/protocols/Tox/toxcore/other/bootstrap_daemon/tox_bootstrap_daemon.sh @@ -0,0 +1,110 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: tox_bootstrap_daemon +# 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_bootstrap_daemon +# You may want to change USER if you are using it anywhere else +USER=tom +CFG=/home/$USER/.$NAME/conf +DAEMON=/home/$USER/.$NAME/$NAME +DAEMON_ARGS="$CFG" +PIDFILE=/home/$USER/.$NAME/."$NAME".pid +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +#[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# 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() +{ + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ + $DAEMON_ARGS \ + || return 2 + sleep 1 +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --exec $DAEMON + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 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) #|force-reload) + 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 diff --git a/protocols/Tox/toxcore/other/bootstrap_node_packets.c b/protocols/Tox/toxcore/other/bootstrap_node_packets.c new file mode 100644 index 0000000000..e8df3289ef --- /dev/null +++ b/protocols/Tox/toxcore/other/bootstrap_node_packets.c @@ -0,0 +1,65 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#define MAX_MOTD_LENGTH 256 /* I recommend you use a maximum of 96 bytes. The hard maximum is this though. */ + +#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, uint32_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; +} diff --git a/protocols/Tox/toxcore/other/fun/bootstrap_node_info.py b/protocols/Tox/toxcore/other/fun/bootstrap_node_info.py new file mode 100644 index 0000000000..1734933472 --- /dev/null +++ b/protocols/Tox/toxcore/other/fun/bootstrap_node_info.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +""" +Copyright (c) 2014 by nurupo <nurupo.contributions@gmail.com> + +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] + " <ipv4|ipv6> <ip/hostname> <port>") + 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)
\ No newline at end of file diff --git a/protocols/Tox/toxcore/other/fun/cracker.c b/protocols/Tox/toxcore/other/fun/cracker.c new file mode 100644 index 0000000000..843b9359cb --- /dev/null +++ b/protocols/Tox/toxcore/other/fun/cracker.c @@ -0,0 +1,75 @@ +/* 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 <time.h> + +/* NaCl includes*/ +#include <crypto_scalarmult_curve25519.h> +#include <randombytes.h> + +/* Sodium include*/ +//#include <sodium.h> + +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; +} diff --git a/protocols/Tox/toxcore/other/fun/sign.c b/protocols/Tox/toxcore/other/fun/sign.c new file mode 100644 index 0000000000..56a9d1e2ca --- /dev/null +++ b/protocols/Tox/toxcore/other/fun/sign.c @@ -0,0 +1,130 @@ +/* Binary signer/checker using ed25519 + * + * Compile with: + * gcc -o sign sign.c -lsodium + * + * Generate a keypair: + * ./sign g + * + * Sign a file: + * ./sign s PRIVATEKEY file.bin signedfile.bin + * + * Check a file: + * + * ./sign c PUBKEY signedfile.bin + * + * NOTE: The signature is appended to the end of the file. + */ +#include <sodium.h> +#include <string.h> +#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; +} diff --git a/protocols/Tox/toxcore/other/fun/strkey.c b/protocols/Tox/toxcore/other/fun/strkey.c new file mode 100644 index 0000000000..7e5a1e1cfb --- /dev/null +++ b/protocols/Tox/toxcore/other/fun/strkey.c @@ -0,0 +1,137 @@ +/* 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> <string> + * + * 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 <stdio.h> +#include <string.h> + +#include <sodium.h> + +#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 <byte offset> <desired hex string with even number of letters>\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; +} diff --git a/protocols/Tox/toxcore/other/tox.png b/protocols/Tox/toxcore/other/tox.png Binary files differnew file mode 100644 index 0000000000..e3d6869614 --- /dev/null +++ b/protocols/Tox/toxcore/other/tox.png diff --git a/protocols/Tox/toxcore/testing/DHT_test.c b/protocols/Tox/toxcore/testing/DHT_test.c new file mode 100644 index 0000000000..2636ed02b2 --- /dev/null +++ b/protocols/Tox/toxcore/testing/DHT_test.c @@ -0,0 +1,254 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#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 <string.h> + +//Sleep function (x = milliseconds) +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) + +#define c_sleep(x) Sleep(1*x) + +#else +#include <unistd.h> +#include <arpa/inet.h> +#define c_sleep(x) usleep(1000*x) + +#endif + +#define PORT 33445 + +uint8_t zeroes_cid[CLIENT_ID_SIZE]; + +void print_client_id(uint8_t *client_id) +{ + uint32_t j; + + for (j = 0; j < CLIENT_ID_SIZE; j++) { + printf("%02hhX", client_id[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 (memcmp(client->client_id, zeroes_cid, CLIENT_ID_SIZE) == 0) + continue; + + printf("ClientID: "); + print_client_id(client->client_id); + + 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].client_id); + + int friendok = DHT_getfriendip(dht, dht->friends_list[k].client_id, &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 (memcmp(client->client_id, zeroes_cid, CLIENT_ID_SIZE) == 0) + continue; + + printf("ClientID: "); + print_client_id(client->client_id); + + 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 client_id 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); + 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; +} diff --git a/protocols/Tox/toxcore/testing/Makefile.inc b/protocols/Tox/toxcore/testing/Makefile.inc new file mode 100644 index 0000000000..236ab19b17 --- /dev/null +++ b/protocols/Tox/toxcore/testing/Makefile.inc @@ -0,0 +1,96 @@ +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) \ + $(WINSOCK2_LIBS) +endif + +EXTRA_DIST += $(top_srcdir)/testing/misc_tools.c + +endif diff --git a/protocols/Tox/toxcore/testing/Messenger_test.c b/protocols/Tox/toxcore/testing/Messenger_test.c new file mode 100644 index 0000000000..905bcef412 --- /dev/null +++ b/protocols/Tox/toxcore/testing/Messenger_test.c @@ -0,0 +1,207 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#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 <unistd.h> +#include <arpa/inet.h> +#define c_sleep(x) usleep(1000*x) +#define PORT 33445 + +#endif + +void print_message(Messenger *m, int friendnumber, const uint8_t *string, uint16_t length, void *userdata) +{ + printf("Message with length %u received from %u: %s \n", length, friendnumber, string); + m_sendmessage(m, friendnumber, (uint8_t *)"Test1", 6); +} + +/* 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, uint16_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: %u 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); + } + + m = new_messenger(ipv6enabled); + + 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_sendmessage(m, num, (uint8_t *)"Test", 5); + 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); +} diff --git a/protocols/Tox/toxcore/testing/dns3_test.c b/protocols/Tox/toxcore/testing/dns3_test.c new file mode 100644 index 0000000000..7052aae732 --- /dev/null +++ b/protocols/Tox/toxcore/testing/dns3_test.c @@ -0,0 +1,115 @@ + + +#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_FRIEND_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_FRIEND_ADDRESS_SIZE; ++i) { + printf("%02hhX", tox_id[i]); + } + + printf("\n"); + return 0; +} diff --git a/protocols/Tox/toxcore/testing/misc_tools.c b/protocols/Tox/toxcore/testing/misc_tools.c new file mode 100644 index 0000000000..ad6b2bff4a --- /dev/null +++ b/protocols/Tox/toxcore/testing/misc_tools.c @@ -0,0 +1,86 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> + +#ifdef DEBUG +#include <assert.h> +#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; +}; diff --git a/protocols/Tox/toxcore/testing/nTox.c b/protocols/Tox/toxcore/testing/nTox.c new file mode 100644 index 0000000000..971a2571ec --- /dev/null +++ b/protocols/Tox/toxcore/testing/nTox.c @@ -0,0 +1,1325 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) +#define _WIN32_WINNT 0x501 +#include <winsock2.h> +#include <ws2tcpip.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/types.h> +#include <netdb.h> +#endif + +#include <sys/select.h> + +#include "nTox.h" +#include "misc_tools.c" + +#include <stdio.h> +#include <time.h> +#include <locale.h> + +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) +#define c_sleep(x) Sleep(1*x) +#else +#include <unistd.h> +#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_CLIENT_ID_SIZE]; + uint8_t accepted; +} Friend_request; + +Friend_request pending_requests[256]; +uint8_t num_requests = 0; + +#define NUM_FILE_SENDERS 256 +typedef struct { + FILE *file; + uint16_t friendnum; + uint8_t filenumber; + uint8_t nextpiece[1024]; + uint16_t piecelength; +} File_Sender; +File_Sender file_senders[NUM_FILE_SENDERS]; +uint8_t numfilesenders; + +void send_filesenders(Tox *m) +{ + uint32_t i; + + for (i = 0; i < NUM_FILE_SENDERS; ++i) { + if (file_senders[i].file == 0) + continue; + + while (1) { + if (tox_file_send_data(m, file_senders[i].friendnum, file_senders[i].filenumber, file_senders[i].nextpiece, + file_senders[i].piecelength) == -1) + break; + + file_senders[i].piecelength = fread(file_senders[i].nextpiece, 1, tox_file_data_size(m, file_senders[i].friendnum), + file_senders[i].file); + + if (file_senders[i].piecelength == 0) { + fclose(file_senders[i].file); + file_senders[i].file = 0; + tox_file_send_control(m, file_senders[i].friendnum, 0, file_senders[i].filenumber, 3, 0, 0); + char msg[512]; + sprintf(msg, "[t] %u file transfer: %u completed", file_senders[i].friendnum, file_senders[i].filenumber); + new_lines(msg); + break; + } + } + } +} +int add_filesender(Tox *m, uint16_t friendnum, char *filename) +{ + FILE *tempfile = fopen(filename, "r"); + + if (tempfile == 0) + return -1; + + fseek(tempfile, 0, SEEK_END); + uint64_t filesize = ftell(tempfile); + fseek(tempfile, 0, SEEK_SET); + int filenum = tox_new_file_sender(m, friendnum, filesize, (uint8_t *)filename, strlen(filename) + 1); + + if (filenum == -1) + return -1; + + file_senders[numfilesenders].file = tempfile; + file_senders[numfilesenders].piecelength = fread(file_senders[numfilesenders].nextpiece, 1, tox_file_data_size(m, + file_senders[numfilesenders].friendnum), + file_senders[numfilesenders].file); + file_senders[numfilesenders].friendnum = friendnum; + file_senders[numfilesenders].filenumber = filenum; + ++numfilesenders; + return filenum; +} + + + +#define FRADDR_TOSTR_CHUNK_LEN 8 +#define FRADDR_TOSTR_BUFSIZE (TOX_FRIEND_ADDRESS_SIZE * 2 + TOX_FRIEND_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_FRIEND_ADDRESS_SIZE; i++) { + sprintf(&id_str[2 * i + delta], "%02hhX", id_bin[i]); + + if ((i + 1) == TOX_CLIENT_ID_SIZE) + pos_extra = 2 * (i + 1) + delta; + + if (i >= TOX_CLIENT_ID_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_FRIEND_ADDRESS_SIZE]; + tox_get_address(m, address); + fraddr_to_str(address, data + offset); +} + +int getfriendname_terminated(Tox *m, int friendnum, char *namebuf) +{ + int res = tox_get_name(m, friendnum, (uint8_t *)namebuf); + + 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_FRIEND_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_FRIEND_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_get_client_id(m, i, fraddr_bin)) + 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); + int num = tox_add_friend(m, bin_string, (uint8_t *)"Install Gentoo", sizeof("Install Gentoo")); + free(bin_string); + char numstring[100]; + + switch (num) { + case TOX_FAERR_TOOLONG: + sprintf(numstring, "[i] Message is too long."); + break; + + case TOX_FAERR_NOMESSAGE: + sprintf(numstring, "[i] Please add a message to your request."); + break; + + case TOX_FAERR_OWNKEY: + sprintf(numstring, "[i] That appears to be your own ID."); + break; + + case TOX_FAERR_ALREADYSENT: + sprintf(numstring, "[i] Friend request already sent."); + break; + + case TOX_FAERR_UNKNOWN: + sprintf(numstring, "[i] Undefined error when adding friend."); + break; + + default: + if (num >= 0) { + sprintf(numstring, "[i] Added friend as %d.", num); + save_data(m); + } else + sprintf(numstring, "[i] Unknown error %i.", num); + + break; + } + + new_lines(numstring); + } else if (inpt_command == 'd') { + tox_do(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_send_message(m, num, (uint8_t *) *posi + 1, strlen(*posi + 1)) < 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_set_name(m, name, i - 2); + 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_STATUSMESSAGE_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_set_status_message(m, status, strlen((char *)status)); + 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 { + int num = tox_add_friend_norequest(m, pending_requests[numf].id); + + if (num != -1) { + 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_del_friend(m, numf); + + if (res == 0) + 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_send_message(m, friendnumber, (uint8_t *)line, strlen(line)); + + 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, uint16_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_CLIENT_ID_SIZE); + pending_requests[num_requests].accepted = 0; + ++num_requests; + do_refresh(); +} + +void print_message(Tox *m, int friendnumber, const uint8_t *string, uint16_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, int friendnumber, const uint8_t *string, uint16_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, int friendnumber, const uint8_t *string, uint16_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 int load_data(Tox *m) +{ + 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; + } + + tox_load(m, data, size); + + if (fclose(data_file) < 0) { + perror("[!] fclose failed"); + /* we got it open and the expected data read... let it be ok */ + /* return 0; */ + } + + return 1; + } + + return 0; +} + +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_size(m); + uint8_t data[size]; + tox_save(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 load_data_or_init(Tox *m, char *path) +{ + data_file_name = path; + + if (load_data(m)) + return 1; + + 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, const uint8_t *group_public_key, void *userdata) +{ + char msg[256]; + sprintf(msg, "[i] received group chat invite from: %u, auto accepting and joining. group number: %u", friendnumber, + tox_join_groupchat(m, friendnumber, group_public_key)); + 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 *m, int friendnumber, uint8_t filenumber, uint64_t filesize, const uint8_t *filename, + uint16_t filename_length, void *userdata) +{ + char msg[512]; + sprintf(msg, "[t] %u is sending us: %s of size %llu", friendnumber, filename, (long long unsigned int)filesize); + new_lines(msg); + + if (tox_file_send_control(m, friendnumber, 1, filenumber, 0, 0, 0) == 0) { + sprintf(msg, "Accepted file transfer. (saving file as: %u.%u.bin)", friendnumber, filenumber); + new_lines(msg); + } else + new_lines("Could not accept file transfer."); +} + +void file_print_control(Tox *m, int friendnumber, uint8_t send_recieve, uint8_t filenumber, uint8_t control_type, + const uint8_t *data, uint16_t length, void *userdata) +{ + char msg[512] = {0}; + + if (control_type == 0) + sprintf(msg, "[t] %u accepted file transfer: %u", friendnumber, filenumber); + else if (control_type == 3) + sprintf(msg, "[t] %u file transfer: %u completed", friendnumber, filenumber); + else + sprintf(msg, "[t] control %u received", control_type); + + new_lines(msg); +} + +void write_file(Tox *m, int friendnumber, uint8_t filenumber, const uint8_t *data, uint16_t length, void *userdata) +{ + char filename[256]; + sprintf(filename, "%u.%u.bin", friendnumber, filenumber); + FILE *pFile = fopen(filename, "a"); + + if (tox_file_data_remaining(m, friendnumber, filenumber, 1) == 0) { + //file_control(m, friendnumber, 1, filenumber, 3, 0, 0); + char msg[512]; + sprintf(msg, "[t] %u file transfer: %u completed", friendnumber, filenumber); + new_lines(msg); + } + + if (fwrite(data, length, 1, pFile) != 1) + new_lines("Error writing to file"); + + fclose(pFile); +} + +char timeout_getch(Tox *m) +{ + char c; + int slpval = tox_do_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 = TOX_ENABLE_IPV6_DEFAULT; /* 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]; + + m = tox_new(ipv6enabled); + + if ( !m ) { + fputs("Failed to allocate Messenger datastructure", stderr); + exit(0); + } + + load_data_or_init(m, filename); + + tox_callback_friend_request(m, print_request, NULL); + tox_callback_friend_message(m, print_message, NULL); + tox_callback_name_change(m, print_nickchange, NULL); + tox_callback_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_data(m, write_file, NULL); + tox_callback_file_control(m, file_print_control, NULL); + tox_callback_file_send_request(m, file_request_accept, NULL); + tox_callback_group_namelist_change(m, print_groupnamelistchange, 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 = htons(atoi(argv[argvoffset + 2])); + unsigned char *binary_string = hex_string_to_bin(argv[argvoffset + 3]); + int res = tox_bootstrap_from_address(m, 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]); + endwin(); + exit(1); + } + + nodelay(stdscr, TRUE); + + new_lines("[i] change username with /n"); + uint8_t name[TOX_MAX_NAME_LENGTH + 1]; + uint16_t namelen = tox_get_self_name(m, name); + 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_isconnected(m)) { + new_lines("[i] connected to DHT"); + on = 1; + } else { + time_t timestamp1 = time(NULL); + + if (timestamp0 + 10 < timestamp1) { + timestamp0 = timestamp1; + tox_bootstrap_from_address(m, argv[argvoffset + 1], ipv6enabled, port, binary_string); + } + } + } + + + + send_filesenders(m); + tox_do(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 == ' ') { + strcpy(input_line, appender(input_line, (char) c)); + } + } + + tox_kill(m); + endwin(); + return 0; +} diff --git a/protocols/Tox/toxcore/testing/nTox.h b/protocols/Tox/toxcore/testing/nTox.h new file mode 100644 index 0000000000..44def1420e --- /dev/null +++ b/protocols/Tox/toxcore/testing/nTox.h @@ -0,0 +1,42 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifndef NTOX_H +#define NTOX_H + +/* + * module actually exports nothing for the outside + */ + +#include <ctype.h> +#include <curses.h> + +#include "../toxcore/tox.h" + +#define STRING_LENGTH 256 +#define HISTORY 50 + +void new_lines(char *line); +void do_refresh(); + +#endif diff --git a/protocols/Tox/toxcore/testing/tox_sync.c b/protocols/Tox/toxcore/testing/tox_sync.c new file mode 100644 index 0000000000..523f2c564a --- /dev/null +++ b/protocols/Tox/toxcore/testing/tox_sync.c @@ -0,0 +1,299 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "../toxcore/tox.h" +#include "misc_tools.c" + +#include <unistd.h> +#define c_sleep(x) usleep(1000*x) + +#include <dirent.h> +#include <stdio.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#define NUM_FILE_SENDERS 256 +typedef struct { + FILE *file; + uint16_t friendnum; + uint8_t filenumber; + uint8_t nextpiece[1400]; + uint16_t piecelength; +} File_t; +File_t file_senders[NUM_FILE_SENDERS]; +File_t file_recv[NUM_FILE_SENDERS]; +uint8_t numfilesenders; + +void send_filesenders(Tox *m) +{ + uint32_t i; + + for (i = 0; i < NUM_FILE_SENDERS; ++i) { + if (file_senders[i].file == 0) + continue; + + while (1) { + if (tox_file_send_data(m, file_senders[i].friendnum, file_senders[i].filenumber, file_senders[i].nextpiece, + file_senders[i].piecelength) != 0) + break; + + file_senders[i].piecelength = fread(file_senders[i].nextpiece, 1, tox_file_data_size(m, file_senders[i].friendnum), + file_senders[i].file); + + if (file_senders[i].piecelength == 0) { + fclose(file_senders[i].file); + file_senders[i].file = 0; + + printf("[t] %u file transfer: %u completed %i\n", file_senders[i].friendnum, file_senders[i].filenumber, + tox_file_send_control(m, file_senders[i].friendnum, 0, file_senders[i].filenumber, TOX_FILECONTROL_FINISHED, 0, 0)); + break; + } + } + } +} +int 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); + int filenum = tox_new_file_sender(m, friendnum, filesize, (uint8_t *)filename, strlen(filename) + 1); + + if (filenum == -1) + return -1; + + file_senders[numfilesenders].file = tempfile; + file_senders[numfilesenders].piecelength = fread(file_senders[numfilesenders].nextpiece, 1, tox_file_data_size(m, + file_senders[numfilesenders].friendnum), + file_senders[numfilesenders].file); + file_senders[numfilesenders].friendnum = friendnum; + file_senders[numfilesenders].filenumber = filenum; + ++numfilesenders; + return filenum; +} + +void kill_filesender(Tox *m, uint8_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 *m, int friendnumber, uint8_t filenumber, uint64_t filesize, const uint8_t *filename, + uint16_t filename_length, void *userdata) +{ + 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_send_control(m, friendnumber, 1, filenumber, TOX_FILECONTROL_KILL, 0, 0); + return; + } + + file_recv[filenumber].file = fopen(fullpath, "wb"); + + if (file_recv[filenumber].file == 0) { + tox_file_send_control(m, friendnumber, 1, filenumber, TOX_FILECONTROL_KILL, 0, 0); + return; + } + + if (tox_file_send_control(m, friendnumber, 1, filenumber, TOX_FILECONTROL_ACCEPT, 0, 0) == 0) { + printf("Accepted file transfer. (file: %s)\n", fullpath); + } + +} + +void file_print_control(Tox *m, int friendnumber, uint8_t recieve_send, uint8_t filenumber, uint8_t control_type, + const uint8_t *data, + uint16_t length, void *userdata) +{ + if (recieve_send == 1 && (control_type == TOX_FILECONTROL_KILL || control_type == TOX_FILECONTROL_FINISHED)) { + kill_filesender(m, filenumber); + return; + } + + if (recieve_send == 0 && (control_type == TOX_FILECONTROL_KILL || control_type == TOX_FILECONTROL_FINISHED)) { + fclose(file_recv[filenumber].file); + printf("File closed\n"); + file_recv[filenumber].file = 0; + return; + } +} + +void write_file(Tox *m, int friendnumber, uint8_t filenumber, const uint8_t *data, uint16_t length, void *userdata) +{ + if (file_recv[filenumber].file != 0) + if (fwrite(data, length, 1, file_recv[filenumber].file) != 1) + printf("Error writing data\n"); +} + +void print_online(Tox *tox, int friendnumber, uint8_t status, void *userdata) +{ + if (status == 1) + printf("\nOther went online.\n"); + else + printf("\nOther went offline.\n"); +} + +int main(int argc, char *argv[]) +{ + 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 + 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(ipv6enabled); + tox_callback_file_data(tox, write_file, NULL); + tox_callback_file_control(tox, file_print_control, NULL); + tox_callback_file_send_request(tox, file_request_accept, NULL); + tox_callback_connection_status(tox, print_online, NULL); + + uint16_t port = htons(atoi(argv[argvoffset + 2])); + unsigned char *binary_string = hex_string_to_bin(argv[argvoffset + 3]); + int res = tox_bootstrap_from_address(tox, 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]); + exit(1); + } + + uint8_t address[TOX_FRIEND_ADDRESS_SIZE]; + tox_get_address(tox, address); + uint32_t i; + + for (i = 0; i < TOX_FRIEND_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); + int num = tox_add_friend(tox, bin_id, (uint8_t *)"Install Gentoo", sizeof("Install Gentoo")); + free(bin_id); + + if (num < 0) { + 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_isconnected(tox) && notconnected) { + printf("\nDHT connected.\n"); + notconnected = 0; + } + + if (not_sending() && tox_get_friend_connection_status(tox, num)) { + 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; + } + } + + send_filesenders(tox); + tox_do(tox); + c_sleep(1); + } + + return 0; +} diff --git a/protocols/Tox/toxcore/tools/README b/protocols/Tox/toxcore/tools/README new file mode 100644 index 0000000000..26802ec73d --- /dev/null +++ b/protocols/Tox/toxcore/tools/README @@ -0,0 +1,11 @@ +This directory can house various tools and utilities. + +astylerc + - This file can be used in the precommit hook to try its best + at making the code conform to the coding style document. + +pre-commit (*NIX only at the moment) + - Lints your file in adherence to the coding style doucment as + best as possible in terms of spacing, indenting, etc. + - Requires you to have astyle installed. + - To use, copy this file to ProjectTox-Core/.git/hooks diff --git a/protocols/Tox/toxcore/tools/astylerc b/protocols/Tox/toxcore/tools/astylerc new file mode 100644 index 0000000000..dd738b7406 --- /dev/null +++ b/protocols/Tox/toxcore/tools/astylerc @@ -0,0 +1,11 @@ +--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
\ No newline at end of file diff --git a/protocols/Tox/toxcore/tools/pre-commit b/protocols/Tox/toxcore/tools/pre-commit new file mode 100644 index 0000000000..8f91779df7 --- /dev/null +++ b/protocols/Tox/toxcore/tools/pre-commit @@ -0,0 +1,17 @@ +#!/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
\ No newline at end of file diff --git a/protocols/Tox/toxcore/toxav/Makefile.inc b/protocols/Tox/toxcore/toxav/Makefile.inc new file mode 100644 index 0000000000..de8ef8ff38 --- /dev/null +++ b/protocols/Tox/toxcore/toxav/Makefile.inc @@ -0,0 +1,36 @@ +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/codec.h \ + ../toxav/codec.c \ + ../toxav/toxav.h \ + ../toxav/toxav.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
\ No newline at end of file diff --git a/protocols/Tox/toxcore/toxav/codec.c b/protocols/Tox/toxcore/toxav/codec.c new file mode 100644 index 0000000000..10dc4f53ff --- /dev/null +++ b/protocols/Tox/toxcore/toxav/codec.c @@ -0,0 +1,357 @@ +/** codec.c + * + * Audio and video codec intitialization, encoding/decoding and playback + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "../toxcore/logger.h" + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <assert.h> + +#include "rtp.h" +#include "codec.h" + +JitterBuffer *create_queue(unsigned int capacity) +{ + unsigned int size = 1; + + while (size <= capacity) { + size *= 2; + } + + JitterBuffer *q; + + if ( !(q = calloc(sizeof(JitterBuffer), 1)) ) return NULL; + + if (!(q->queue = calloc(sizeof(RTPMessage *), size))) { + free(q); + return NULL; + } + + q->size = size; + q->capacity = capacity; + return q; +} + +static void clear_queue(JitterBuffer *q) +{ + for (; q->bottom != q->top; ++q->bottom) { + if (q->queue[q->bottom % q->size]) { + rtp_free_msg(NULL, q->queue[q->bottom % q->size]); + q->queue[q->bottom % q->size] = NULL; + } + } +} + +void terminate_queue(JitterBuffer *q) +{ + if (!q) return; + + clear_queue(q); + free(q->queue); + free(q); +} + +void queue(JitterBuffer *q, RTPMessage *pk) +{ + uint16_t sequnum = pk->header->sequnum; + + unsigned int num = sequnum % q->size; + + if ((uint32_t)(sequnum - q->bottom) > q->size) { + clear_queue(q); + q->bottom = sequnum; + q->queue[num] = pk; + q->top = sequnum + 1; + return; + } + + if (q->queue[num]) + return; + + q->queue[num] = pk; + + if ((sequnum - q->bottom) >= (q->top - q->bottom)) + q->top = sequnum + 1; +} + +/* success is 0 when there is nothing to dequeue, 1 when there's a good packet, 2 when there's a lost packet */ +RTPMessage *dequeue(JitterBuffer *q, int *success) +{ + if (q->top == q->bottom) { + *success = 0; + return NULL; + } + + unsigned int num = q->bottom % q->size; + + if (q->queue[num]) { + 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; +} + + +int init_video_decoder(CodecState *cs) +{ + int rc = vpx_codec_dec_init_ver(&cs->v_decoder, VIDEO_CODEC_DECODER_INTERFACE, NULL, 0, VPX_DECODER_ABI_VERSION); + + if ( rc != VPX_CODEC_OK) { + LOGGER_ERROR("Init video_decoder failed: %s", vpx_codec_err_to_string(rc)); + return -1; + } + + return 0; +} + +int init_audio_decoder(CodecState *cs, uint32_t audio_channels) +{ + int rc; + cs->audio_decoder = opus_decoder_create(cs->audio_sample_rate, audio_channels, &rc ); + + if ( rc != OPUS_OK ) { + LOGGER_ERROR("Error while starting audio decoder: %s", opus_strerror(rc)); + return -1; + } + + cs->audio_decoder_channels = audio_channels; + return 0; +} + +int reconfigure_video_encoder_resolution(CodecState *cs, uint16_t width, uint16_t height) +{ + vpx_codec_enc_cfg_t cfg = *cs->v_encoder.config.enc; + + if (cfg.g_w == width && cfg.g_h == height) + return 0; + + if (width * height > cs->max_width * cs->max_height) + return -1; + + LOGGER_DEBUG("New video resolution: %u %u", width, height); + cfg.g_w = width; + cfg.g_h = height; + int rc = vpx_codec_enc_config_set(&cs->v_encoder, &cfg); + + if ( rc != VPX_CODEC_OK) { + LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); + return -1; + } + + return 0; +} + +int reconfigure_video_encoder_bitrate(CodecState *cs, uint32_t video_bitrate) +{ + vpx_codec_enc_cfg_t cfg = *cs->v_encoder.config.enc; + + if (cfg.rc_target_bitrate == video_bitrate) + return 0; + + LOGGER_DEBUG("New video bitrate: %u", video_bitrate); + cfg.rc_target_bitrate = video_bitrate; + int rc = vpx_codec_enc_config_set(&cs->v_encoder, &cfg); + + if ( rc != VPX_CODEC_OK) { + LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); + return -1; + } + + return 0; +} + +int init_video_encoder(CodecState *cs, uint16_t max_width, uint16_t max_height, uint32_t video_bitrate) +{ + vpx_codec_enc_cfg_t cfg; + int 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)); + return -1; + } + + cfg.rc_target_bitrate = video_bitrate; + cfg.g_w = max_width; + cfg.g_h = max_height; + cfg.g_pass = VPX_RC_ONE_PASS; + 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 = 300; + cfg.kf_mode = VPX_KF_AUTO; + + cs->max_width = max_width; + cs->max_height = max_height; + cs->bitrate = video_bitrate; + + rc = vpx_codec_enc_init_ver(&cs->v_encoder, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0, VPX_ENCODER_ABI_VERSION); + + if ( rc != VPX_CODEC_OK) { + LOGGER_ERROR("Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); + return -1; + } + + rc = vpx_codec_control(&cs->v_encoder, VP8E_SET_CPUUSED, 7); + + if ( rc != VPX_CODEC_OK) { + LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); + return -1; + } + + return 0; +} + +int init_audio_encoder(CodecState *cs, uint32_t audio_channels) +{ + int rc = OPUS_OK; + cs->audio_encoder = opus_encoder_create(cs->audio_sample_rate, audio_channels, OPUS_APPLICATION_AUDIO, &rc); + + if ( rc != OPUS_OK ) { + LOGGER_ERROR("Error while starting audio encoder: %s", opus_strerror(rc)); + return -1; + } + + rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_BITRATE(cs->audio_bitrate)); + + if ( rc != OPUS_OK ) { + LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); + return -1; + } + + rc = opus_encoder_ctl(cs->audio_encoder, OPUS_SET_COMPLEXITY(10)); + + if ( rc != OPUS_OK ) { + LOGGER_ERROR("Error while setting encoder ctl: %s", opus_strerror(rc)); + return -1; + } + + cs->audio_encoder_channels = audio_channels; + return 0; +} + + +CodecState *codec_init_session ( uint32_t audio_bitrate, + uint16_t audio_frame_duration, + uint32_t audio_sample_rate, + uint32_t encoder_audio_channels, + uint32_t decoder_audio_channels, + uint32_t audio_VAD_tolerance_ms, + uint16_t max_video_width, + uint16_t max_video_height, + uint32_t video_bitrate ) +{ + CodecState *retu = calloc(sizeof(CodecState), 1); + + if (!retu) return NULL; + + retu->audio_bitrate = audio_bitrate; + retu->audio_sample_rate = audio_sample_rate; + + /* Encoders */ + if (!max_video_width || !max_video_height) { /* Disable video */ + /*video_width = 320; + video_height = 240; */ + } else { + retu->capabilities |= ( 0 == init_video_encoder(retu, max_video_width, max_video_height, + video_bitrate) ) ? v_encoding : 0; + retu->capabilities |= ( 0 == init_video_decoder(retu) ) ? v_decoding : 0; + } + + retu->capabilities |= ( 0 == init_audio_encoder(retu, encoder_audio_channels) ) ? a_encoding : 0; + retu->capabilities |= ( 0 == init_audio_decoder(retu, decoder_audio_channels) ) ? a_decoding : 0; + + if ( retu->capabilities == 0 ) { /* everything failed */ + free (retu); + return NULL; + } + + + retu->EVAD_tolerance = audio_VAD_tolerance_ms > audio_frame_duration ? + audio_VAD_tolerance_ms / audio_frame_duration : audio_frame_duration; + + return retu; +} + +void codec_terminate_session ( CodecState *cs ) +{ + if (!cs) return; + + if ( cs->audio_encoder ) + opus_encoder_destroy(cs->audio_encoder); + + if ( cs->audio_decoder ) + opus_decoder_destroy(cs->audio_decoder); + + if ( cs->capabilities & v_decoding ) + vpx_codec_destroy(&cs->v_decoder); + + if ( cs->capabilities & v_encoding ) + vpx_codec_destroy(&cs->v_encoder); + + LOGGER_DEBUG("Terminated codec state: %p", cs); + free(cs); +} + +static float calculate_sum_sq (int16_t *n, uint16_t k) +{ + float result = 0; + uint16_t i = 0; + + for ( ; i < k; i ++) result += (float) (n[i] * n[i]); + + return result; +} + +int energy_VAD(CodecState *cs, int16_t *PCM, uint16_t frame_size, float energy) +{ + float frame_energy = sqrt(calculate_sum_sq(PCM, frame_size)) / frame_size; + + if ( frame_energy > energy) { + cs->EVAD_tolerance_cr = cs->EVAD_tolerance; /* Reset counter */ + return 1; + } + + if ( cs->EVAD_tolerance_cr ) { + cs->EVAD_tolerance_cr --; + return 1; + } + + return 0; +} diff --git a/protocols/Tox/toxcore/toxav/codec.h b/protocols/Tox/toxcore/toxav/codec.h new file mode 100644 index 0000000000..db4fbea0dc --- /dev/null +++ b/protocols/Tox/toxcore/toxav/codec.h @@ -0,0 +1,116 @@ +/** codec.h + * + * Audio and video codec intitialization, encoding/decoding and playback + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifndef _CODEC_H_ +#define _CODEC_H_ + +#include <stdio.h> +#include <math.h> +#include <pthread.h> + +#include <vpx/vpx_decoder.h> +#include <vpx/vpx_encoder.h> +#include <vpx/vp8dx.h> +#include <vpx/vp8cx.h> +#define VIDEO_CODEC_DECODER_INTERFACE (vpx_codec_vp8_dx()) +#define VIDEO_CODEC_ENCODER_INTERFACE (vpx_codec_vp8_cx()) + +/* Audio encoding/decoding */ +#include <opus.h> + +typedef enum _Capabilities { + none, + a_encoding = 1 << 0, + a_decoding = 1 << 1, + v_encoding = 1 << 2, + v_decoding = 1 << 3 +} Capabilities; + +extern const uint16_t min_jbuf_size; + +typedef struct _CodecState { + + /* video encoding */ + vpx_codec_ctx_t v_encoder; + uint32_t frame_counter; + + /* video decoding */ + vpx_codec_ctx_t v_decoder; + int bitrate; + int max_width; + int max_height; + + /* audio encoding */ + OpusEncoder *audio_encoder; + int audio_bitrate; + int audio_sample_rate; + int audio_encoder_channels; + + /* audio decoding */ + OpusDecoder *audio_decoder; + int audio_decoder_channels; + + uint64_t capabilities; /* supports*/ + + /* Voice activity detection */ + uint32_t EVAD_tolerance; /* In frames */ + uint32_t EVAD_tolerance_cr; +} CodecState; + + +typedef struct _JitterBuffer { + RTPMessage **queue; + uint32_t size; + uint32_t capacity; + uint16_t bottom; + uint16_t top; +} JitterBuffer; + +JitterBuffer *create_queue(unsigned int capacity); +void terminate_queue(JitterBuffer *q); +void queue(JitterBuffer *q, RTPMessage *pk); +RTPMessage *dequeue(JitterBuffer *q, int *success); + + +CodecState *codec_init_session ( uint32_t audio_bitrate, + uint16_t audio_frame_duration, + uint32_t audio_sample_rate, + uint32_t encoder_audio_channels, + uint32_t decoder_audio_channels, + uint32_t audio_VAD_tolerance_ms, + uint16_t max_video_width, + uint16_t max_video_height, + uint32_t video_bitrate ); + +void codec_terminate_session(CodecState *cs); + +/* Reconfigure video encoder + return 0 on success. + return -1 on failure. */ +int reconfigure_video_encoder_resolution(CodecState *cs, uint16_t width, uint16_t height); +int reconfigure_video_encoder_bitrate(CodecState *cs, uint32_t video_bitrate); + +/* Calculate energy and return 1 if has voice, 0 if not */ +int energy_VAD(CodecState *cs, int16_t *PCM, uint16_t frame_size, float energy); + +#endif /* _CODEC_H_ */ diff --git a/protocols/Tox/toxcore/toxav/msi.c b/protocols/Tox/toxcore/toxav/msi.c new file mode 100644 index 0000000000..91742c3579 --- /dev/null +++ b/protocols/Tox/toxcore/toxav/msi.c @@ -0,0 +1,1947 @@ +/** msi.c + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "../toxcore/logger.h" +#include "../toxcore/util.h" + +#include "msi.h" + +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> + +#define MSI_MAXMSG_SIZE 256 + +/* Define default timeout for a request. + * There is no behavior specified by the msi on what will + * client do on timeout, but to call timeout callback. + */ +#define m_deftout 10000 /* in milliseconds */ + +/** + * Protocol: + * + * |id [1 byte]| |size [1 byte]| |data [$size bytes]| |...{repeat}| |0 {end byte}| + */ + +typedef uint8_t MSIRawCSettingsType[23]; + +typedef enum { + IDRequest = 1, + IDResponse, + IDReason, + IDCallId, + IDCSettings, + +} MSIHeaderID; + +typedef enum { + TypeRequest, + TypeResponse, + +} MSIMessageType; + +typedef enum { + invite, + start, + cancel, + reject, + end, + +} MSIRequest; + +typedef enum { + ringing, + starting, + ending, + error + +} MSIResponse; + + +#define GENERIC_HEADER(header, val_type) \ +typedef struct _MSIHeader##header { \ +val_type value; \ +_Bool exists; \ +} MSIHeader##header; + + +GENERIC_HEADER ( Request, MSIRequest ) +GENERIC_HEADER ( Response, MSIResponse ) +GENERIC_HEADER ( CallId, MSICallIDType ) +GENERIC_HEADER ( Reason, MSIReasonStrType ) +GENERIC_HEADER ( CSettings, MSIRawCSettingsType ) + + +/** + * @brief This is the message structure. It contains all of the headers and + * destination/source of the message stored in friend_id. + * + */ +typedef struct _MSIMessage { + + MSIHeaderRequest request; + MSIHeaderResponse response; + MSIHeaderReason reason; + MSIHeaderCallId callid; + MSIHeaderCSettings csettings; + + int friend_id; + +} MSIMessage; + + +inline__ void invoke_callback(MSISession *session, int32_t call_index, MSICallbackID id) +{ + if ( session->callbacks[id].function ) { + LOGGER_DEBUG("Invoking callback function: %d", id); + session->callbacks[id].function ( session->agent_handler, call_index, session->callbacks[id].data ); + } +} + +/** + * @brief Parse raw 'data' received from socket into MSIMessage struct. + * Every message has to have end value of 'end_byte' or _undefined_ behavior + * occures. The best practice is to check the end of the message at the handle_packet. + * + * @param msg Container. + * @param data The data. + * @return int + * @retval -1 Error occurred. + * @retval 0 Success. + */ +static int parse_raw_data ( MSIMessage *msg, const uint8_t *data, uint16_t length ) +{ + +#define FAIL_CONSTRAINT(constraint, wanted) if ((constraint -= wanted) < 1) { LOGGER_ERROR("Read over length!"); return -1; } +#define FAIL_SIZE(byte, valid) if ( byte != valid ) { LOGGER_ERROR("Invalid data size!"); return -1; } +#define FAIL_LIMITS(byte, high) if ( byte > high ) { LOGGER_ERROR("Failed limit!"); return -1; } + + if ( msg == NULL ) { + LOGGER_ERROR("Could not parse message: no storage!"); + return -1; + } + + if ( data[length - 1] ) { /* End byte must have value 0 */ + LOGGER_ERROR("Invalid end byte"); + return -1; + } + + const uint8_t *it = data; + int size_constraint = length; + + while ( *it ) {/* until end byte is hit */ + switch (*it) { + case IDRequest: + FAIL_CONSTRAINT(size_constraint, 3); + FAIL_SIZE(it[1], 1); +// FAIL_LIMITS(it[2], invite, end); + FAIL_LIMITS(it[2], end); + msg->request.value = it[2]; + it += 3; + msg->request.exists = 1; + break; + + case IDResponse: + FAIL_CONSTRAINT(size_constraint, 3); + FAIL_SIZE(it[1], 1); +// FAIL_LIMITS(it[2], ringing, error); + FAIL_LIMITS(it[2], error); + msg->response.value = it[2]; + it += 3; + msg->response.exists = 1; + break; + + case IDCallId: + FAIL_CONSTRAINT(size_constraint, sizeof(MSICallIDType) + 2); + FAIL_SIZE(it[1], sizeof(MSICallIDType)); + memcpy(msg->callid.value, it + 2, sizeof(MSICallIDType)); + it += sizeof(MSICallIDType) + 2; + msg->callid.exists = 1; + break; + + case IDReason: + FAIL_CONSTRAINT(size_constraint, sizeof(MSIReasonStrType) + 2); + FAIL_SIZE(it[1], sizeof(MSIReasonStrType)); + memcpy(msg->reason.value, it + 2, sizeof(MSIReasonStrType)); + it += sizeof(MSIReasonStrType) + 2; + msg->reason.exists = 1; + break; + + case IDCSettings: + FAIL_CONSTRAINT(size_constraint, sizeof(MSIRawCSettingsType) + 2); + FAIL_SIZE(it[1], sizeof(MSIRawCSettingsType)); + memcpy(msg->csettings.value, it + 2, sizeof(MSIRawCSettingsType)); + it += sizeof(MSIRawCSettingsType) + 2; + msg->csettings.exists = 1; + break; + + default: + LOGGER_ERROR("Invalid id byte"); + return -1; + break; + } + } + + return 0; +} + +/** + * @brief Create the message. + * + * @param type Request or response. + * @param type_id Type of request/response. + * @return MSIMessage* Created message. + * @retval NULL Error occurred. + */ +MSIMessage *msi_new_message ( MSIMessageType type, const uint8_t type_value ) +{ + MSIMessage *retu = calloc ( sizeof ( MSIMessage ), 1 ); + + if ( retu == NULL ) { + LOGGER_WARNING("Allocation failed! Program might misbehave!"); + return NULL; + } + + if ( type == TypeRequest ) { + retu->request.exists = 1; + retu->request.value = type_value; + + } else { + retu->response.exists = 1; + retu->response.value = type_value; + } + + return retu; +} + + +/** + * @brief Parse data from handle_packet. + * + * @param data The data. + * @return MSIMessage* Parsed message. + * @retval NULL Error occurred. + */ +MSIMessage *parse_recv ( const uint8_t *data, uint16_t length ) +{ + if ( data == NULL ) { + LOGGER_WARNING("Tried to parse empty message!"); + return NULL; + } + + MSIMessage *retu = calloc ( sizeof ( MSIMessage ), 1 ); + + if ( retu == NULL ) { + LOGGER_WARNING("Allocation failed! Program might misbehave!"); + return NULL; + } + + if ( parse_raw_data ( retu, data, length ) == -1 ) { + + free ( retu ); + return NULL; + } + + return retu; +} + + +/** + * @brief Speaks for it self. + * + * @param dest Container. + * @param header_field Field. + * @param header_value Field value. + * @param value_len Length of field value. + * @param length Pointer to container length. + * @return uint8_t* Iterated container. + */ +uint8_t *format_output ( uint8_t *dest, MSIHeaderID id, const void *value, uint8_t value_len, uint16_t *length ) +{ + if ( dest == NULL ) { + LOGGER_ERROR("No destination space!"); + return NULL; + } + + if (value == NULL || value_len == 0) { + LOGGER_ERROR("Empty header value"); + return NULL; + } + + *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 */ +} + + +/** + * @brief Parse MSIMessage to send. + * + * @param msg The message. + * @param dest Destination. + * @return uint16_t Its final size. + */ +uint16_t parse_send ( MSIMessage *msg, uint8_t *dest ) +{ + if (msg == NULL) { + LOGGER_ERROR("No message!"); + return 0; + } + + if (dest == NULL ) { + LOGGER_ERROR("No destination!"); + return 0; + } + + uint8_t *it = dest; + uint16_t size = 0; + + if (msg->request.exists) { + uint8_t cast = msg->request.value; + it = format_output(it, IDRequest, &cast, 1, &size); + } + + if (msg->response.exists) { + uint8_t cast = msg->response.value; + it = format_output(it, IDResponse, &cast, 1, &size); + } + + if (msg->callid.exists) { + it = format_output(it, IDCallId, &msg->callid.value, sizeof(msg->callid.value), &size); + } + + if (msg->reason.exists) { + it = format_output(it, IDReason, &msg->reason.value, sizeof(msg->reason.value), &size); + } + + if (msg->csettings.exists) { + it = format_output(it, IDCSettings, &msg->csettings.value, sizeof(msg->csettings.value), &size); + } + + *it = 0; + size ++; + + return size; +} + +void msi_msg_set_reason ( MSIMessage *msg, const MSIReasonStrType value ) +{ + if ( !msg ) return; + + msg->reason.exists = 1; + memcpy(msg->reason.value, value, sizeof(MSIReasonStrType)); +} + +void msi_msg_set_callid ( MSIMessage *msg, const MSICallIDType value ) +{ + if ( !msg ) return; + + msg->callid.exists = 1; + memcpy(msg->callid.value, value, sizeof(MSICallIDType)); +} + +void msi_msg_set_csettings ( MSIMessage *msg, const MSICSettings *value ) +{ + if ( !msg ) return; + + msg->csettings.exists = 1; + + msg->csettings.value[0] = value->call_type; + uint8_t *iter = msg->csettings.value + 1; + + /* Video bitrate */ + uint32_t lval = htonl(value->video_bitrate); + memcpy(iter, &lval, 4); + iter += 4; + + /* Video max width */ + uint16_t sval = htons(value->max_video_width); + memcpy(iter, &sval, 2); + iter += 2; + + /* Video max height */ + sval = htons(value->max_video_height); + memcpy(iter, &sval, 2); + iter += 2; + + /* Audio bitrate */ + lval = htonl(value->audio_bitrate); + memcpy(iter, &lval, 4); + iter += 4; + + /* Audio frame duration */ + sval = htons(value->audio_frame_duration); + memcpy(iter, &sval, 2); + iter += 2; + + /* Audio sample rate */ + lval = htonl(value->audio_sample_rate); + memcpy(iter, &lval, 4); + iter += 4; + + /* Audio channels */ + lval = htonl(value->audio_channels); + memcpy(iter, &lval, 4); +} + +void msi_msg_get_csettings ( MSIMessage *msg, MSICSettings *dest ) +{ + if ( !msg || !dest || !msg->csettings.exists ) return; + + dest->call_type = msg->csettings.value[0]; + uint8_t *iter = msg->csettings.value + 1; + + memcpy(&dest->video_bitrate, iter, 4); + iter += 4; + dest->video_bitrate = ntohl(dest->video_bitrate); + + memcpy(&dest->max_video_width, iter, 2); + iter += 2; + dest->max_video_width = ntohs(dest->max_video_width); + + memcpy(&dest->max_video_height, iter, 2); + iter += 2; + dest->max_video_height = ntohs(dest->max_video_height); + + memcpy(&dest->audio_bitrate, iter, 4); + iter += 4; + dest->audio_bitrate = ntohl(dest->audio_bitrate); + + memcpy(&dest->audio_frame_duration, iter, 2); + iter += 2; + dest->audio_frame_duration = ntohs(dest->audio_frame_duration); + + memcpy(&dest->audio_sample_rate, iter, 4); + iter += 4; + dest->audio_sample_rate = ntohl(dest->audio_sample_rate); + + memcpy(&dest->audio_channels, iter, 4); + dest->audio_channels = ntohl(dest->audio_channels); +} + +typedef struct _Timer { + void *(*func)(void *); + void *func_arg1; + int func_arg2; + uint64_t timeout; + int idx; + +} Timer; + +typedef struct _TimerHandler { + Timer **timers; + pthread_mutex_t mutex; + + uint32_t max_capacity; + uint32_t size; + uint64_t resolution; + + _Bool running; + +} TimerHandler; + +struct timer_function_args { + void *arg1; + int arg2; +}; + +/** + * @brief Allocate timer in array + * + * @param timers_container Handler + * @param func Function to be executed + * @param arg Its args + * @param timeout Timeout in ms + * @return int + */ +static int timer_alloc ( TimerHandler *timers_container, void *(func)(void *), void *arg1, int arg2, uint32_t timeout) +{ + static int timer_id; + pthread_mutex_lock(&timers_container->mutex); + + uint32_t i = 0; + + for (; i < timers_container->max_capacity && timers_container->timers[i]; i ++); + + if (i == timers_container->max_capacity) { + LOGGER_WARNING("Maximum capacity reached!"); + pthread_mutex_unlock(&timers_container->mutex); + return -1; + } + + Timer *timer = timers_container->timers[i] = calloc(sizeof(Timer), 1); + + if (timer == NULL) { + LOGGER_ERROR("Failed to allocate timer!"); + pthread_mutex_unlock(&timers_container->mutex); + return -1; + } + + timers_container->size ++; + + timer->func = func; + timer->func_arg1 = arg1; + timer->func_arg2 = arg2; + timer->timeout = timeout + current_time_monotonic(); /* In ms */ + ++timer_id; + timer->idx = timer_id; + + /* reorder */ + if (i) { + int64_t j = i - 1; + + for (; j >= 0 && timeout < timers_container->timers[j]->timeout; j--) { + Timer *tmp = timers_container->timers[j]; + timers_container->timers[j] = timer; + timers_container->timers[j + 1] = tmp; + } + } + + pthread_mutex_unlock(&timers_container->mutex); + + LOGGER_DEBUG("Allocated timer index: %ull timeout: %ull, current size: %ull", i, timeout, timers_container->size); + return timer->idx; +} + +/** + * @brief Remove timer from array + * + * @param timers_container handler + * @param idx timer id + * @param lock_mutex (does the mutex need to be locked) + * @return int + */ +static int timer_release ( TimerHandler *timers_container, int idx , int lock_mutex) +{ + if (lock_mutex) + pthread_mutex_lock(&timers_container->mutex); + + Timer **timed_events = timers_container->timers; + + size_t i; + int rc = -1; + + for (i = 0; i < timers_container->max_capacity; ++i) { + if (timed_events[i] && timed_events[i]->idx == idx) { + rc = i; + break; + } + } + + if (rc == -1) { + LOGGER_WARNING("No event with id: %d", idx); + + if (lock_mutex) pthread_mutex_unlock(&timers_container->mutex); + + return -1; + } + + free(timed_events[rc]); + + timed_events[rc] = NULL; + + i = rc + 1; + + for (; i < timers_container->max_capacity && timed_events[i]; i ++) { + timed_events[i - 1] = timed_events[i]; + timed_events[i] = NULL; + } + + timers_container->size--; + + LOGGER_DEBUG("Popped id: %d, current size: %ull ", idx, timers_container->size); + + if (lock_mutex) pthread_mutex_unlock(&timers_container->mutex); + + return 0; +} + +/** + * @brief Main poll for timer execution + * + * @param arg ... + * @return void* + */ +static void *timer_poll( void *arg ) +{ + TimerHandler *handler = arg; + + while ( handler->running ) { + + pthread_mutex_lock(&handler->mutex); + + if ( handler->running ) { + + uint64_t time = current_time_monotonic(); + + while ( handler->timers[0] && handler->timers[0]->timeout < time ) { + pthread_t tid; + + struct timer_function_args *args = malloc(sizeof(struct timer_function_args)); + args->arg1 = handler->timers[0]->func_arg1; + args->arg2 = handler->timers[0]->func_arg2; + + if ( 0 != pthread_create(&tid, NULL, handler->timers[0]->func, args) || + 0 != pthread_detach(tid) ) { + LOGGER_ERROR("Failed to execute timer at: %d!", handler->timers[0]->timeout); + free(args); + } else { + LOGGER_DEBUG("Exectued timer assigned at: %d", handler->timers[0]->timeout); + } + + timer_release(handler, handler->timers[0]->idx, 0); + } + + } + + pthread_mutex_unlock(&handler->mutex); + + usleep(handler->resolution); + } + + pthread_exit(NULL); +} + +/** + * @brief Start timer poll and return handler + * + * @param max_capacity capacity + * @param resolution ... + * @return TimerHandler* + */ +static TimerHandler *timer_init_session (int max_capacity, int resolution) +{ + TimerHandler *handler = calloc(1, sizeof(TimerHandler)); + + if (handler == NULL) { + LOGGER_ERROR("Failed to allocate memory, program might misbehave!"); + return NULL; + } + + handler->timers = calloc(max_capacity, sizeof(Timer *)); + + if (handler->timers == NULL) { + LOGGER_ERROR("Failed to allocate %d timed events!", max_capacity); + free(handler); + return NULL; + } + + handler->max_capacity = max_capacity; + handler->running = 1; + handler->resolution = resolution; + + pthread_mutex_init(&handler->mutex, NULL); + + + pthread_t _tid; + + if ( 0 != pthread_create(&_tid, NULL, timer_poll, handler) || 0 != pthread_detach(_tid) ) { + LOGGER_ERROR("Failed to start timer poll thread!"); + free(handler->timers); + free(handler); + return NULL; + } + + return handler; +} + +/** + * @brief Terminate timer session + * + * @param handler The timer handler + * @return void + */ +static void timer_terminate_session(TimerHandler *handler) +{ + pthread_mutex_lock(&handler->mutex); + + handler->running = 0; + + pthread_mutex_unlock(&handler->mutex); + + size_t i = 0; + + for (; i < handler->max_capacity; i ++) + free(handler->timers[i]); + + free(handler->timers); + + pthread_mutex_destroy( &handler->mutex ); +} + +/** + * @brief Generate _random_ alphanumerical string. + * + * @param str Destination. + * @param size Size of string. + * @return void + */ +static void t_randomstr ( uint8_t *str, uint32_t size ) +{ + if (str == NULL) { + LOGGER_DEBUG("Empty destination!"); + return; + } + + static const uint8_t _bytes[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + + uint32_t _it = 0; + + for ( ; _it < size; _it++ ) { + str[_it] = _bytes[ random_int() % 61 ]; + } +} + + +typedef enum { + error_none, + error_deadcall, /* has call id but it's from old call */ + error_id_mismatch, /* non-existing call */ + + error_no_callid, /* not having call id */ + error_no_call, /* no call in session */ + error_no_crypto_key, /* no crypto key */ + + error_busy + +} MSICallError; /* Error codes */ + + +/** + * @brief Stringify error code. + * + * @param error_code The code. + * @return const uint8_t* The string. + */ +static inline__ const uint8_t *stringify_error ( MSICallError error_code ) +{ + static const uint8_t *strings[] = { + ( uint8_t *) "", + ( uint8_t *) "Using dead call", + ( uint8_t *) "Call id not set to any call", + ( uint8_t *) "Call id not available", + ( uint8_t *) "No active call in session", + ( uint8_t *) "No Crypto-key set", + ( uint8_t *) "Callee busy" + }; + + return strings[error_code]; +} + +/** + * @brief Speaks for it self. + * + * @param session Control session. + * @param msg The message. + * @param to Where to. + * @return int + * @retval -1 Error occurred. + * @retval 0 Success. + */ +static int send_message ( MSISession *session, MSICall *call, MSIMessage *msg, uint32_t to ) +{ + msi_msg_set_callid ( msg, call->id ); + + uint8_t msg_string_final [MSI_MAXMSG_SIZE]; + uint16_t length = parse_send ( msg, msg_string_final ); + + if (!length) { + LOGGER_WARNING("Parsing message failed; nothing sent!"); + return -1; + } + + if ( m_msi_packet(session->messenger_handle, to, msg_string_final, length) ) { + LOGGER_DEBUG("Sent message"); + return 0; + } + + return -1; +} + +inline__ int send_reponse ( MSISession *session, MSICall *call, MSIResponse response, uint32_t to ) +{ + MSIMessage *msg = msi_new_message ( TypeResponse, response ); + int ret = send_message ( session, call, msg, to ); + free ( msg ); + return ret; +} + +/** + * @brief Determine 'bigger' call id + * + * @param first duh + * @param second duh + * @return int + * @retval 0 it's first + * @retval 1 it's second + */ +static int call_id_bigger( const uint8_t *first, const uint8_t *second) +{ + return (memcmp(first, second, sizeof(MSICallIDType)) < 0); +} + + +/** + * @brief Speaks for it self. + * + * @param session Control session. + * @param msg The message. + * @param peer_id The peer. + * @return -1, 0 + */ +static int flush_peer_csettings ( MSICall *call, MSIMessage *msg, int peer_id ) +{ + if ( msg->csettings.exists ) { + msi_msg_get_csettings(msg, &call->csettings_peer[peer_id]); + + LOGGER_DEBUG("Peer: %d \n" + "Type: %u \n" + "Video bitrate: %u \n" + "Video height: %u \n" + "Video width: %u \n" + "Audio bitrate: %u \n" + "Audio framedur: %u \n" + "Audio sample rate: %u \n" + "Audio channels: %u \n", peer_id, + call->csettings_peer[peer_id].call_type, + call->csettings_peer[peer_id].video_bitrate, + call->csettings_peer[peer_id].max_video_height, + call->csettings_peer[peer_id].max_video_width, + call->csettings_peer[peer_id].audio_bitrate, + call->csettings_peer[peer_id].audio_frame_duration, + call->csettings_peer[peer_id].audio_sample_rate, + call->csettings_peer[peer_id].audio_channels ); + + return 0; + } + + LOGGER_WARNING("No csettings header!"); + return -1; +} + +static int terminate_call ( MSISession *session, MSICall *call ); + +static void handle_remote_connection_change(Messenger *messenger, int friend_num, uint8_t status, void *session_p) +{ + (void)messenger; + MSISession *session = session_p; + + switch ( status ) { + case 0: { /* Went offline */ + int32_t j = 0; + + for ( ; j < session->max_calls; j ++ ) { + + if ( !session->calls[j] ) continue; + + uint16_t i = 0; + + for ( ; i < session->calls[j]->peer_count; i ++ ) + if ( session->calls[j]->peers[i] == (uint32_t)friend_num ) { + invoke_callback(session, j, MSI_OnPeerTimeout); + terminate_call(session, session->calls[j]); + LOGGER_DEBUG("Remote: %d timed out!", friend_num); + return; /* TODO: On group calls change behaviour */ + } + } + } + break; + + default: + break; + } +} + +static MSICall *find_call ( MSISession *session, uint8_t *call_id ) +{ + if ( call_id == NULL ) return NULL; + + int32_t i = 0; + + for (; i < session->max_calls; i ++ ) + if ( session->calls[i] && memcmp(session->calls[i]->id, call_id, sizeof(session->calls[i]->id)) == 0 ) { + return session->calls[i]; + } + + return NULL; +} + +/** + * @brief Sends error response to peer. + * + * @param session The session. + * @param errid The id. + * @param to Where to? + * @return int + * @retval -1/0 It's usually always success. + */ +static int send_error ( MSISession *session, MSICall *call, MSICallError errid, uint32_t to ) +{ + if (!call) { + LOGGER_WARNING("Cannot handle error on 'null' call"); + return -1; + } + + LOGGER_DEBUG("Sending error: %d on call: %s", errid, call->id); + + MSIMessage *msg_error = msi_new_message ( TypeResponse, error ); + + msi_msg_set_reason ( msg_error, stringify_error(errid) ); + send_message ( session, call, msg_error, to ); + free ( msg_error ); + + return 0; +} + + + +/** + * @brief Add peer to peer list. + * + * @param call What call. + * @param peer_id Its id. + * @return void + */ +static void add_peer( MSICall *call, int peer_id ) +{ + uint32_t *peers = !call->peers ? peers = calloc(sizeof(uint32_t), 1) : + realloc( call->peers, sizeof(uint32_t) * call->peer_count); + + if (!peers) { + LOGGER_WARNING("Allocation failed! Program might misbehave!"); + return; + } + + call->peer_count ++; + call->peers = peers; + call->peers[call->peer_count - 1] = peer_id; + + LOGGER_DEBUG("Added peer: %d", peer_id); +} + + +/** + * @brief Speaks for it self. + * + * @param session Control session. + * @param peers Amount of peers. (Currently it only supports 1) + * @param ringing_timeout Ringing timeout. + * @return MSICall* The created call. + */ +static MSICall *init_call ( MSISession *session, int peers, int ringing_timeout ) +{ + + if (peers == 0) { + LOGGER_ERROR("No peers!"); + return NULL; + } + + int32_t call_idx = 0; + + for (; call_idx < session->max_calls; call_idx ++) { + if ( !session->calls[call_idx] ) { + + if (!(session->calls[call_idx] = calloc ( sizeof ( MSICall ), 1 ))) { + LOGGER_WARNING("Allocation failed! Program might misbehave!"); + return NULL; + } + + break; + } + } + + if ( call_idx == session->max_calls ) { + LOGGER_WARNING("Reached maximum amount of calls!"); + return NULL; + } + + + MSICall *call = session->calls[call_idx]; + + call->call_idx = call_idx; + + if ( !(call->csettings_peer = calloc ( sizeof ( MSICSettings ), peers )) ) { + LOGGER_WARNING("Allocation failed! Program might misbehave!"); + free(call); + return NULL; + } + + call->session = session; + + call->request_timer_id = 0; + call->ringing_timer_id = 0; + + call->ringing_tout_ms = ringing_timeout; + + pthread_mutex_init ( &call->mutex, NULL ); + + LOGGER_DEBUG("Started new call with index: %u", call_idx); + return call; +} + + +/** + * @brief Terminate the call. + * + * @param session Control session. + * @return int + * @retval -1 Error occurred. + * @retval 0 Success. + */ +static int terminate_call ( MSISession *session, MSICall *call ) +{ + if ( !call ) { + LOGGER_WARNING("Tried to terminate non-existing call!"); + return -1; + } + + LOGGER_DEBUG("Terminated call id: %d", call->call_idx); + /* Check event loop and cancel timed events if there are any + * NOTE: This has to be done before possibly + * locking the mutex the second time + */ + timer_release ( session->timer_handler, call->request_timer_id, 1); + timer_release ( session->timer_handler, call->ringing_timer_id, 1); + + /* Get a handle */ + pthread_mutex_lock ( &call->mutex ); + + session->calls[call->call_idx] = NULL; + + free ( call->csettings_peer ); + free ( call->peers); + + /* Release handle */ + pthread_mutex_unlock ( &call->mutex ); + + pthread_mutex_destroy ( &call->mutex ); + + free ( call ); + + return 0; +} + + +/** + * @brief Function called at request timeout. If not called in thread it might cause trouble + * + * @param arg Control session + * @return void* + */ +static void *handle_timeout ( void *arg ) +{ + /* TODO: Cancel might not arrive there; set up + * timers on these cancels and terminate call on + * their timeout + */ + struct timer_function_args *args = arg; + int call_index = args->arg2; + MSISession *session = args->arg1; + MSICall *call = session->calls[call_index]; + + if (call) { + LOGGER_DEBUG("[Call: %d] Request timed out!", call->call_idx); + + invoke_callback(session, call_index, MSI_OnRequestTimeout); + } + + if ( call && call->session ) { + + /* TODO: Cancel all? */ + /* uint16_t _it = 0; + * for ( ; _it < _session->call->peer_count; _it++ ) */ + msi_cancel ( call->session, call->call_idx, call->peers [0], "Request timed out" ); + /*terminate_call(call->session, call);*/ + } + + free(arg); + pthread_exit(NULL); +} + + +/********** Request handlers **********/ +static int handle_recv_invite ( MSISession *session, MSICall *call, MSIMessage *msg ) +{ + LOGGER_DEBUG("Session: %p Handling 'invite' on call: %d", session, call ? call->call_idx : -1); + + pthread_mutex_lock(&session->mutex); + + if (!msg->csettings.exists) {/**/ + LOGGER_WARNING("Peer sent invalid codec settings!"); + send_error ( session, call, error_no_callid, msg->friend_id ); + pthread_mutex_unlock(&session->mutex); + return 0; + } + + if ( call ) { + if ( call->peers[0] == (uint32_t)msg->friend_id ) { + if (call->state == call_inviting) { + /* The glare case. A calls B when at the same time + * B calls A. Who has advantage is set bey calculating + * 'bigger' Call id and then that call id is being used in + * future. User with 'bigger' Call id has the advantage + * as in he will wait the response from the other. + */ + LOGGER_DEBUG("Glare case; Peer: %d", call->peers[0]); + + if ( call_id_bigger (call->id, msg->callid.value) == 1 ) { /* Peer has advantage */ + + /* Terminate call; peer will timeout(call) if call initialization fails */ + terminate_call(session, call); + + call = init_call ( session, 1, 0 ); + + if ( !call ) { + pthread_mutex_unlock(&session->mutex); + LOGGER_ERROR("Starting call"); + return 0; + } + + } else { + pthread_mutex_unlock(&session->mutex); + return 0; /* Wait for ringing from peer */ + } + } else if (call->state == call_active) { + /* Request for media change; call callback and send starting response */ + if (flush_peer_csettings(call, msg, 0) != 0) { /**/ + LOGGER_WARNING("Peer sent invalid csetting!"); + send_error ( session, call, error_no_callid, msg->friend_id ); + pthread_mutex_unlock(&session->mutex); + return 0; + } + + LOGGER_DEBUG("Set new call type: %s", call->csettings_peer[0].call_type == type_audio ? "audio" : "video"); + send_reponse(session, call, starting, msg->friend_id); + pthread_mutex_unlock(&session->mutex); + invoke_callback(session, call->call_idx, MSI_OnMediaChange); + return 1; + } + } else { + send_error ( session, call, error_busy, msg->friend_id ); /* TODO: Ugh*/ + terminate_call(session, call); + pthread_mutex_unlock(&session->mutex); + return 0; + } + } else { + call = init_call ( session, 1, 0 ); + + if ( !call ) { + pthread_mutex_unlock(&session->mutex); + LOGGER_ERROR("Starting call"); + return 0; + } + } + + if ( !msg->callid.exists ) { + send_error ( session, call, error_no_callid, msg->friend_id ); + terminate_call(session, call); + pthread_mutex_unlock(&session->mutex); + return 0; + } + + memcpy ( call->id, msg->callid.value, sizeof(msg->callid.value) ); + call->state = call_starting; + + add_peer( call, msg->friend_id); + + flush_peer_csettings ( call, msg, 0 ); + + send_reponse(session, call, ringing, msg->friend_id); + + pthread_mutex_unlock(&session->mutex); + + invoke_callback(session, call->call_idx, MSI_OnInvite); + + return 1; +} + +static int handle_recv_start ( MSISession *session, MSICall *call, MSIMessage *msg ) +{ + if ( !call ) { + LOGGER_WARNING("Session: %p Handling 'start' on no call"); + return 0; + } + + (void)msg; + + LOGGER_DEBUG("Session: %p Handling 'start' on call: %d, friend id: %d", session, call->call_idx, msg->friend_id ); + + pthread_mutex_lock(&session->mutex); + + call->state = call_active; + + pthread_mutex_unlock(&session->mutex); + + invoke_callback(session, call->call_idx, MSI_OnStart); + return 1; +} + +static int handle_recv_reject ( MSISession *session, MSICall *call, MSIMessage *msg ) +{ + if ( !call ) { + LOGGER_WARNING("Session: %p Handling 'start' on no call"); + return 0; + } + + LOGGER_DEBUG("Session: %p Handling 'reject' on call: %u", session, call->call_idx); + + invoke_callback(session, call->call_idx, MSI_OnReject); + + pthread_mutex_lock(&session->mutex); + + send_reponse(session, call, ending, msg->friend_id); + terminate_call(session, call); + + pthread_mutex_unlock(&session->mutex); + + return 1; +} + +static int handle_recv_cancel ( MSISession *session, MSICall *call, MSIMessage *msg ) +{ + if ( !call ) { + LOGGER_WARNING("Session: %p Handling 'start' on no call"); + return 0; + } + + (void)msg; + + LOGGER_DEBUG("Session: %p Handling 'cancel' on call: %u", session, call->call_idx); + + invoke_callback(session, call->call_idx, MSI_OnCancel); + + pthread_mutex_lock(&session->mutex); + + terminate_call ( session, call ); + + pthread_mutex_unlock(&session->mutex); + + return 1; +} + +static int handle_recv_end ( MSISession *session, MSICall *call, MSIMessage *msg ) +{ + if ( !call ) { + LOGGER_WARNING("Session: %p Handling 'start' on no call"); + return 0; + } + + LOGGER_DEBUG("Session: %p Handling 'end' on call: %d", session, call->call_idx); + + invoke_callback(session, call->call_idx, MSI_OnEnd); + pthread_mutex_lock(&session->mutex); + + send_reponse(session, call, ending, msg->friend_id); + terminate_call ( session, call ); + + pthread_mutex_unlock(&session->mutex); + + + return 1; +} + +/********** Response handlers **********/ +static int handle_recv_ringing ( MSISession *session, MSICall *call, MSIMessage *msg ) +{ + if ( !call ) { + LOGGER_WARNING("Session: %p Handling 'start' on no call"); + return 0; + } + + (void)msg; + + pthread_mutex_lock(&session->mutex); + + if ( call->ringing_timer_id ) { + LOGGER_WARNING("Call already ringing"); + pthread_mutex_unlock(&session->mutex); + return 0; + } + + LOGGER_DEBUG("Session: %p Handling 'ringing' on call: %d", session, call->call_idx ); + + call->ringing_timer_id = timer_alloc ( session->timer_handler, handle_timeout, session, call->call_idx, + call->ringing_tout_ms ); + + pthread_mutex_unlock(&session->mutex); + + invoke_callback(session, call->call_idx, MSI_OnRinging); + return 1; +} +static int handle_recv_starting ( MSISession *session, MSICall *call, MSIMessage *msg ) +{ + if ( !call ) { + LOGGER_WARNING("Session: %p Handling 'starting' on non-existing call"); + return 0; + } + + pthread_mutex_lock(&session->mutex); + + if ( call->state == call_active ) { /* Change media */ + + LOGGER_DEBUG("Session: %p Changing media on call: %d", session, call->call_idx ); + pthread_mutex_unlock(&session->mutex); + + invoke_callback(session, call->call_idx, MSI_OnMediaChange); + + } else if ( call->state == call_inviting ) { + LOGGER_DEBUG("Session: %p Handling 'starting' on call: %d", session, call->call_idx ); + + call->state = call_active; + + MSIMessage *msg_start = msi_new_message ( TypeRequest, start ); + send_message ( session, call, msg_start, msg->friend_id ); + free ( msg_start ); + + + flush_peer_csettings ( call, msg, 0 ); + + /* This is here in case of glare */ + timer_release ( session->timer_handler, call->ringing_timer_id, 1 ); + + pthread_mutex_unlock(&session->mutex); + + invoke_callback(session, call->call_idx, MSI_OnStarting); + } else { + LOGGER_ERROR("Invalid call state"); + terminate_call(session, call ); + pthread_mutex_unlock(&session->mutex); + return 0; + } + + return 1; +} +static int handle_recv_ending ( MSISession *session, MSICall *call, MSIMessage *msg ) +{ + if ( !call ) { + LOGGER_WARNING("Session: %p Handling 'start' on no call"); + return 0; + } + + (void)msg; + + LOGGER_DEBUG("Session: %p Handling 'ending' on call: %d", session, call->call_idx ); + + invoke_callback(session, call->call_idx, MSI_OnEnding); + + /* Terminate call */ + pthread_mutex_lock(&session->mutex); + terminate_call ( session, call ); + pthread_mutex_unlock(&session->mutex); + + return 1; +} +static int handle_recv_error ( MSISession *session, MSICall *call, MSIMessage *msg ) +{ + + if ( !call ) { + LOGGER_WARNING("Handling 'error' on non-existing call!"); + pthread_mutex_unlock(&session->mutex); + return -1; + } + + LOGGER_DEBUG("Session: %p Handling 'error' on call: %d", session, call->call_idx ); + + invoke_callback(session, call->call_idx, MSI_OnEnding); + + pthread_mutex_lock(&session->mutex); + + /* Handle error accordingly */ + if ( msg->reason.exists ) { + /* TODO */ + } + + terminate_call ( session, call ); + + pthread_mutex_unlock(&session->mutex); + + return 1; +} + + +/** + * @brief BASIC call flow: + * + * ALICE BOB + * | invite --> | + * | | + * | <-- ringing | + * | | + * | <-- starting | + * | | + * | start --> | + * | | + * | <-- MEDIA TRANS --> | + * | | + * | end --> | + * | | + * | <-- ending | + * + * Alice calls Bob by sending invite packet. + * Bob recvs the packet and sends an ringing packet; + * which notifies Alice that her invite is acknowledged. + * Ringing screen shown on both sides. + * Bob accepts the invite for a call by sending starting packet. + * Alice recvs the starting packet and sends the started packet to + * inform Bob that she recved the starting packet. + * Now the media transmission is established ( i.e. RTP transmission ). + * Alice hangs up and sends end packet. + * Bob recves the end packet and sends ending packet + * as the acknowledgement that the call is ending. + * + * + */ +static void msi_handle_packet ( Messenger *messenger, int source, const uint8_t *data, uint16_t length, void *object ) +{ + LOGGER_DEBUG("Got msi message"); + /* Unused */ + (void)messenger; + + MSISession *session = object; + MSIMessage *msg; + + if ( !length ) { + LOGGER_WARNING("Lenght param negative"); + return; + } + + msg = parse_recv ( data, length ); + + if ( !msg ) { + LOGGER_WARNING("Error parsing message"); + return; + } else { + LOGGER_DEBUG("Successfully parsed message"); + } + + msg->friend_id = source; + + + /* Find what call */ + MSICall *call = msg->callid.exists ? find_call(session, msg->callid.value ) : NULL; + + /* Now handle message */ + + if ( msg->request.exists ) { /* Handle request */ + + switch (msg->request.value) { + case invite: + handle_recv_invite ( session, call, msg ); + break; + + case start: + handle_recv_start ( session, call, msg ); + break; + + case cancel: + handle_recv_cancel ( session, call, msg ); + break; + + case reject: + handle_recv_reject ( session, call, msg ); + break; + + case end: + handle_recv_end ( session, call, msg ); + break; + } + + } else if ( msg->response.exists ) { /* Handle response */ + + /* Got response so cancel timer */ + if ( call ) timer_release ( session->timer_handler, call->request_timer_id, 1 ); + + switch (msg->response.value) { + case ringing: + handle_recv_ringing ( session, call, msg ); + break; + + case starting: + handle_recv_starting ( session, call, msg ); + break; + + case ending: + handle_recv_ending ( session, call, msg ); + break; + + case error: + handle_recv_error ( session, call, msg ); + break; + } + + } else { + LOGGER_WARNING("Invalid message: no resp nor requ headers"); + } + + free ( msg ); +} + + +/** + * @brief Callback setter. + * + * @param callback The callback. + * @param id The id. + * @return void + */ +void msi_register_callback ( MSISession *session, MSICallbackType callback, MSICallbackID id, void *userdata ) +{ + session->callbacks[id].function = callback; + session->callbacks[id].data = userdata; +} + + +/** + * @brief Start the control session. + * + * @param messenger Tox* object. + * @param max_calls Amount of calls possible + * @return MSISession* The created session. + * @retval NULL Error occurred. + */ +MSISession *msi_init_session ( Messenger *messenger, int32_t max_calls ) +{ + if (messenger == NULL) { + LOGGER_ERROR("Could not init session on empty messenger!"); + return NULL; + } + + TimerHandler *handler = timer_init_session(max_calls * 10, 10000); + + if ( !max_calls || !handler ) { + LOGGER_WARNING("Invalid max call treshold or timer handler initialization failed!"); + return NULL; + } + + MSISession *retu = calloc ( sizeof ( MSISession ), 1 ); + + if (retu == NULL) { + LOGGER_ERROR("Allocation failed! Program might misbehave!"); + return NULL; + } + + retu->messenger_handle = messenger; + retu->agent_handler = NULL; + retu->timer_handler = handler; + + if (!(retu->calls = calloc( sizeof (MSICall *), max_calls ))) { + LOGGER_ERROR("Allocation failed! Program might misbehave!"); + free(retu); + return NULL; + } + + retu->max_calls = max_calls; + + retu->frequ = 10000; /* default value? */ + retu->call_timeout = 30000; /* default value? */ + + + m_callback_msi_packet(messenger, msi_handle_packet, retu ); + + /* This is called when remote terminates session */ + m_callback_connectionstatus_internal_av(messenger, handle_remote_connection_change, retu); + + pthread_mutex_init(&retu->mutex, NULL); + + LOGGER_DEBUG("New msi session: %p max calls: %u", retu, max_calls); + return retu; +} + + +/** + * @brief Terminate control session. + * + * @param session The session + * @return int + */ +int msi_terminate_session ( MSISession *session ) +{ + if (session == NULL) { + LOGGER_ERROR("Tried to terminate non-existing session"); + return -1; + } + + pthread_mutex_lock(&session->mutex); + m_callback_msi_packet((struct Messenger *) session->messenger_handle, NULL, NULL); + pthread_mutex_unlock(&session->mutex); + + int _status = 0; + + /* If have calls, cancel them */ + int32_t idx = 0; + + for (; idx < session->max_calls; idx ++) if ( session->calls[idx] ) { + /* Cancel all? */ + uint16_t _it = 0; + /*for ( ; _it < session->calls[idx]->peer_count; _it++ ) + * FIXME: will not work on multiple peers, must cancel call for all peers + */ + msi_cancel ( session, idx, session->calls[idx]->peers [_it], "MSI session terminated!" ); + } + + timer_terminate_session(session->timer_handler); + + pthread_mutex_destroy(&session->mutex); + + LOGGER_DEBUG("Terminated session: %p", session); + free ( session ); + return _status; +} + + +/** + * @brief Send invite request to friend_id. + * + * @param session Control session. + * @param call_type Type of the call. Audio or Video(both audio and video) + * @param rngsec Ringing timeout. + * @param friend_id The friend. + * @return int + */ +int msi_invite ( MSISession *session, int32_t *call_index, MSICSettings csettings, uint32_t rngsec, uint32_t friend_id ) +{ + pthread_mutex_lock(&session->mutex); + + LOGGER_DEBUG("Session: %p Inviting friend: %u", session, friend_id); + + + int i = 0; + + for (; i < session->max_calls; i ++) + if (session->calls[i] && session->calls[i]->peers[0] == friend_id) { + LOGGER_ERROR("Already in a call with friend %d", friend_id); + pthread_mutex_unlock(&session->mutex); + return -1; + } + + + MSICall *call = init_call ( session, 1, rngsec ); /* Just one peer for now */ + + if ( !call ) { + pthread_mutex_unlock(&session->mutex); + LOGGER_ERROR("Cannot handle more calls"); + return -1; + } + + *call_index = call->call_idx; + + t_randomstr ( call->id, sizeof(call->id) ); + + add_peer ( call, friend_id ); + + call->csettings_local = csettings; + + MSIMessage *msg_invite = msi_new_message ( TypeRequest, invite ); + + msi_msg_set_csettings(msg_invite, &csettings); + send_message ( session, call, msg_invite, friend_id ); + free( msg_invite ); + + call->state = call_inviting; + + call->request_timer_id = timer_alloc ( session->timer_handler, handle_timeout, session, call->call_idx, m_deftout ); + + LOGGER_DEBUG("Invite sent"); + + pthread_mutex_unlock(&session->mutex); + + return 0; +} + + +/** + * @brief Hangup active call. + * + * @param session Control session. + * @param call_id To which call is this action handled. + * @return int + * @retval -1 Error occurred. + * @retval 0 Success. + */ +int msi_hangup ( MSISession *session, int32_t call_index ) +{ + pthread_mutex_lock(&session->mutex); + LOGGER_DEBUG("Session: %p Hanging up call: %u", session, call_index); + + if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { + LOGGER_ERROR("Invalid call index!"); + pthread_mutex_unlock(&session->mutex); + return -1; + } + + if ( session->calls[call_index]->state != call_active ) { + LOGGER_ERROR("No call with such index or call is not active!"); + pthread_mutex_unlock(&session->mutex); + return -1; + } + + MSIMessage *msg_end = msi_new_message ( TypeRequest, end ); + + /* hangup for each peer */ + int it = 0; + + for ( ; it < session->calls[call_index]->peer_count; it ++ ) + send_message ( session, session->calls[call_index], msg_end, session->calls[call_index]->peers[it] ); + + session->calls[call_index]->state = call_hanged_up; + + free ( msg_end ); + + session->calls[call_index]->request_timer_id = + timer_alloc ( session->timer_handler, handle_timeout, session, call_index, m_deftout ); + + pthread_mutex_unlock(&session->mutex); + return 0; +} + + +/** + * @brief Answer active call request. + * + * @param session Control session. + * @param call_id To which call is this action handled. + * @param call_type Answer with Audio or Video(both). + * @return int + */ +int msi_answer ( MSISession *session, int32_t call_index, MSICSettings csettings ) +{ + pthread_mutex_lock(&session->mutex); + LOGGER_DEBUG("Session: %p Answering call: %u", session, call_index); + + if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { + LOGGER_ERROR("Invalid call index!"); + pthread_mutex_unlock(&session->mutex); + return -1; + } + + MSIMessage *msg_starting = msi_new_message ( TypeResponse, starting ); + + session->calls[call_index]->csettings_local = csettings; + + msi_msg_set_csettings(msg_starting, &csettings); + + send_message ( session, session->calls[call_index], msg_starting, session->calls[call_index]->peers[0] ); + free ( msg_starting ); + + session->calls[call_index]->state = call_active; + + pthread_mutex_unlock(&session->mutex); + return 0; +} + + +/** + * @brief Cancel request. + * + * @param session Control session. + * @param call_id To which call is this action handled. + * @param reason Set optional reason header. Pass NULL if none. + * @return int + */ +int msi_cancel ( MSISession *session, int32_t call_index, uint32_t peer, const char *reason ) +{ + pthread_mutex_lock(&session->mutex); + LOGGER_DEBUG("Session: %p Canceling call: %u; reason: %s", session, call_index, reason ? reason : "Unknown"); + + if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { + LOGGER_ERROR("Invalid call index!"); + pthread_mutex_unlock(&session->mutex); + return -1; + } + + MSIMessage *msg_cancel = msi_new_message ( TypeRequest, cancel ); + + /* FIXME */ +#if 0 + + if ( reason && strlen(reason) < sizeof(MSIReasonStrType) ) { + MSIReasonStrType reason_cast; + memset(reason_cast, '\0', sizeof(MSIReasonStrType)); + memcpy(reason_cast, reason, strlen(reason)); + msi_msg_set_reason(msg_cancel, reason_cast); + } + +#else + (void)reason; + +#endif + + send_message ( session, session->calls[call_index], msg_cancel, peer ); + free ( msg_cancel ); + + terminate_call ( session, session->calls[call_index] ); + pthread_mutex_unlock(&session->mutex); + + return 0; +} + + +/** + * @brief Reject request. + * + * @param session Control session. + * @param call_id To which call is this action handled. + * @return int + */ +int msi_reject ( MSISession *session, int32_t call_index, const char *reason ) +{ + pthread_mutex_lock(&session->mutex); + LOGGER_DEBUG("Session: %p Rejecting call: %u; reason: %s", session, call_index, reason ? reason : "Unknown"); + + if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { + LOGGER_ERROR("Invalid call index!"); + pthread_mutex_unlock(&session->mutex); + return -1; + } + + MSIMessage *msg_reject = msi_new_message ( TypeRequest, reject ); + + /* FIXME */ +#if 0 + + if ( reason && strlen(reason) < sizeof(MSIReasonStrType) ) { + MSIReasonStrType reason_cast; + memset(reason_cast, '\0', sizeof(MSIReasonStrType)); + memcpy(reason_cast, reason, strlen(reason)); + msi_msg_set_reason(msg_reject, reason_cast); + } + +#else + (void)reason; + +#endif + + send_message ( session, session->calls[call_index], msg_reject, + session->calls[call_index]->peers[session->calls[call_index]->peer_count - 1] ); + free ( msg_reject ); + + session->calls[call_index]->state = call_hanged_up; + session->calls[call_index]->request_timer_id = + timer_alloc ( session->timer_handler, handle_timeout, session, call_index, m_deftout ); + + pthread_mutex_unlock(&session->mutex); + return 0; +} + + +/** + * @brief Send invite request to friend_id. + * + * @param session Control session. + * @param call_index Call index. + * @param call_type Type of the call. Audio or Video(both audio and video) + * @param rngsec Ringing timeout. + * @param friend_id The friend. + * @return int + */ +int msi_change_csettings(MSISession *session, int32_t call_index, MSICSettings csettings) +{ + pthread_mutex_lock(&session->mutex); + + LOGGER_DEBUG("Changing media on call: %d", call_index); + + if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { + LOGGER_ERROR("Invalid call index!"); + pthread_mutex_unlock(&session->mutex); + return -1; + } + + MSICall *call = session->calls[call_index]; + + if ( call->state != call_active ) { + LOGGER_ERROR("Call is not active!"); + pthread_mutex_unlock(&session->mutex); + return -1; + } + + MSICSettings *local = &call->csettings_local; + + if ( + local->call_type == csettings.call_type && + local->video_bitrate == csettings.video_bitrate && + local->max_video_width == csettings.max_video_width && + local->max_video_height == csettings.max_video_height && + local->audio_bitrate == csettings.audio_bitrate && + local->audio_frame_duration == csettings.audio_frame_duration && + local->audio_sample_rate == csettings.audio_sample_rate && + local->audio_channels == csettings.audio_channels ) { + LOGGER_ERROR("Call is already set accordingly!"); + pthread_mutex_unlock(&session->mutex); + return -1; + } + + *local = csettings; + + MSIMessage *msg_invite = msi_new_message ( TypeRequest, invite ); + + msi_msg_set_csettings ( msg_invite, local ); + send_message ( session, call, msg_invite, call->peers[0] ); + free ( msg_invite ); + + LOGGER_DEBUG("Request for media change sent"); + + pthread_mutex_unlock(&session->mutex); + + return 0; +} + + +/** + * @brief Terminate the current call. + * + * @param session Control session. + * @param call_id To which call is this action handled. + * @return int + */ +int msi_stopcall ( MSISession *session, int32_t call_index ) +{ + pthread_mutex_lock(&session->mutex); + LOGGER_DEBUG("Session: %p Stopping call index: %u", session, call_index); + + if ( call_index < 0 || call_index >= session->max_calls || !session->calls[call_index] ) { + pthread_mutex_unlock(&session->mutex); + return -1; + } + + /* just terminate it */ + + terminate_call ( session, session->calls[call_index] ); + + pthread_mutex_unlock(&session->mutex); + return 0; +} diff --git a/protocols/Tox/toxcore/toxav/msi.h b/protocols/Tox/toxcore/toxav/msi.h new file mode 100644 index 0000000000..64fa08819a --- /dev/null +++ b/protocols/Tox/toxcore/toxav/msi.h @@ -0,0 +1,267 @@ +/** msi.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 <http://www.gnu.org/licenses/>. + * + */ + +#ifndef __TOXMSI +#define __TOXMSI + +#include <inttypes.h> +#include <pthread.h> + +#include "../toxcore/Messenger.h" + +typedef uint8_t MSICallIDType[12]; +typedef uint8_t MSIReasonStrType[255]; +typedef void ( *MSICallbackType ) ( void *agent, int32_t call_idx, void *arg ); + + +/** + * @brief Call type identifier. Also used as rtp callback prefix. + */ +typedef enum { + type_audio = 192, + type_video +} MSICallType; + + +/** + * @brief Call state identifiers. + */ +typedef enum { + call_inviting, /* when sending call invite */ + call_starting, /* when getting call invite */ + call_active, + call_hold, + call_hanged_up + +} MSICallState; + + +/** + * @brief Encoding settings. + */ +typedef struct _MSICodecSettings { + MSICallType call_type; + + uint32_t video_bitrate; /* In kbits/s */ + uint16_t max_video_width; /* In px */ + uint16_t max_video_height; /* In px */ + + uint32_t audio_bitrate; /* In bits/s */ + uint16_t audio_frame_duration; /* In ms */ + uint32_t audio_sample_rate; /* In Hz */ + uint32_t audio_channels; +} MSICSettings; + + +/** + * @brief Callbacks ids that handle the states + */ +typedef enum { + /* Requests */ + MSI_OnInvite, + MSI_OnStart, + MSI_OnCancel, + MSI_OnReject, + MSI_OnEnd, + + /* Responses */ + MSI_OnRinging, + MSI_OnStarting, + MSI_OnEnding, + + /* Protocol */ + MSI_OnRequestTimeout, + MSI_OnPeerTimeout, + MSI_OnMediaChange +} MSICallbackID; + +/** + * @brief Callbacks container + */ +typedef struct _MSICallbackCont { + MSICallbackType function; + void *data; +} MSICallbackCont; + +/** + * @brief The call struct. + * + */ +typedef struct _MSICall { /* Call info structure */ + struct _MSISession *session; /* Session pointer */ + + MSICallState state; + + MSICSettings csettings_local; /* Local call settings */ + MSICSettings *csettings_peer; /* Peers call settings */ + + MSICallIDType id; /* Random value identifying the call */ + + int ringing_tout_ms; /* Ringing timeout in ms */ + + int request_timer_id; /* Timer id for outgoing request/action */ + int ringing_timer_id; /* Timer id for ringing timeout */ + + + pthread_mutex_t mutex; /* */ + uint32_t *peers; + uint16_t peer_count; + + int32_t call_idx; /* Index of this call in MSISession */ +} MSICall; + + +/** + * @brief Control session struct + * + */ +typedef struct _MSISession { + + /* Call handlers */ + MSICall **calls; + int32_t max_calls; + + void *agent_handler; + Messenger *messenger_handle; + + uint32_t frequ; + uint32_t call_timeout; /* Time of the timeout for some action to end; 0 if infinite */ + + pthread_mutex_t mutex; + + void *timer_handler; + MSICallbackCont callbacks[11]; /* Callbacks used by this session */ +} MSISession; + +/** + * @brief Callback setter. + * + * @param session The container. + * @param callback The callback. + * @param id The id. + * @return void + */ +void msi_register_callback(MSISession *session, MSICallbackType callback, MSICallbackID id, void *userdata); + + +/** + * @brief Start the control session. + * + * @param messenger Tox* object. + * @param max_calls Amount of calls possible + * @return MSISession* The created session. + * @retval NULL Error occurred. + */ +MSISession *msi_init_session ( Messenger *messenger, int32_t max_calls ); + + +/** + * @brief Terminate control session. + * + * @param session The session + * @return int + */ +int msi_terminate_session ( MSISession *session ); + + +/** + * @brief Send invite request to friend_id. + * + * @param session Control session. + * @param call_index Set to new call index. + * @param call_type Type of the call. Audio or Video(both audio and video) + * @param rngsec Ringing timeout. + * @param friend_id The friend. + * @return int + */ +int msi_invite ( MSISession *session, int32_t *call_index, MSICSettings csettings, uint32_t rngsec, + uint32_t friend_id ); + + +/** + * @brief Hangup active call. + * + * @param session Control session. + * @param call_index To which call is this action handled. + * @return int + * @retval -1 Error occurred. + * @retval 0 Success. + */ +int msi_hangup ( MSISession *session, int32_t call_index ); + + +/** + * @brief Answer active call request. + * + * @param session Control session. + * @param call_index To which call is this action handled. + * @param call_type Answer with Audio or Video(both). + * @return int + */ +int msi_answer ( MSISession *session, int32_t call_index, MSICSettings csettings ); + + +/** + * @brief Cancel request. + * + * @param session Control session. + * @param call_index To which call is this action handled. + * @param peer To which peer. + * @param reason Set optional reason header. Pass NULL if none. + * @return int + */ +int msi_cancel ( MSISession *session, int32_t call_index, uint32_t peer, const char *reason ); + + +/** + * @brief Reject request. + * + * @param session Control session. + * @param call_index To which call is this action handled. + * @param reason Set optional reason header. Pass NULL if none. + * @return int + */ +int msi_reject ( MSISession *session, int32_t call_index, const char *reason ); + + +/** + * @brief Send invite request to friend_id. + * + * @param session Control session. + * @param call_index Call index. + * @param call_type Type of the call. Audio or Video(both audio and video) + * @param rngsec Ringing timeout. + * @param friend_id The friend. + * @return int + */ +int msi_change_csettings ( MSISession *session, int32_t call_index, MSICSettings csettings ); + + +/** + * @brief Terminate the current call. + * + * @param session Control session. + * @param call_index To which call is this action handled. + * @return int + */ +int msi_stopcall ( MSISession *session, int32_t call_index ); + +#endif /* __TOXMSI */ diff --git a/protocols/Tox/toxcore/toxav/rtp.c b/protocols/Tox/toxcore/toxav/rtp.c new file mode 100644 index 0000000000..de6c9c418c --- /dev/null +++ b/protocols/Tox/toxcore/toxav/rtp.c @@ -0,0 +1,600 @@ +/** rtp.c + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "../toxcore/logger.h" +#include "../toxcore/util.h" + +#include "rtp.h" +#include <stdlib.h> +void toxav_handle_packet(RTPSession *_session, RTPMessage *_msg); + +#define size_32 4 + +#define ADD_FLAG_VERSION(_h, _v) do { ( _h->flags ) &= 0x3F; ( _h->flags ) |= ( ( ( _v ) << 6 ) & 0xC0 ); } while(0) +#define ADD_FLAG_PADDING(_h, _v) do { if ( _v > 0 ) _v = 1; ( _h->flags ) &= 0xDF; ( _h->flags ) |= ( ( ( _v ) << 5 ) & 0x20 ); } while(0) +#define ADD_FLAG_EXTENSION(_h, _v) do { if ( _v > 0 ) _v = 1; ( _h->flags ) &= 0xEF;( _h->flags ) |= ( ( ( _v ) << 4 ) & 0x10 ); } while(0) +#define ADD_FLAG_CSRCC(_h, _v) do { ( _h->flags ) &= 0xF0; ( _h->flags ) |= ( ( _v ) & 0x0F ); } while(0) +#define ADD_SETTING_MARKER(_h, _v) do { if ( _v > 1 ) _v = 1; ( _h->marker_payloadt ) &= 0x7F; ( _h->marker_payloadt ) |= ( ( ( _v ) << 7 ) /*& 0x80 */ ); } while(0) +#define ADD_SETTING_PAYLOAD(_h, _v) do { if ( _v > 127 ) _v = 127; ( _h->marker_payloadt ) &= 0x80; ( _h->marker_payloadt ) |= ( ( _v ) /* & 0x7F */ ); } while(0) + +#define GET_FLAG_VERSION(_h) (( _h->flags & 0xd0 ) >> 6) +#define GET_FLAG_PADDING(_h) (( _h->flags & 0x20 ) >> 5) +#define GET_FLAG_EXTENSION(_h) (( _h->flags & 0x10 ) >> 4) +#define GET_FLAG_CSRCC(_h) ( _h->flags & 0x0f ) +#define GET_SETTING_MARKER(_h) (( _h->marker_payloadt ) >> 7) +#define GET_SETTING_PAYLOAD(_h) ((_h->marker_payloadt) & 0x7f) + +/** + * @brief Checks if message came in late. + * + * @param session Control session. + * @param msg The message. + * @return int + * @retval -1 The message came in order. + * @retval 0 The message came late. + */ +inline__ int check_late_message (RTPSession *session, RTPMessage *msg) +{ + /* + * Check Sequence number. If this new msg has lesser number then the session->rsequnum + * it shows that the message came in late. Also check timestamp to be 100% certain. + * + */ + return ( msg->header->sequnum < session->rsequnum && msg->header->timestamp < session->timestamp ) ? 0 : -1; +} + + +/** + * @brief Extracts header from payload. + * + * @param payload The payload. + * @param length The size of payload. + * @return RTPHeader* Extracted header. + * @retval NULL Error occurred while extracting header. + */ +RTPHeader *extract_header ( const uint8_t *payload, int length ) +{ + if ( !payload || !length ) { + LOGGER_WARNING("No payload to extract!"); + return NULL; + } + + RTPHeader *_retu = calloc(1, sizeof (RTPHeader)); + + if ( !_retu ) { + LOGGER_WARNING("Alloc failed! Program might misbehave!"); + return NULL; + } + + bytes_to_U16(&_retu->sequnum, payload); + + const uint8_t *_it = payload + 2; + + _retu->flags = *_it; + ++_it; + + /* This indicates if the first 2 bits are valid. + * Now it may happen that this is out of order but + * it cuts down chances of parsing some invalid value + */ + + if ( GET_FLAG_VERSION(_retu) != RTP_VERSION ) { + /* Deallocate */ + LOGGER_WARNING("Invalid version!"); + free(_retu); + return NULL; + } + + /* + * Added a check for the size of the header little sooner so + * I don't need to parse the other stuff if it's bad + */ + uint8_t _cc = GET_FLAG_CSRCC ( _retu ); + int _length = 12 /* Minimum header len */ + ( _cc * 4 ); + + if ( length < _length ) { + /* Deallocate */ + LOGGER_WARNING("Length invalid!"); + free(_retu); + return NULL; + } + + memset(_retu->csrc, 0, 16 * sizeof (uint32_t)); + + _retu->marker_payloadt = *_it; + ++_it; + _retu->length = _length; + + + bytes_to_U32(&_retu->timestamp, _it); + _it += 4; + bytes_to_U32(&_retu->ssrc, _it); + + uint8_t _x; + + for ( _x = 0; _x < _cc; _x++ ) { + _it += 4; + bytes_to_U32(&(_retu->csrc[_x]), _it); + } + + return _retu; +} + +/** + * @brief Extracts external header from payload. Must be called AFTER extract_header()! + * + * @param payload The ITERATED payload. + * @param length The size of payload. + * @return RTPExtHeader* Extracted extension header. + * @retval NULL Error occurred while extracting extension header. + */ +RTPExtHeader *extract_ext_header ( const uint8_t *payload, uint16_t length ) +{ + const uint8_t *_it = payload; + + RTPExtHeader *_retu = calloc(1, sizeof (RTPExtHeader)); + + if ( !_retu ) { + LOGGER_WARNING("Alloc failed! Program might misbehave!"); + return NULL; + } + + uint16_t _ext_length; + bytes_to_U16(&_ext_length, _it); + _it += 2; + + + if ( length < ( _ext_length * sizeof(uint32_t) ) ) { + LOGGER_WARNING("Length invalid!"); + free(_retu); + return NULL; + } + + _retu->length = _ext_length; + bytes_to_U16(&_retu->type, _it); + _it += 2; + + if ( !(_retu->table = calloc(_ext_length, sizeof (uint32_t))) ) { + LOGGER_WARNING("Alloc failed! Program might misbehave!"); + free(_retu); + return NULL; + } + + uint16_t _x; + + for ( _x = 0; _x < _ext_length; _x++ ) { + _it += 4; + bytes_to_U32(&(_retu->table[_x]), _it); + } + + return _retu; +} + +/** + * @brief Adds header to payload. Make sure _payload_ has enough space. + * + * @param header The header. + * @param payload The payload. + * @return uint8_t* Iterated position. + */ +uint8_t *add_header ( RTPHeader *header, uint8_t *payload ) +{ + uint8_t _cc = GET_FLAG_CSRCC ( header ); + + uint8_t *_it = payload; + + + /* Add sequence number first */ + U16_to_bytes(_it, header->sequnum); + _it += 2; + + *_it = header->flags; + ++_it; + *_it = header->marker_payloadt; + ++_it; + + + U32_to_bytes( _it, header->timestamp); + _it += 4; + U32_to_bytes( _it, header->ssrc); + + uint8_t _x; + + for ( _x = 0; _x < _cc; _x++ ) { + _it += 4; + U32_to_bytes( _it, header->csrc[_x]); + } + + return _it + 4; +} + +/** + * @brief Adds extension header to payload. Make sure _payload_ has enough space. + * + * @param header The header. + * @param payload The payload. + * @return uint8_t* Iterated position. + */ +uint8_t *add_ext_header ( RTPExtHeader *header, uint8_t *payload ) +{ + uint8_t *_it = payload; + + U16_to_bytes(_it, header->length); + _it += 2; + U16_to_bytes(_it, header->type); + _it -= 2; /* Return to 0 position */ + + if ( header->table ) { + uint16_t _x; + + for ( _x = 0; _x < header->length; _x++ ) { + _it += 4; + U32_to_bytes(_it, header->table[_x]); + } + } + + return _it + 4; +} + +/** + * @brief Builds header from control session values. + * + * @param session Control session. + * @return RTPHeader* Created header. + */ +RTPHeader *build_header ( RTPSession *session ) +{ + RTPHeader *_retu = calloc ( 1, sizeof (RTPHeader) ); + + if ( !_retu ) { + LOGGER_WARNING("Alloc failed! Program might misbehave!"); + return NULL; + } + + ADD_FLAG_VERSION ( _retu, session->version ); + ADD_FLAG_PADDING ( _retu, session->padding ); + ADD_FLAG_EXTENSION ( _retu, session->extension ); + ADD_FLAG_CSRCC ( _retu, session->cc ); + ADD_SETTING_MARKER ( _retu, session->marker ); + ADD_SETTING_PAYLOAD ( _retu, session->payload_type ); + + _retu->sequnum = session->sequnum; + _retu->timestamp = current_time_monotonic(); /* milliseconds */ + _retu->ssrc = session->ssrc; + + int i; + + for ( i = 0; i < session->cc; i++ ) + _retu->csrc[i] = session->csrc[i]; + + _retu->length = 12 /* Minimum header len */ + ( session->cc * size_32 ); + + return _retu; +} + + +/** + * @brief Parses data into RTPMessage struct. Stores headers separately from the payload data + * and so the length variable is set accordingly. _sequnum_ argument is + * passed by the handle_packet() since it's parsed already. + * + * @param session Control session. + * @param sequnum Sequence number that's parsed from payload in handle_packet() + * @param data Payload data. + * @param length Payload size. + * @return RTPMessage* + * @retval NULL Error occurred. + */ +RTPMessage *msg_parse ( const uint8_t *data, int length ) +{ + RTPMessage *_retu = calloc(1, sizeof (RTPMessage)); + + _retu->header = extract_header ( data, length ); /* It allocates memory and all */ + + if ( !_retu->header ) { + LOGGER_WARNING("Header failed to extract!"); + free(_retu); + return NULL; + } + + uint16_t _from_pos = _retu->header->length; + _retu->length = length - _from_pos; + + + + if ( GET_FLAG_EXTENSION ( _retu->header ) ) { + _retu->ext_header = extract_ext_header ( data + _from_pos, length ); + + if ( _retu->ext_header ) { + _retu->length -= ( 4 /* Minimum ext header len */ + _retu->ext_header->length * size_32 ); + _from_pos += ( 4 /* Minimum ext header len */ + _retu->ext_header->length * size_32 ); + } else { /* Error */ + LOGGER_WARNING("Ext Header failed to extract!"); + rtp_free_msg(NULL, _retu); + return NULL; + } + } else { + _retu->ext_header = NULL; + } + + if ( length - _from_pos <= MAX_RTP_SIZE ) + memcpy ( _retu->data, data + _from_pos, length - _from_pos ); + else { + LOGGER_WARNING("Invalid length!"); + rtp_free_msg(NULL, _retu); + return NULL; + } + + _retu->next = NULL; + + return _retu; +} + +/** + * @brief Callback for networking core. + * + * @param object RTPSession object. + * @param ip_port Where the message comes from. + * @param data Message data. + * @param length Message length. + * @return int + * @retval -1 Error occurred. + * @retval 0 Success. + */ +int rtp_handle_packet ( void *object, const uint8_t *data, uint32_t length ) +{ + RTPSession *_session = object; + RTPMessage *_msg; + + if ( !_session || length < 13 ) { /* 12 is the minimum length for rtp + desc. byte */ + LOGGER_WARNING("No session or invalid length of received buffer!"); + return -1; + } + + _msg = msg_parse ( data + 1, length - 1 ); + + if ( !_msg ) { + LOGGER_WARNING("Could not parse message!"); + return -1; + } + + /* Check if message came in late */ + if ( check_late_message(_session, _msg) < 0 ) { /* Not late */ + _session->rsequnum = _msg->header->sequnum; + _session->timestamp = _msg->header->timestamp; + } + + toxav_handle_packet(_session, _msg); + + return 0; +} + + + +/** + * @brief Stores headers and payload data in one container ( data ) + * and the length is set accordingly. Returned message is used for sending _only_. + * + * @param session The control session. + * @param data Payload data to send ( This is what you pass ). + * @param length Size of the payload data. + * @return RTPMessage* Created message. + * @retval NULL Error occurred. + */ +RTPMessage *rtp_new_message ( RTPSession *session, const uint8_t *data, uint32_t length ) +{ + if ( !session ) { + LOGGER_WARNING("No session!"); + return NULL; + } + + uint8_t *_from_pos; + RTPMessage *_retu = calloc(1, sizeof (RTPMessage)); + + if ( !_retu ) { + LOGGER_WARNING("Alloc failed! Program might misbehave!"); + return NULL; + } + + /* Sets header values and copies the extension header in _retu */ + _retu->header = build_header ( session ); /* It allocates memory and all */ + _retu->ext_header = session->ext_header; + + + uint32_t _total_length = length + _retu->header->length + 1; + + _retu->data[0] = session->prefix; + + if ( _retu->ext_header ) { + _total_length += ( 4 /* Minimum ext header len */ + _retu->ext_header->length * size_32 ); + + _from_pos = add_header ( _retu->header, _retu->data + 1 ); + _from_pos = add_ext_header ( _retu->ext_header, _from_pos + 1 ); + } else { + _from_pos = add_header ( _retu->header, _retu->data + 1 ); + } + + /* + * Parses the extension header into the message + * Of course if any + */ + + /* Appends _data on to _retu->_data */ + memcpy ( _from_pos, data, length ); + + _retu->length = _total_length; + + _retu->next = NULL; + + return _retu; +} + + +/** + * @brief Sends data to _RTPSession::dest + * + * @param session The session. + * @param messenger Tox* object. + * @param data The payload. + * @param length Size of the payload. + * @return int + * @retval -1 On error. + * @retval 0 On success. + */ +int rtp_send_msg ( RTPSession *session, Messenger *messenger, const uint8_t *data, uint16_t length ) +{ + RTPMessage *msg = rtp_new_message (session, data, length); + + if ( !msg ) { + LOGGER_WARNING("No session!"); + return -1; + } + + if ( -1 == send_custom_lossy_packet(messenger, session->dest, msg->data, msg->length) ) { + LOGGER_WARNING("Failed to send full packet! std error: %s", strerror(errno)); + rtp_free_msg ( session, msg ); + return -1; + } + + + /* Set sequ number */ + session->sequnum = session->sequnum >= MAX_SEQU_NUM ? 0 : session->sequnum + 1; + rtp_free_msg ( session, msg ); + + return 0; +} + + +/** + * @brief Speaks for it self. + * + * @param session The control session msg belongs to. You set it as NULL when freeing recved messages. + * Otherwise set it to session the message was created from. + * @param msg The message. + * @return void + */ +void rtp_free_msg ( RTPSession *session, RTPMessage *msg ) +{ + if ( !session ) { + if ( msg->ext_header ) { + free ( msg->ext_header->table ); + free ( msg->ext_header ); + } + } else { + if ( msg->ext_header && session->ext_header != msg->ext_header ) { + free ( msg->ext_header->table ); + free ( msg->ext_header ); + } + } + + free ( msg->header ); + free ( msg ); +} + +/** + * @brief Must be called before calling any other rtp function. It's used + * to initialize RTP control session. + * + * @param payload_type Type of payload used to send. You can use values in toxmsi.h::MSICallType + * @param messenger Tox* object. + * @param friend_num Friend id. + * @return RTPSession* Created control session. + * @retval NULL Error occurred. + */ +RTPSession *rtp_init_session ( int payload_type, Messenger *messenger, int friend_num ) +{ + RTPSession *_retu = calloc(1, sizeof(RTPSession)); + + if ( !_retu ) { + LOGGER_WARNING("Alloc failed! Program might misbehave!"); + return NULL; + } + + if ( -1 == custom_lossy_packet_registerhandler(messenger, friend_num, payload_type, rtp_handle_packet, _retu)) { + LOGGER_ERROR("Error setting custom register handler for rtp session"); + free(_retu); + return NULL; + } + + LOGGER_DEBUG("Registered packet handler: pt: %d; fid: %d", payload_type, friend_num); + + _retu->version = RTP_VERSION; /* It's always 2 */ + _retu->padding = 0; /* If some additional data is needed about the packet */ + _retu->extension = 0; /* If extension to header is needed */ + _retu->cc = 1; /* Amount of contributors */ + _retu->csrc = NULL; /* Container */ + _retu->ssrc = random_int(); + _retu->marker = 0; + _retu->payload_type = payload_type % 128; + + _retu->dest = friend_num; + + _retu->rsequnum = _retu->sequnum = 0; + + _retu->ext_header = NULL; /* When needed allocate */ + + + if ( !(_retu->csrc = calloc(1, sizeof (uint32_t))) ) { + LOGGER_WARNING("Alloc failed! Program might misbehave!"); + free(_retu); + return NULL; + } + + _retu->csrc[0] = _retu->ssrc; /* Set my ssrc to the list receive */ + + /* Also set payload type as prefix */ + _retu->prefix = payload_type; + + /* + * + */ + return _retu; +} + + +/** + * @brief Terminate the session. + * + * @param session The session. + * @param messenger The messenger who owns the session + * @return int + * @retval -1 Error occurred. + * @retval 0 Success. + */ +void rtp_terminate_session ( RTPSession *session, Messenger *messenger ) +{ + if ( !session ) return; + + custom_lossy_packet_registerhandler(messenger, session->dest, session->prefix, NULL, NULL); + + free ( session->ext_header ); + free ( session->csrc ); + + LOGGER_DEBUG("Terminated RTP session: %p", session); + + /* And finally free session */ + free ( session ); + +} diff --git a/protocols/Tox/toxcore/toxav/rtp.h b/protocols/Tox/toxcore/toxav/rtp.h new file mode 100644 index 0000000000..d57c5ef720 --- /dev/null +++ b/protocols/Tox/toxcore/toxav/rtp.h @@ -0,0 +1,196 @@ +/** rtp.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 <http://www.gnu.org/licenses/>. + * + */ + +#ifndef __TOXRTP +#define __TOXRTP + +#define RTP_VERSION 2 +#include <inttypes.h> +#include <pthread.h> + +#include "../toxcore/util.h" +#include "../toxcore/network.h" +#include "../toxcore/net_crypto.h" +#include "../toxcore/Messenger.h" + +#define MAX_SEQU_NUM 65535 +#define MAX_RTP_SIZE 65535 + +/** + * @brief Standard rtp header + * + */ + +typedef struct _RTPHeader { + uint8_t flags; /* Version(2),Padding(1), Ext(1), Cc(4) */ + uint8_t marker_payloadt; /* Marker(1), PlayLoad Type(7) */ + uint16_t sequnum; /* Sequence Number */ + uint32_t timestamp; /* Timestamp */ + uint32_t ssrc; /* SSRC */ + uint32_t csrc[16]; /* CSRC's table */ + uint32_t length; /* Length of the header in payload string. */ + +} RTPHeader; + + +/** + * @brief Standard rtp extension header. + * + */ +typedef struct _RTPExtHeader { + uint16_t type; /* Extension profile */ + uint16_t length; /* Number of extensions */ + uint32_t *table; /* Extension's table */ + +} RTPExtHeader; + + +/** + * @brief Standard rtp message. + * + */ +typedef struct _RTPMessage { + RTPHeader *header; + RTPExtHeader *ext_header; + + uint8_t data[MAX_RTP_SIZE]; + uint32_t length; + + struct _RTPMessage *next; +} RTPMessage; + + +/** + * @brief Our main session descriptor. + * It measures the session variables and controls + * the entire session. There are functions for manipulating + * the session so tend to use those instead of directly modifying + * session parameters. + * + */ +typedef struct _RTPSession { + uint8_t version; + uint8_t padding; + uint8_t extension; + uint8_t cc; + uint8_t marker; + uint8_t payload_type; + uint16_t sequnum; /* Set when sending */ + uint16_t rsequnum; /* Check when recving msg */ + uint32_t timestamp; + uint32_t ssrc; + uint32_t *csrc; + + /* If some additional data must be sent via message + * apply it here. Only by allocating this member you will be + * automatically placing it within a message. + */ + RTPExtHeader *ext_header; + + /* Msg prefix for core to know when recving */ + uint8_t prefix; + + int dest; + int32_t call_index; + struct _ToxAv *av; + +} RTPSession; + + +/** + * @brief Release all messages held by session. + * + * @param session The session. + * @return int + * @retval -1 Error occurred. + * @retval 0 Success. + */ +int rtp_release_session_recv ( RTPSession *session ); + + +/** + * @brief Call this to change queue limit + * + * @param session The session + * @param limit new limit + * @return void + */ +void rtp_queue_adjust_limit ( RTPSession *session, uint64_t limit ); + +/** + * @brief Get's oldest message in the list. + * + * @param session Where the list is. + * @return RTPMessage* The message. You need to call rtp_msg_free() to free it. + * @retval NULL No messages in the list, or no list. + */ +RTPMessage *rtp_recv_msg ( RTPSession *session ); + + +/** + * @brief Sends msg to _RTPSession::dest + * + * @param session The session. + * @param msg The message + * @param messenger Tox* object. + * @return int + * @retval -1 On error. + * @retval 0 On success. + */ +int rtp_send_msg ( RTPSession *session, Messenger *messenger, const uint8_t *data, uint16_t length ); + + +/** + * @brief Speaks for it self. + * + * @param session The control session msg belongs to. It can be NULL. + * @param msg The message. + * @return void + */ +void rtp_free_msg ( RTPSession *session, RTPMessage *msg ); + +/** + * @brief Must be called before calling any other rtp function. It's used + * to initialize RTP control session. + * + * @param payload_type Type of payload used to send. You can use values in toxmsi.h::MSICallType + * @param messenger Tox* object. + * @param friend_num Friend id. + * @return RTPSession* Created control session. + * @retval NULL Error occurred. + */ +RTPSession *rtp_init_session ( int payload_type, Messenger *messenger, int friend_num ); + + +/** + * @brief Terminate the session. + * + * @param session The session. + * @param messenger The messenger who owns the session + * @return int + * @retval -1 Error occurred. + * @retval 0 Success. + */ +void rtp_terminate_session ( RTPSession *session, Messenger *messenger ); + + + +#endif /* __TOXRTP */ diff --git a/protocols/Tox/toxcore/toxav/toxav.c b/protocols/Tox/toxcore/toxav/toxav.c new file mode 100644 index 0000000000..cd0ec70ed7 --- /dev/null +++ b/protocols/Tox/toxcore/toxav/toxav.c @@ -0,0 +1,1148 @@ +/** toxav.c + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + + +#define _GNU_SOURCE /* implicit declaration warning */ + +#include "rtp.h" +#include "codec.h" +#include "msi.h" +#include "toxav.h" + +#include "../toxcore/logger.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +/* Assume 24 fps*/ +#define MAX_ENCODE_TIME_US ((1000 / 24) * 1000) +#define MAX_DECODE_TIME_US 0 + +#define MAX_VIDEOFRAME_SIZE 0x40000 /* 256KiB */ +#define VIDEOFRAME_PIECE_SIZE 0x500 /* 1.25 KiB*/ +#define VIDEOFRAME_HEADER_SIZE 0x2 + + +#define inline__ inline __attribute__((always_inline)) + +/* call index invalid: true if invalid */ +#define cii(c_idx, session) (c_idx < 0 || c_idx >= session->max_calls) + + +const ToxAvCSettings av_DefaultSettings = { + TypeAudio, + + 500, + 1280, + 720, + + 64000, + 20, + 48000, + 1 +}; + +const uint32_t av_jbufdc = 3; +const uint32_t av_VADd = 40; + + +static const uint8_t audio_index = 0, video_index = 1; + +typedef struct { + uint32_t size; + uint8_t data[0]; +} DECODE_PACKET; + +#define VIDEO_DECODE_QUEUE_SIZE 2 +#define AUDIO_DECODE_QUEUE_SIZE 16 + +typedef struct _CallSpecific { + RTPSession *crtps[2]; /** Audio is first and video is second */ + CodecState *cs;/** Each call have its own encoders and decoders. + * You can, but don't have to, reuse encoders for + * multiple calls. If you choose to reuse encoders, + * make sure to also reuse encoded payload for every call. + * Decoders have to be unique for each call. FIXME: Now add refcounted encoders and + * reuse them really. + */ + JitterBuffer *j_buf; /** Jitter buffer for audio */ + + uint32_t frame_limit; /* largest address written to in frame_buf for current input frame*/ + uint8_t frame_id, frame_outid; /* id of input and output video frame */ + void *frame_buf; /* buffer for split video payloads */ + + _Bool call_active; + pthread_mutex_t mutex; + + /* used in the "decode on another thread" system */ + volatile _Bool exit, decoding; + uint8_t video_decode_read, video_decode_write, audio_decode_read, audio_decode_write; + pthread_mutex_t decode_cond_mutex; + pthread_cond_t decode_cond; + DECODE_PACKET *volatile video_decode_queue[VIDEO_DECODE_QUEUE_SIZE]; + DECODE_PACKET *volatile audio_decode_queue[AUDIO_DECODE_QUEUE_SIZE]; +} CallSpecific; + +struct _ToxAv { + Messenger *messenger; + MSISession *msi_session; /** Main msi session */ + CallSpecific *calls; /** Per-call params */ + + void (*audio_callback)(ToxAv *, int32_t, int16_t *, int, void *); + void (*video_callback)(ToxAv *, int32_t, vpx_image_t *, void *); + + void *audio_callback_userdata; + void *video_callback_userdata; + + uint32_t max_calls; +}; + +static void *toxav_decoding(void *arg); + +static MSICSettings msicsettings_cast (const ToxAvCSettings *from) +{ + MSICSettings csettings; + csettings.call_type = from->call_type; + + csettings.video_bitrate = from->video_bitrate; + csettings.max_video_width = from->max_video_width; + csettings.max_video_height = from->max_video_height; + + csettings.audio_bitrate = from->audio_bitrate; + csettings.audio_frame_duration = from->audio_frame_duration; + csettings.audio_sample_rate = from->audio_sample_rate; + csettings.audio_channels = from->audio_channels; + + return csettings; +} + +static ToxAvCSettings toxavcsettings_cast (const MSICSettings *from) +{ + ToxAvCSettings csettings; + csettings.call_type = from->call_type; + + csettings.video_bitrate = from->video_bitrate; + csettings.max_video_width = from->max_video_width; + csettings.max_video_height = from->max_video_height; + + csettings.audio_bitrate = from->audio_bitrate; + csettings.audio_frame_duration = from->audio_frame_duration; + csettings.audio_sample_rate = from->audio_sample_rate; + csettings.audio_channels = from->audio_channels; + + return csettings; +} + +/** + * @brief Start new A/V session. There can only be one session at the time. If you register more + * it will result in undefined behaviour. + * + * @param messenger The messenger handle. + * @param userdata The agent handling A/V session (i.e. phone). + * @param video_width Width of video frame. + * @param video_height Height of video frame. + * @return ToxAv* + * @retval NULL On error. + */ +ToxAv *toxav_new( Tox *messenger, int32_t max_calls) +{ + ToxAv *av = calloc ( sizeof(ToxAv), 1); + + if (av == NULL) { + LOGGER_WARNING("Allocation failed!"); + return NULL; + } + + av->messenger = (Messenger *)messenger; + av->msi_session = msi_init_session(av->messenger, max_calls); + av->msi_session->agent_handler = av; + av->calls = calloc(sizeof(CallSpecific), max_calls); + av->max_calls = max_calls; + + return av; +} + +/** + * @brief Remove A/V session. + * + * @param av Handler. + * @return void + */ +void toxav_kill ( ToxAv *av ) +{ + uint32_t i; + + for (i = 0; i < av->max_calls; i ++) { + if ( av->calls[i].crtps[audio_index] ) + rtp_terminate_session(av->calls[i].crtps[audio_index], av->msi_session->messenger_handle); + + + if ( av->calls[i].crtps[video_index] ) + rtp_terminate_session(av->calls[i].crtps[video_index], av->msi_session->messenger_handle); + + + + if ( av->calls[i].j_buf ) terminate_queue(av->calls[i].j_buf); + + if ( av->calls[i].cs ) codec_terminate_session(av->calls[i].cs); + } + + msi_terminate_session(av->msi_session); + + free(av->calls); + free(av); +} + +/** + * @brief Register callback for call state. + * + * @param av Handler. + * @param callback The callback + * @param id One of the ToxAvCallbackID values + * @return void + */ +void toxav_register_callstate_callback ( ToxAv *av, ToxAVCallback callback, ToxAvCallbackID id, void *userdata ) +{ + msi_register_callback(av->msi_session, (MSICallbackType)callback, (MSICallbackID) id, userdata); +} + +/** + * @brief Register callback for recieving audio data + * + * @param callback The callback + * @return void + */ +void toxav_register_audio_recv_callback (ToxAv *av, void (*callback)(ToxAv *, int32_t, int16_t *, int, void *), + void *user_data) +{ + av->audio_callback = callback; + av->audio_callback_userdata = user_data; +} + +/** + * @brief Register callback for recieving video data + * + * @param callback The callback + * @return void + */ +void toxav_register_video_recv_callback (ToxAv *av, void (*callback)(ToxAv *, int32_t, vpx_image_t *, void *), + void *user_data) +{ + av->video_callback = callback; + av->video_callback_userdata = user_data; +} + +/** + * @brief Call user. Use its friend_id. + * + * @param av Handler. + * @param user The user. + * @param call_type Call type. + * @param ringing_seconds Ringing timeout. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_call (ToxAv *av, int32_t *call_index, int user, const ToxAvCSettings *csettings, int ringing_seconds ) +{ + return msi_invite(av->msi_session, call_index, msicsettings_cast(csettings), ringing_seconds * 1000, user); +} + +/** + * @brief Hangup active call. + * + * @param av Handler. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_hangup ( ToxAv *av, int32_t call_index ) +{ + if ( cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] ) { + return ErrorNoCall; + } + + if ( av->msi_session->calls[call_index]->state != call_active ) { + return ErrorInvalidState; + } + + return msi_hangup(av->msi_session, call_index); +} + +/** + * @brief Answer incomming call. + * + * @param av Handler. + * @param call_type Answer with... + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_answer ( ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings ) +{ + if ( cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] ) { + return ErrorNoCall; + } + + if ( av->msi_session->calls[call_index]->state != call_starting ) { + return ErrorInvalidState; + } + + return msi_answer(av->msi_session, call_index, msicsettings_cast(csettings)); +} + +/** + * @brief Reject incomming call. + * + * @param av Handler. + * @param reason Optional reason. Set NULL if none. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_reject ( ToxAv *av, int32_t call_index, const char *reason ) +{ + if ( cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] ) { + return ErrorNoCall; + } + + if ( av->msi_session->calls[call_index]->state != call_starting ) { + return ErrorInvalidState; + } + + return msi_reject(av->msi_session, call_index, reason); +} + +/** + * @brief Cancel outgoing request. + * + * @param av Handler. + * @param reason Optional reason. + * @param peer_id peer friend_id + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_cancel ( ToxAv *av, int32_t call_index, int peer_id, const char *reason ) +{ + if ( cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] ) { + return ErrorNoCall; + } + + if ( av->msi_session->calls[call_index]->state != call_inviting ) { + return ErrorInvalidState; + } + + return msi_cancel(av->msi_session, call_index, peer_id, reason); +} + +/** + * @brief Notify peer that we are changing call type + * + * @param av Handler. + * @return int + * @param call_type Change to... + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_change_settings(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings) +{ + if ( cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] ) { + return ErrorNoCall; + } + + return msi_change_csettings(av->msi_session, call_index, msicsettings_cast(csettings)); +} + +/** + * @brief Terminate transmission. Note that transmission will be terminated without informing remote peer. + * + * @param av Handler. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_stop_call ( ToxAv *av, int32_t call_index ) +{ + if ( cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] ) { + return ErrorNoCall; + } + + return msi_stopcall(av->msi_session, call_index); +} + +/** + * @brief Must be call before any RTP transmission occurs. + * + * @param av Handler. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_prepare_transmission ( ToxAv *av, int32_t call_index, uint32_t jbuf_capacity, uint32_t VAD_treshold, + int support_video ) +{ + if ( !av->msi_session || cii(call_index, av->msi_session) || + !av->msi_session->calls[call_index] || !av->msi_session->calls[call_index]->csettings_peer || + av->calls[call_index].call_active) { + LOGGER_ERROR("Error while starting RTP session: invalid call!\n"); + return ErrorInternal; + } + + CallSpecific *call = &av->calls[call_index]; + + call->crtps[audio_index] = + rtp_init_session(type_audio, av->messenger, av->msi_session->calls[call_index]->peers[0]); + + + if ( !call->crtps[audio_index] ) { + LOGGER_ERROR("Error while starting audio RTP session!\n"); + return ErrorInternal; + } + + call->crtps[audio_index]->call_index = call_index; + call->crtps[audio_index]->av = av; + + if ( support_video ) { + call->crtps[video_index] = + rtp_init_session(type_video, av->messenger, av->msi_session->calls[call_index]->peers[0]); + + if ( !call->crtps[video_index] ) { + LOGGER_ERROR("Error while starting video RTP session!\n"); + goto error; + } + + call->crtps[video_index]->call_index = call_index; + call->crtps[video_index]->av = av; + + call->frame_limit = 0; + call->frame_id = 0; + call->frame_outid = 0; + + call->frame_buf = calloc(MAX_VIDEOFRAME_SIZE, 1); + + if (!call->frame_buf) { + LOGGER_WARNING("Frame buffer allocation failed!"); + goto error; + } + + } + + if ( !(call->j_buf = create_queue(jbuf_capacity)) ) { + LOGGER_WARNING("Jitter buffer creaton failed!"); + goto error; + } + + ToxAvCSettings csettings_peer = toxavcsettings_cast(&av->msi_session->calls[call_index]->csettings_peer[0]); + ToxAvCSettings csettings_local = toxavcsettings_cast(&av->msi_session->calls[call_index]->csettings_local); + LOGGER_DEBUG( + "Type: %u \n" + "Video bitrate: %u \n" + "Video height: %u \n" + "Video width: %u \n" + "Audio bitrate: %u \n" + "Audio framedur: %u \n" + "Audio sample rate: %u \n" + "Audio channels: %u \n", + csettings_peer.call_type, + csettings_peer.video_bitrate, + csettings_peer.max_video_height, + csettings_peer.max_video_width, + csettings_peer.audio_bitrate, + csettings_peer.audio_frame_duration, + csettings_peer.audio_sample_rate, + csettings_peer.audio_channels ); + + if ( (call->cs = codec_init_session(csettings_local.audio_bitrate, + csettings_local.audio_frame_duration, + csettings_local.audio_sample_rate, + csettings_local.audio_channels, + csettings_peer.audio_channels, + VAD_treshold, + csettings_local.max_video_width, + csettings_local.max_video_height, + csettings_local.video_bitrate) )) { + + if ( pthread_mutex_init(&call->mutex, NULL) != 0 ) goto error; + + //todo: add error checks + pthread_mutex_init(&call->decode_cond_mutex, NULL); + pthread_cond_init(&call->decode_cond, NULL); + + void **arg = malloc(2 * sizeof(void *)); + arg[0] = av; + arg[1] = call; + + pthread_t temp; + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 1 << 18); + pthread_create(&temp, &attr, toxav_decoding, arg); + pthread_attr_destroy(&attr); + + + LOGGER_WARNING("Got here"); + call->call_active = 1; + + return ErrorNone; + } + +error: + rtp_terminate_session(call->crtps[audio_index], av->messenger); + rtp_terminate_session(call->crtps[video_index], av->messenger); + free(call->frame_buf); + terminate_queue(call->j_buf); + codec_terminate_session(call->cs); + + return ErrorInternal; +} + +/** + * @brief Call this at the end of the transmission. + * + * @param av Handler. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_kill_transmission ( ToxAv *av, int32_t call_index ) +{ + if (cii(call_index, av->msi_session)) { + LOGGER_WARNING("Invalid call index: %d", call_index); + return ErrorNoCall; + } + + CallSpecific *call = &av->calls[call_index]; + + pthread_mutex_lock(&call->mutex); + + if (!call->call_active) { + pthread_mutex_unlock(&call->mutex); + LOGGER_WARNING("Action on inactive call: %d", call_index); + return ErrorNoCall; + } + + + call->call_active = 0; + + rtp_terminate_session(call->crtps[audio_index], av->messenger); + call->crtps[audio_index] = NULL; + rtp_terminate_session(call->crtps[video_index], av->messenger); + call->crtps[video_index] = NULL; + terminate_queue(call->j_buf); + call->j_buf = NULL; + + int i; + DECODE_PACKET *p; + + call->exit = 1; + pthread_mutex_lock(&call->decode_cond_mutex); + pthread_cond_signal(&call->decode_cond); + pthread_cond_wait(&call->decode_cond, &call->decode_cond_mutex); + pthread_mutex_unlock(&call->decode_cond_mutex); + pthread_mutex_destroy(&call->decode_cond_mutex); + pthread_cond_destroy(&call->decode_cond); + + for (i = 0; i != VIDEO_DECODE_QUEUE_SIZE; i++) { + p = call->video_decode_queue[i]; + call->video_decode_queue[i] = NULL; + + if (p) { + free(p); + } + } + + for (i = 0; i != AUDIO_DECODE_QUEUE_SIZE; i++) { + p = call->audio_decode_queue[i]; + call->audio_decode_queue[i] = NULL; + + if (p) { + free(p); + } + } + + codec_terminate_session(call->cs); + call->cs = NULL; + + pthread_mutex_unlock(&call->mutex); + pthread_mutex_destroy(&call->mutex); + + memset(call, 0, sizeof(CallSpecific)); + return ErrorNone; +} + + +/** + * @brief Send RTP payload. + * + * @param av Handler. + * @param type Type of payload. + * @param payload The payload. + * @param length Size of it. + * @return int + * @retval 0 Success. + * @retval -1 Failure. + */ +static int toxav_send_rtp_payload(ToxAv *av, int32_t call_index, ToxAvCallType type, const uint8_t *payload, + unsigned int length) +{ + CallSpecific *call = &av->calls[call_index]; + + if (call->crtps[type - TypeAudio]) { + + if (type == TypeAudio) { + return rtp_send_msg(call->crtps[type - TypeAudio], av->messenger, payload, length); + } else { + if (length == 0 || length > MAX_VIDEOFRAME_SIZE) { + LOGGER_ERROR("Invalid video frame size: %u\n", length); + return ErrorInternal; + } + + /* number of pieces - 1*/ + uint8_t numparts = (length - 1) / VIDEOFRAME_PIECE_SIZE; + + uint8_t load[2 + VIDEOFRAME_PIECE_SIZE]; + load[0] = call->frame_outid++; + load[1] = 0; + + int i; + + for (i = 0; i < numparts; i++) { + memcpy(load + VIDEOFRAME_HEADER_SIZE, payload, VIDEOFRAME_PIECE_SIZE); + payload += VIDEOFRAME_PIECE_SIZE; + + if (rtp_send_msg(call->crtps[type - TypeAudio], av->messenger, + load, VIDEOFRAME_HEADER_SIZE + VIDEOFRAME_PIECE_SIZE) != 0) { + + return ErrorInternal; + } + + load[1]++; + } + + /* remainder = length % VIDEOFRAME_PIECE_SIZE, VIDEOFRAME_PIECE_SIZE if = 0 */ + length = ((length - 1) % VIDEOFRAME_PIECE_SIZE) + 1; + memcpy(load + VIDEOFRAME_HEADER_SIZE, payload, length); + + return rtp_send_msg(call->crtps[type - TypeAudio], av->messenger, load, VIDEOFRAME_HEADER_SIZE + length); + } + } else { + return ErrorNoRtpSession; + } +} + +/** + * @brief Encode and send video packet. + * + * @param av Handler. + * @param input The packet. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_send_video ( ToxAv *av, int32_t call_index, const uint8_t *frame, unsigned int frame_size) +{ + + if (cii(call_index, av->msi_session)) { + LOGGER_WARNING("Invalid call index: %d", call_index); + return ErrorNoCall; + } + + CallSpecific *call = &av->calls[call_index]; + pthread_mutex_lock(&call->mutex); + + + if (!call->call_active) { + pthread_mutex_unlock(&call->mutex); + LOGGER_WARNING("Action on inactive call: %d", call_index); + return ErrorNoCall; + } + + int rc = toxav_send_rtp_payload(av, call_index, TypeVideo, frame, frame_size); + pthread_mutex_unlock(&call->mutex); + + return rc; +} + +/** + * @brief Encode video frame + * + * @param av Handler + * @param dest Where to + * @param dest_max Max size + * @param input What to encode + * @return int + * @retval ToxAvError On error. + * @retval >0 On success + */ +int toxav_prepare_video_frame(ToxAv *av, int32_t call_index, uint8_t *dest, int dest_max, vpx_image_t *input) +{ + if (cii(call_index, av->msi_session)) { + LOGGER_WARNING("Invalid call index: %d", call_index); + return ErrorNoCall; + } + + + CallSpecific *call = &av->calls[call_index]; + pthread_mutex_lock(&call->mutex); + + if (!call->call_active) { + pthread_mutex_unlock(&call->mutex); + LOGGER_WARNING("Action on inactive call: %d", call_index); + return ErrorNoCall; + } + + if (reconfigure_video_encoder_resolution(call->cs, input->d_w, input->d_h) != 0) { + pthread_mutex_unlock(&call->mutex); + return ErrorInternal; + } + + int rc = vpx_codec_encode(&call->cs->v_encoder, input, call->cs->frame_counter, 1, 0, MAX_ENCODE_TIME_US); + + if ( rc != VPX_CODEC_OK) { + LOGGER_ERROR("Could not encode video frame: %s\n", vpx_codec_err_to_string(rc)); + pthread_mutex_unlock(&call->mutex); + return ErrorInternal; + } + + ++call->cs->frame_counter; + + vpx_codec_iter_t iter = NULL; + const vpx_codec_cx_pkt_t *pkt; + int copied = 0; + + while ( (pkt = vpx_codec_get_cx_data(&call->cs->v_encoder, &iter)) ) { + if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { + if ( copied + pkt->data.frame.sz > dest_max ) { + pthread_mutex_unlock(&call->mutex); + return ErrorPacketTooLarge; + } + + memcpy(dest + copied, pkt->data.frame.buf, pkt->data.frame.sz); + copied += pkt->data.frame.sz; + } + } + + pthread_mutex_unlock(&call->mutex); + return copied; +} + +/** + * @brief Send audio frame. + * + * @param av Handler. + * @param data The audio data encoded with toxav_prepare_audio_frame(). + * @param size Its size in number of bytes. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_send_audio ( ToxAv *av, int32_t call_index, const uint8_t *data, unsigned int size) +{ + if (size > MAX_CRYPTO_DATA_SIZE) + return ErrorInternal; + + if (cii(call_index, av->msi_session) || !av->calls[call_index].call_active) { + LOGGER_WARNING("Action on inactive call: %d", call_index); + return ErrorNoCall; + } + + CallSpecific *call = &av->calls[call_index]; + pthread_mutex_lock(&call->mutex); + + + if (!call->call_active) { + pthread_mutex_unlock(&call->mutex); + LOGGER_WARNING("Action on inactive call: %d", call_index); + return ErrorNoCall; + } + + int rc = toxav_send_rtp_payload(av, call_index, TypeAudio, data, size); + pthread_mutex_unlock(&call->mutex); + + return rc; +} + +/** + * @brief Encode audio frame + * + * @param av Handler + * @param dest dest + * @param dest_max Max dest size + * @param frame The frame + * @param frame_size The frame size + * @return int + * @retval ToxAvError On error. + * @retval >0 On success + */ +int toxav_prepare_audio_frame ( ToxAv *av, int32_t call_index, uint8_t *dest, int dest_max, const int16_t *frame, + int frame_size) +{ + if (cii(call_index, av->msi_session) || !av->calls[call_index].call_active) { + LOGGER_WARNING("Action on inactive call: %d", call_index); + return ErrorNoCall; + } + + CallSpecific *call = &av->calls[call_index]; + pthread_mutex_lock(&call->mutex); + + + if (!call->call_active) { + pthread_mutex_unlock(&call->mutex); + LOGGER_WARNING("Action on inactive call: %d", call_index); + return ErrorNoCall; + } + + int32_t rc = opus_encode(call->cs->audio_encoder, frame, frame_size, dest, dest_max); + pthread_mutex_unlock(&call->mutex); + + if (rc < 0) { + LOGGER_ERROR("Failed to encode payload: %s\n", opus_strerror(rc)); + return ErrorInternal; + } + + return rc; +} + +/** + * @brief Get peer transmission type. It can either be audio or video. + * + * @param av Handler. + * @param peer The peer + * @return int + * @retval ToxAvCallType On success. + * @retval ToxAvError On error. + */ +int toxav_get_peer_csettings ( ToxAv *av, int32_t call_index, int peer, ToxAvCSettings *dest ) +{ + if ( peer < 0 || cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] + || av->msi_session->calls[call_index]->peer_count <= peer ) + return ErrorInternal; + + *dest = toxavcsettings_cast(&av->msi_session->calls[call_index]->csettings_peer[peer]); + return ErrorNone; +} + +/** + * @brief Get id of peer participating in conversation + * + * @param av Handler + * @param peer peer index + * @return int + * @retval ToxAvError No peer id + */ +int toxav_get_peer_id ( ToxAv *av, int32_t call_index, int peer ) +{ + if ( peer < 0 || cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] + || av->msi_session->calls[call_index]->peer_count <= peer ) + return ErrorInternal; + + return av->msi_session->calls[call_index]->peers[peer]; +} + +/** + * @brief Get id of peer participating in conversation + * + * @param av Handler + * @param peer peer index + * @return int + * @retval ToxAvError No peer id + */ +ToxAvCallState toxav_get_call_state(ToxAv *av, int32_t call_index) +{ + if ( cii(call_index, av->msi_session) || !av->msi_session->calls[call_index] ) + return av_CallNonExistant; + + return av->msi_session->calls[call_index]->state; + +} + +/** + * @brief Is certain capability supported + * + * @param av Handler + * @return int + * @retval 1 Yes. + * @retval 0 No. + */ +inline__ int toxav_capability_supported ( ToxAv *av, int32_t call_index, ToxAvCapabilities capability ) +{ + return av->calls[call_index].cs ? av->calls[call_index].cs->capabilities & (Capabilities) capability : 0; + /* 0 is error here */ +} + +inline__ Tox *toxav_get_tox(ToxAv *av) +{ + return (Tox *)av->messenger; +} + +int toxav_has_activity(ToxAv *av, int32_t call_index, int16_t *PCM, uint16_t frame_size, float ref_energy) +{ + if ( !av->calls[call_index].cs ) return ErrorInvalidCodecState; + + return energy_VAD(av->calls[call_index].cs, PCM, frame_size, ref_energy); +} + + +static void decode_video(ToxAv *av, CallSpecific *call, DECODE_PACKET *p) +{ + int32_t call_index = call - av->calls; + + int rc = vpx_codec_decode(&call->cs->v_decoder, p->data, p->size, NULL, MAX_DECODE_TIME_US); + + if (rc != VPX_CODEC_OK) { + LOGGER_ERROR("Error decoding video: %s\n", vpx_codec_err_to_string(rc)); + } + + vpx_codec_iter_t iter = NULL; + vpx_image_t *img; + img = vpx_codec_get_frame(&call->cs->v_decoder, &iter); + + if (img && av->video_callback) { + av->video_callback(av, call_index, img, av->video_callback_userdata); + } else { + LOGGER_WARNING("Video packet dropped due to missing callback or no image!"); + } + + free(p); +} + +static void decode_audio(ToxAv *av, CallSpecific *call, DECODE_PACKET *p) +{ + int32_t call_index = call - av->calls; + + // ToxAvCSettings csettings; + // toxav_get_peer_csettings(av, call_index, 0, &csettings); + + int frame_size = 10000; /* FIXME: not static? */ + int16_t dest[frame_size]; + + int dec_size = opus_decode(call->cs->audio_decoder, p->data, p->size, dest, frame_size, (p->size == 0)); + free(p); + + if (dec_size < 0) { + LOGGER_WARNING("Decoding error: %s", opus_strerror(dec_size)); + return; + } + + if ( av->audio_callback ) + av->audio_callback(av, call_index, dest, dec_size, av->audio_callback_userdata); + else + LOGGER_WARNING("Audio packet dropped due to missing callback!"); +} + +static void *toxav_decoding(void *arg) +{ + void **pp = arg; + ToxAv *av = pp[0]; + CallSpecific *call = pp[1]; + free(pp); + + while (1) { + DECODE_PACKET *p; + _Bool video = 0; + + pthread_mutex_lock(&call->decode_cond_mutex); + + if (call->exit) { + break; + } + + uint8_t r; + + /* first check for available packets, otherwise wait for condition*/ + r = call->audio_decode_read; + p = call->audio_decode_queue[r]; + + if (!p) { + r = call->video_decode_read; + p = call->video_decode_queue[r]; + + if (!p) { + pthread_cond_wait(&call->decode_cond, &call->decode_cond_mutex); + r = call->audio_decode_read; + p = call->audio_decode_queue[r]; + + if (!p) { + r = call->video_decode_read; + p = call->video_decode_queue[r]; + video = 1; + } + } else { + video = 1; + } + } + + if (video) { + if (p) { + call->video_decode_queue[r] = NULL; + call->video_decode_read = (r + 1) % VIDEO_DECODE_QUEUE_SIZE; + } + } else { + call->audio_decode_queue[r] = NULL; + call->audio_decode_read = (r + 1) % AUDIO_DECODE_QUEUE_SIZE; + } + + pthread_mutex_unlock(&call->decode_cond_mutex); + + if (p) { + if (video) { + decode_video(av, call, p); + } else { + decode_audio(av, call, p); + } + } + } + + call->exit = 0; + pthread_cond_signal(&call->decode_cond); + pthread_mutex_unlock(&call->decode_cond_mutex); + + return NULL; +} + +void toxav_handle_packet(RTPSession *_session, RTPMessage *_msg) +{ + ToxAv *av = _session->av; + int32_t call_index = _session->call_index; + CallSpecific *call = &av->calls[call_index]; + + if (!call->call_active) return; + + if (_session->payload_type == type_audio % 128) { + queue(call->j_buf, _msg); + + int success = 0; + + while ((_msg = dequeue(call->j_buf, &success)) || success == 2) { + DECODE_PACKET *p; + + if (success == 2) { + p = malloc(sizeof(DECODE_PACKET)); + + if (p) { + p->size = 0; + } + } else { + p = malloc(sizeof(DECODE_PACKET) + _msg->length); + + if (p) { + p->size = _msg->length; + memcpy(p->data, _msg->data, _msg->length); + } + + rtp_free_msg(NULL, _msg); + } + + if (p) { + /* do the decoding on another thread */ + pthread_mutex_lock(&call->decode_cond_mutex); + uint8_t w = call->audio_decode_write; + + if (call->audio_decode_queue[w] == NULL) { + call->audio_decode_queue[w] = p; + call->audio_decode_write = (w + 1) % AUDIO_DECODE_QUEUE_SIZE; + pthread_cond_signal(&call->decode_cond); + } else { + LOGGER_DEBUG("Dropped audio frame\n"); + free(p); + } + + pthread_mutex_unlock(&call->decode_cond_mutex); + } else { + //malloc failed + } + } + + } else { + uint8_t *packet = _msg->data; + int recved_size = _msg->length; + + if (recved_size < VIDEOFRAME_HEADER_SIZE) { + goto end; + } + + uint8_t i = packet[0] - call->frame_id; + + if (i == 0) { + /* piece of current frame */ + } else if (i > 0 && i < 128) { + /* recieved a piece of a frame ahead, flush current frame and start reading this new frame */ + DECODE_PACKET *p = malloc(sizeof(DECODE_PACKET) + call->frame_limit); + + if (p) { + p->size = call->frame_limit; + memcpy(p->data, call->frame_buf, call->frame_limit); + + /* do the decoding on another thread */ + pthread_mutex_lock(&call->decode_cond_mutex); + uint8_t w = call->video_decode_write; + + if (call->video_decode_queue[w] == NULL) { + call->video_decode_queue[w] = p; + call->video_decode_write = (w + 1) % VIDEO_DECODE_QUEUE_SIZE; + pthread_cond_signal(&call->decode_cond); + } else { + LOGGER_DEBUG("Dropped video frame\n"); + free(p); + } + + pthread_mutex_unlock(&call->decode_cond_mutex); + } else { + //malloc failed + } + + call->frame_id = packet[0]; + memset(call->frame_buf, 0, call->frame_limit); + call->frame_limit = 0; + } else { + /* old packet, dont read */ + LOGGER_DEBUG("Old packet: %u\n", i); + goto end; + } + + if (packet[1] > (MAX_VIDEOFRAME_SIZE - VIDEOFRAME_PIECE_SIZE + 1) / + VIDEOFRAME_PIECE_SIZE) { //TODO, fix this check? not sure + /* packet out of buffer range */ + goto end; + } + + LOGGER_DEBUG("Video Packet: %u %u\n", packet[0], packet[1]); + memcpy(call->frame_buf + packet[1] * VIDEOFRAME_PIECE_SIZE, packet + VIDEOFRAME_HEADER_SIZE, + recved_size - VIDEOFRAME_HEADER_SIZE); + uint32_t limit = packet[1] * VIDEOFRAME_PIECE_SIZE + recved_size - VIDEOFRAME_HEADER_SIZE; + + if (limit > call->frame_limit) { + call->frame_limit = limit; + LOGGER_DEBUG("Limit: %u\n", call->frame_limit); + } + +end: + ; + rtp_free_msg(NULL, _msg); + } +} diff --git a/protocols/Tox/toxcore/toxav/toxav.h b/protocols/Tox/toxcore/toxav/toxav.h new file mode 100644 index 0000000000..e31c7aad1f --- /dev/null +++ b/protocols/Tox/toxcore/toxav/toxav.h @@ -0,0 +1,389 @@ +/** toxav.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 <http://www.gnu.org/licenses/>. + * + */ + + +#ifndef __TOXAV +#define __TOXAV +#include <inttypes.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* vpx_image_t */ +#include <vpx/vpx_image.h> + +typedef void ( *ToxAVCallback ) ( void *agent, int32_t call_idx, void *arg ); +typedef struct _ToxAv ToxAv; + +#ifndef __TOX_DEFINED__ +#define __TOX_DEFINED__ +typedef struct Tox Tox; +#endif + +#define RTP_PAYLOAD_SIZE 65535 + + +/** + * @brief Callbacks ids that handle the call states. + */ +typedef enum { + /* Requests */ + av_OnInvite, + av_OnStart, + av_OnCancel, + av_OnReject, + av_OnEnd, + + /* Responses */ + av_OnRinging, + av_OnStarting, + av_OnEnding, + + /* Protocol */ + av_OnRequestTimeout, + av_OnPeerTimeout, + av_OnMediaChange +} ToxAvCallbackID; + + +/** + * @brief Call type identifier. + */ +typedef enum { + TypeAudio = 192, + TypeVideo +} ToxAvCallType; + + +typedef enum { + av_CallNonExistant = -1, + av_CallInviting, /* when sending call invite */ + av_CallStarting, /* when getting call invite */ + av_CallActive, + av_CallHold, + av_CallHanged_up +} ToxAvCallState; + +/** + * @brief Error indicators. + */ +typedef enum { + ErrorNone = 0, + ErrorInternal = -1, /* Internal error */ + ErrorAlreadyInCall = -2, /* Already has an active call */ + ErrorNoCall = -3, /* Trying to perform call action while not in a call */ + ErrorInvalidState = -4, /* Trying to perform call action while in invalid state*/ + ErrorNoRtpSession = -5, /* Trying to perform rtp action on invalid session */ + ErrorAudioPacketLost = -6, /* Indicating packet loss */ + ErrorStartingAudioRtp = -7, /* Error in toxav_prepare_transmission() */ + ErrorStartingVideoRtp = -8 , /* Error in toxav_prepare_transmission() */ + ErrorTerminatingAudioRtp = -9, /* Returned in toxav_kill_transmission() */ + ErrorTerminatingVideoRtp = -10, /* Returned in toxav_kill_transmission() */ + ErrorPacketTooLarge = -11, /* Buffer exceeds size while encoding */ + ErrorInvalidCodecState = -12, /* Codec state not initialized */ + +} ToxAvError; + + +/** + * @brief Locally supported capabilities. + */ +typedef enum { + AudioEncoding = 1 << 0, + AudioDecoding = 1 << 1, + VideoEncoding = 1 << 2, + VideoDecoding = 1 << 3 +} ToxAvCapabilities; + + +/** + * @brief Encoding settings. + */ +typedef struct _ToxAvCodecSettings { + ToxAvCallType call_type; + + uint32_t video_bitrate; /* In kbits/s */ + uint16_t max_video_width; /* In px */ + uint16_t max_video_height; /* In px */ + + uint32_t audio_bitrate; /* In bits/s */ + uint16_t audio_frame_duration; /* In ms */ + uint32_t audio_sample_rate; /* In Hz */ + uint32_t audio_channels; +} ToxAvCSettings; + +extern const ToxAvCSettings av_DefaultSettings; +extern const uint32_t av_jbufdc; /* Jitter buffer default capacity */ +extern const uint32_t av_VADd; /* VAD default treshold */ + +/** + * @brief Start new A/V session. There can only be one session at the time. If you register more + * it will result in undefined behaviour. + * + * @param messenger The messenger handle. + * @param userdata The agent handling A/V session (i.e. phone). + * @param video_width Width of video frame. + * @param video_height Height of video frame. + * @return ToxAv* + * @retval NULL On error. + */ +ToxAv *toxav_new(Tox *messenger, int32_t max_calls); + +/** + * @brief Remove A/V session. + * + * @param av Handler. + * @return void + */ +void toxav_kill(ToxAv *av); + +/** + * @brief Register callback for call state. + * + * @param av Handler. + * @param callback The callback + * @param id One of the ToxAvCallbackID values + * @return void + */ +void toxav_register_callstate_callback (ToxAv *av, ToxAVCallback callback, ToxAvCallbackID id, void *userdata); + +/** + * @brief Register callback for recieving audio data + * + * @param av Handler. + * @param callback The callback + * @return void + */ +void toxav_register_audio_recv_callback (ToxAv *av, void (*callback)(ToxAv *, int32_t, int16_t *, int, void *), + void *user_data); + +/** + * @brief Register callback for recieving video data + * + * @param av Handler. + * @param callback The callback + * @return void + */ +void toxav_register_video_recv_callback (ToxAv *av, void (*callback)(ToxAv *, int32_t, vpx_image_t *, void *), + void *user_data); + +/** + * @brief Call user. Use its friend_id. + * + * @param av Handler. + * @param user The user. + * @param call_type Call type. + * @param ringing_seconds Ringing timeout. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_call(ToxAv *av, int32_t *call_index, int user, const ToxAvCSettings *csettings, int ringing_seconds); + +/** + * @brief Hangup active call. + * + * @param av Handler. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_hangup(ToxAv *av, int32_t call_index); + +/** + * @brief Answer incomming call. + * + * @param av Handler. + * @param call_type Answer with... + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_answer(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings ); + +/** + * @brief Reject incomming call. + * + * @param av Handler. + * @param reason Optional reason. Set NULL if none. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_reject(ToxAv *av, int32_t call_index, const char *reason); + +/** + * @brief Cancel outgoing request. + * + * @param av Handler. + * @param reason Optional reason. + * @param peer_id peer friend_id + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_cancel(ToxAv *av, int32_t call_index, int peer_id, const char *reason); + +/** + * @brief Notify peer that we are changing call settings + * + * @param av Handler. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_change_settings(ToxAv *av, int32_t call_index, const ToxAvCSettings *csettings); + +/** + * @brief Terminate transmission. Note that transmission will be terminated without informing remote peer. + * + * @param av Handler. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_stop_call(ToxAv *av, int32_t call_index); + +/** + * @brief Must be call before any RTP transmission occurs. + * + * @param av Handler. + * @param support_video Is video supported ? 1 : 0 + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_prepare_transmission(ToxAv *av, int32_t call_index, uint32_t jbuf_size, uint32_t VAD_treshold, + int support_video); + +/** + * @brief Call this at the end of the transmission. + * + * @param av Handler. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_kill_transmission(ToxAv *av, int32_t call_index); + +/** + * @brief Encode and send video packet. + * + * @param av Handler. + * @param frame The encoded frame. + * @param frame_size The size of the encoded frame. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_send_video ( ToxAv *av, int32_t call_index, const uint8_t *frame, unsigned int frame_size); + +/** + * @brief Send audio frame. + * + * @param av Handler. + * @param data The audio data encoded with toxav_prepare_audio_frame(). + * @param size Its size in number of bytes. + * @return int + * @retval 0 Success. + * @retval ToxAvError On error. + */ +int toxav_send_audio ( ToxAv *av, int32_t call_index, const uint8_t *frame, unsigned int size); + +/** + * @brief Encode video frame + * + * @param av Handler + * @param dest Where to + * @param dest_max Max size + * @param input What to encode + * @return int + * @retval ToxAvError On error. + * @retval >0 On success + */ +int toxav_prepare_video_frame ( ToxAv *av, int32_t call_index, uint8_t *dest, int dest_max, vpx_image_t *input ); + +/** + * @brief Encode audio frame + * + * @param av Handler + * @param dest dest + * @param dest_max Max dest size + * @param frame The frame + * @param frame_size The frame size + * @return int + * @retval ToxAvError On error. + * @retval >0 On success + */ +int toxav_prepare_audio_frame ( ToxAv *av, int32_t call_index, uint8_t *dest, int dest_max, const int16_t *frame, + int frame_size); + +/** + * @brief Get peer transmission type. It can either be audio or video. + * + * @param av Handler. + * @param peer The peer + * @return int + * @retval ToxAvCallType On success. + * @retval ToxAvError On error. + */ +int toxav_get_peer_csettings ( ToxAv *av, int32_t call_index, int peer, ToxAvCSettings *dest ); + +/** + * @brief Get id of peer participating in conversation + * + * @param av Handler + * @param peer peer index + * @return int + * @retval ToxAvError No peer id + */ +int toxav_get_peer_id ( ToxAv *av, int32_t call_index, int peer ); + +/** + * @brief Get current call state + * + * @param av Handler + * @param call_index What call + * @return int + * @retval ToxAvCallState State id + */ +ToxAvCallState toxav_get_call_state ( ToxAv *av, int32_t call_index ); +/** + * @brief Is certain capability supported + * + * @param av Handler + * @return int + * @retval 1 Yes. + * @retval 0 No. + */ +int toxav_capability_supported ( ToxAv *av, int32_t call_index, ToxAvCapabilities capability ); + + +Tox *toxav_get_tox(ToxAv *av); + +int toxav_has_activity ( ToxAv *av, int32_t call_index, int16_t *PCM, uint16_t frame_size, float ref_energy ); + +#ifdef __cplusplus +} +#endif + +#endif /* __TOXAV */ diff --git a/protocols/Tox/toxcore/toxcore/DHT.c b/protocols/Tox/toxcore/toxcore/DHT.c new file mode 100644 index 0000000000..77d126c16d --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/DHT.c @@ -0,0 +1,2588 @@ +/* DHT.c + * + * An implementation of the DHT as seen in http://wiki.tox.im/index.php/DHT + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +/*----------------------------------------------------------------------------------*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef DEBUG +#include <assert.h> +#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 300 + +/* 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 10 + +/* Used in the comparison function for sorting lists of Client_data. */ +typedef struct { + Client_data c1; + Client_data c2; +} ClientPair; + +/* Create the declaration for a quick sort for ClientPair structures. */ +declare_quick_sort(ClientPair); +/* Create the quicksort function. See misc_tools.h for the definition. */ +make_quick_sort(ClientPair); + +Client_data *DHT_get_close_list(DHT *dht) +{ + return dht->close_clientlist; +} + +/* Compares client_id1 and client_id2 with client_id. + * + * return 0 if both are same distance. + * return 1 if client_id1 is closer. + * return 2 if client_id2 is closer. + */ +int id_closest(const uint8_t *id, const uint8_t *id1, const uint8_t *id2) +{ + size_t i; + uint8_t distance1, distance2; + + for (i = 0; i < CLIENT_ID_SIZE; ++i) { + + distance1 = abs(((int8_t *)id)[i] ^ ((int8_t *)id1)[i]); + distance2 = abs(((int8_t *)id)[i] ^ ((int8_t *)id2)[i]); + + if (distance1 < distance2) + return 1; + + if (distance1 > distance2) + return 2; + } + + return 0; +} + +/* Turns the result of id_closest into something quick_sort can use. + * Assumes p1->c1 == p2->c1. + */ +static int client_id_cmp(const ClientPair p1, const ClientPair p2) +{ + int c = id_closest(p1.c1.client_id, p1.c2.client_id, p2.c2.client_id); + + if (c == 2) + return -1; + + return c; +} + +/* 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 *client_id) +{ + uint32_t i, num = ~0, curr = 0; + + for (i = 0; i < MAX_KEYS_PER_SLOT; ++i) { + int index = client_id[30] * MAX_KEYS_PER_SLOT + i; + + if (shared_keys->keys[index].stored) { + if (memcmp(client_id, shared_keys->keys[index].client_id, CLIENT_ID_SIZE) == 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(client_id, 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].client_id, client_id, CLIENT_ID_SIZE); + 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 client_id into shared_key + * for packets that we receive. + */ +void DHT_get_shared_key_recv(DHT *dht, uint8_t *shared_key, const uint8_t *client_id) +{ + return get_shared_key(&dht->shared_keys_recv, shared_key, dht->self_secret_key, client_id); +} + +/* Copy shared_key to encrypt/decrypt DHT packet from client_id into shared_key + * for packets that we send. + */ +void DHT_get_shared_key_sent(DHT *dht, uint8_t *shared_key, const uint8_t *client_id) +{ + return get_shared_key(&dht->shared_keys_sent, shared_key, dht->self_secret_key, client_id); +} + +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; +} + +void to_host_family(IP *ip) +{ + if (ip->family == TOX_AF_INET) + ip->family = AF_INET; + else if (ip->family == TOX_AF_INET6) + ip->family = AF_INET6; +} + +/* 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; + + 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 = 1 + sizeof(IP4) + sizeof(uint16_t) + CLIENT_ID_SIZE; + + if (packed_length + size > length) + return -1; + + data[packed_length] = net_family; + memcpy(data + packed_length + 1, &nodes[i].ip_port.ip.ip4, sizeof(IP4)); + memcpy(data + packed_length + 1 + sizeof(IP4), &nodes[i].ip_port.port, sizeof(uint16_t)); + memcpy(data + packed_length + 1 + sizeof(IP4) + sizeof(uint16_t), nodes[i].client_id, CLIENT_ID_SIZE); + packed_length += size; + } else if (ipv6 == 1) { + uint32_t size = 1 + sizeof(IP6) + sizeof(uint16_t) + CLIENT_ID_SIZE; + + if (packed_length + size > length) + return -1; + + data[packed_length] = net_family; + memcpy(data + packed_length + 1, &nodes[i].ip_port.ip.ip6, sizeof(IP6)); + memcpy(data + packed_length + 1 + sizeof(IP6), &nodes[i].ip_port.port, sizeof(uint16_t)); + memcpy(data + packed_length + 1 + sizeof(IP6) + sizeof(uint16_t), nodes[i].client_id, CLIENT_ID_SIZE); + 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 = 1 + sizeof(IP4) + sizeof(uint16_t) + CLIENT_ID_SIZE; + + 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, sizeof(IP4)); + memcpy(&nodes[num].ip_port.port, data + len_processed + 1 + sizeof(IP4), sizeof(uint16_t)); + memcpy(nodes[num].client_id, data + len_processed + 1 + sizeof(IP4) + sizeof(uint16_t), CLIENT_ID_SIZE); + len_processed += size; + ++num; + } else if (ipv6 == 1) { + uint32_t size = 1 + sizeof(IP6) + sizeof(uint16_t) + CLIENT_ID_SIZE; + + 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, sizeof(IP6)); + memcpy(&nodes[num].ip_port.port, data + len_processed + 1 + sizeof(IP6), sizeof(uint16_t)); + memcpy(nodes[num].client_id, data + len_processed + 1 + sizeof(IP6) + sizeof(uint16_t), CLIENT_ID_SIZE); + len_processed += size; + ++num; + } else { + return -1; + } + } + + if (processed_data_len) + *processed_data_len = len_processed; + + return num; +} + + + +/* Check if client with client_id 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, uint32_t length, const uint8_t *client_id, IP_Port ip_port) +{ + uint32_t i; + uint64_t temp_time = unix_time(); + + /* if client_id is in list, find it and maybe overwrite ip_port */ + for (i = 0; i < length; ++i) + if (id_equal(list[i].client_id, client_id)) { + /* Refresh the client timestamp. */ + if (ip_port.ip.family == AF_INET) { + + LOGGER_SCOPE( if (!ipport_equal(&list[i].assoc4.ip_port, &ip_port)) { + LOGGER_INFO("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_INFO("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; + } + + /* client_id not in list yet: see if we can find an identical ip_port, in + * that case we kill the old client_id by overwriting it with the new one + * TODO: maybe we SHOULDN'T do that if that client_id is in a friend_list + * and the one who is the actual friend's client_id/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].client_id, client_id, CLIENT_ID_SIZE); + + LOGGER_DEBUG("coipil[%u]: switching client_id (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].client_id, client_id, CLIENT_ID_SIZE); + + LOGGER_DEBUG("coipil[%u]: switching client_id (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 client_id 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, uint32_t length, const uint8_t *client_id) +{ + uint32_t i; + + for (i = 0; i < length; ++i) { + if (id_equal(list[i].client_id, client_id)) + return 1; + } + + return 0; +} + +/* return friend number from the client_id. + * return -1 if a failure occurs. + */ +static int friend_number(const DHT *dht, const uint8_t *client_id) +{ + uint32_t i; + + for (i = 0; i < dht->num_friends; ++i) { + if (id_equal(dht->friends_list[i].client_id, client_id)) + return i; + } + + return -1; +} + +/*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 *client_id, 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; + int j, closest; + 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->client_id)) + 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(client_id, client->client_id)) + continue; + + if (num_nodes < MAX_SENT_NODES) { + memcpy(nodes_list[num_nodes].client_id, + client->client_id, + CLIENT_ID_SIZE ); + + nodes_list[num_nodes].ip_port = ipptp->ip_port; + num_nodes++; + } else { + /* see if node_list contains a client_id that's "further away" + * compared to the one we're looking at at the moment, if there + * is, replace it + */ + for (j = 0; j < MAX_SENT_NODES; ++j) { + closest = id_closest( client_id, + nodes_list[j].client_id, + client->client_id ); + + /* second client_id is closer than current: change to it */ + if (closest == 2) { + memcpy( nodes_list[j].client_id, + client->client_id, + CLIENT_ID_SIZE); + + nodes_list[j].ip_port = ipptp->ip_port; + break; + } + } + } + } + + *num_nodes_ptr = num_nodes; +} + +/* Find MAX_SENT_NODES nodes closest to the client_id for the send nodes request: + * put them in the nodes_list and return how many were found. + * + * TODO: For the love of based <your favorite deity, in doubt use "love"> 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 *client_id, 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(client_id, nodes_list, sa_family, + dht->close_clientlist, LCLIENT_LIST, &num_nodes, is_LAN, want_good); + + /*TODO uncomment this when hardening is added to close friend clients + for (i = 0; i < dht->num_friends; ++i) + get_close_nodes_inner(dht, client_id, 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(client_id, 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 *client_id, 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, client_id, 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 = client_id; + 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, client_id, 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].client_id, client->client_id); + + 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 +} + +/* Replace first bad (or empty) node with this one. + * + * return 0 if successful. + * return 1 if not (list contains no bad nodes). + */ +static int replace_bad( Client_data *list, + uint32_t length, + const uint8_t *client_id, + IP_Port ip_port ) +{ + if ((ip_port.ip.family != AF_INET) && (ip_port.ip.family != AF_INET6)) + return 1; + + uint32_t i; + + for (i = 0; i < length; ++i) { + /* If node is bad */ + Client_data *client = &list[i]; + + if (is_timeout(client->assoc4.timestamp, BAD_NODE_TIMEOUT) && + is_timeout(client->assoc6.timestamp, BAD_NODE_TIMEOUT)) { + + 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; + } + + memcpy(client->client_id, client_id, CLIENT_ID_SIZE); + 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; +} + + +/* Sort the list. It will be sorted from furthest to closest. + * Turns list into data that quick sort can use and reverts it back. + */ +static void sort_list(Client_data *list, uint32_t length, const uint8_t *comp_client_id) +{ + Client_data cd; + ClientPair pairs[length]; + uint32_t i; + + memcpy(cd.client_id, comp_client_id, CLIENT_ID_SIZE); + + for (i = 0; i < length; ++i) { + pairs[i].c1 = cd; + pairs[i].c2 = list[i]; + } + + ClientPair_quick_sort(pairs, length, client_id_cmp); + + for (i = 0; i < length; ++i) + list[i] = pairs[i].c2; +} + +/* Replace first node that is possibly bad (tests failed or not done yet.) with this one. + * + * return 0 if successful. + * return 1 if not (list contains no bad nodes). + */ +static int replace_possible_bad( Client_data *list, + uint32_t length, + const uint8_t *client_id, + IP_Port ip_port, + const uint8_t *comp_client_id ) +{ + if ((ip_port.ip.family != AF_INET) && (ip_port.ip.family != AF_INET6)) + return 1; + + sort_list(list, length, comp_client_id); + + /* TODO: decide if the following lines should stay commented or not. + if (id_closest(comp_client_id, list[0].client_id, client_id) == 1) + return 0;*/ + + uint32_t i; + + for (i = 0; i < length; ++i) { + /* If node is bad */ + Client_data *client = &list[i]; + + if (hardening_correct(&client->assoc4.hardening) != HARDENING_ALL_OK && + hardening_correct(&client->assoc6.hardening) != HARDENING_ALL_OK) { + + 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; + } + + memcpy(client->client_id, client_id, CLIENT_ID_SIZE); + 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; +} + +/* Replace the first good node that is further to the comp_client_id than that of the client_id in the list + * + * returns 0 when the item was stored, 1 otherwise */ +static int replace_good( Client_data *list, + uint32_t length, + const uint8_t *client_id, + IP_Port ip_port, + const uint8_t *comp_client_id ) +{ + if ((ip_port.ip.family != AF_INET) && (ip_port.ip.family != AF_INET6)) + return 1; + + /* TODO: eventually remove this.*/ + if (length != LCLIENT_LIST) + sort_list(list, length, comp_client_id); + + int8_t replace = -1; + + /* Because the list is sorted, we can simply check the client_id at the + * border, either it is closer, then every other one is as well, or it is + * further, then it gets pushed out in favor of the new address, which + * will with the next sort() move to its "rightful" position + * + * CAVEAT: weirdly enough, the list is sorted DESCENDING in distance + * so the furthest element is the first, NOT the last (at least that's + * what the comment above sort_list() claims) + */ + if (id_closest(comp_client_id, list[0].client_id, client_id) == 2) + replace = 0; + + if (replace != -1) { +#ifdef DEBUG + assert(replace >= 0 && replace < length); +#endif + Client_data *client = &list[replace]; + 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; + } + + memcpy(client->client_id, client_id, CLIENT_ID_SIZE); + 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; +} + +/* Attempt to add client with ip_port and client_id 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 *client_id) +{ + 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, client_id, ip_port)) { + if (replace_bad(dht->close_clientlist, LCLIENT_LIST, client_id, ip_port)) { + if (replace_possible_bad(dht->close_clientlist, LCLIENT_LIST, client_id, ip_port, + dht->self_public_key)) { + /* If we can't replace bad nodes we try replacing good ones. */ + if (!replace_good(dht->close_clientlist, LCLIENT_LIST, client_id, ip_port, + dht->self_public_key)) + used++; + } else + used++; + } else + used++; + } else + used++; + + for (i = 0; i < dht->num_friends; ++i) { + if (!client_or_ip_port_in_list(dht->friends_list[i].client_list, + MAX_FRIEND_CLIENTS, client_id, ip_port)) { + + if (replace_bad(dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, + client_id, ip_port)) { + /*if (replace_possible_bad(dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, + client_id, ip_port, dht->friends_list[i].client_id)) {*/ + /* If we can't replace bad nodes we try replacing good ones. */ + if (!replace_good(dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, + client_id, ip_port, dht->friends_list[i].client_id)) + used++; + + /*} else + used++;*/ + } else + used++; + } else + used++; + } + +#ifdef ENABLE_ASSOC_DHT + + if (dht->assoc) { + IPPTs ippts; + + ippts.ip_port = ip_port; + ippts.timestamp = unix_time(); + + Assoc_add_entry(dht->assoc, client_id, &ippts, NULL, used ? 1 : 0); + } + +#endif + return used; +} + +/* If client_id is a friend or us, update ret_ip_port + * nodeclient_id is the id of the node that sent us this info. + */ +static int returnedip_ports(DHT *dht, IP_Port ip_port, const uint8_t *client_id, const uint8_t *nodeclient_id) +{ + 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(client_id, dht->self_public_key)) { + for (i = 0; i < LCLIENT_LIST; ++i) { + if (id_equal(nodeclient_id, dht->close_clientlist[i].client_id)) { + 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(client_id, dht->friends_list[i].client_id)) { + for (j = 0; j < MAX_FRIEND_CLIENTS; ++j) { + if (id_equal(nodeclient_id, dht->friends_list[i].client_list[j].client_id)) { + 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, client_id, &ippts, NULL, used ? 1 : 0); + } + +#endif + return 0; +} + +#define NODES_ENCRYPTED_MESSAGE_LENGTH (crypto_box_NONCEBYTES + sizeof(uint64_t) + sizeof(Node_format) + sizeof(Node_format) + crypto_box_MACBYTES) + +/* 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.client_id, public_key, CLIENT_ID_SIZE); + 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[CLIENT_ID_SIZE + sizeof(ping_id)]; + uint8_t encrypt[sizeof(plain) + crypto_box_MACBYTES]; + uint8_t data[1 + CLIENT_ID_SIZE + crypto_box_NONCEBYTES + sizeof(encrypt)]; + + memcpy(plain, client_id, CLIENT_ID_SIZE); + memcpy(plain + CLIENT_ID_SIZE, &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, CLIENT_ID_SIZE); + memcpy(data + 1 + CLIENT_ID_SIZE, nonce, crypto_box_NONCEBYTES); + memcpy(data + 1 + CLIENT_ID_SIZE + 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 > NODES_ENCRYPTED_MESSAGE_LENGTH || length == 0) + return -1; + + size_t Node_format_size = sizeof(Node_format); + uint8_t data[1 + CLIENT_ID_SIZE + 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); + + if (num_nodes == 0) + return 0; + + 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 = 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, CLIENT_ID_SIZE); + memcpy(data + 1 + CLIENT_ID_SIZE, nonce, crypto_box_NONCEBYTES); + memcpy(data + 1 + CLIENT_ID_SIZE + crypto_box_NONCEBYTES, encrypt, len); + + return sendpacket(dht->net, ip_port, data, 1 + CLIENT_ID_SIZE + crypto_box_NONCEBYTES + len); +} + +static int handle_getnodes(void *object, IP_Port source, const uint8_t *packet, uint32_t length) +{ + uint32_t cmp_len = 1 + CLIENT_ID_SIZE + crypto_box_NONCEBYTES + CLIENT_ID_SIZE + crypto_box_MACBYTES; + + if (length <= cmp_len) + return 1; + + if (length > cmp_len + NODES_ENCRYPTED_MESSAGE_LENGTH) + return 1; + + uint16_t sendback_data_length = length - cmp_len; + + DHT *dht = object; + + /* Check if packet is from ourself. */ + if (id_equal(packet + 1, dht->self_public_key)) + return 1; + + uint8_t plain[CLIENT_ID_SIZE + sendback_data_length]; + 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 + CLIENT_ID_SIZE, + packet + 1 + CLIENT_ID_SIZE + crypto_box_NONCEBYTES, + CLIENT_ID_SIZE + sendback_data_length + crypto_box_MACBYTES, + plain ); + + if (len != CLIENT_ID_SIZE + sendback_data_length) + return 1; + + sendnodes_ipv6(dht, source, packet + 1, plain, plain + CLIENT_ID_SIZE, sendback_data_length, 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 *client_id, 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) || memcmp(test.client_id, client_id, CLIENT_ID_SIZE) != 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, uint32_t length, + Node_format *plain_nodes, uint16_t size_plain_nodes, uint32_t *num_nodes_out) +{ + DHT *dht = object; + uint32_t cid_size = 1 + CLIENT_ID_SIZE + 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 + CLIENT_ID_SIZE, + packet + 1 + CLIENT_ID_SIZE + 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 || plain[0] == 0) + 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, uint32_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)) { + send_ping_request(dht->ping, plain_nodes[i].ip_port, plain_nodes[i].client_id); + returnedip_ports(dht, plain_nodes[i].ip_port, plain_nodes[i].client_id, packet + 1); + } + } + + return 0; +} + +/*----------------------------------------------------------------------------------*/ +/*------------------------END of packet handling functions--------------------------*/ + +/* + * Send get nodes requests with client_id to max_num peers in list of length length + */ +/* +static void get_bunchnodes(DHT *dht, Client_data *list, uint16_t length, uint16_t max_num, uint8_t *client_id) +{ + uint32_t i, num = 0; + + for (i = 0; i < length; ++i) { + IPPTsPng *assoc; + uint32_t a; + + for (a = 0, assoc = &list[i].assoc6; a < 2; a++, assoc = &list[i].assoc4) + if (ipport_isset(&(assoc->ip_port)) && + !is_timeout(assoc->ret_timestamp, BAD_NODE_TIMEOUT)) { + getnodes(dht, assoc->ip_port, list[i].client_id, client_id, NULL); + ++num; + + if (num >= max_num) + return; + } + } +} +*/ +int DHT_addfriend(DHT *dht, const uint8_t *client_id) +{ + if (friend_number(dht, client_id) != -1) /* Is friend already in DHT? */ + return 1; + + DHT_Friend *temp; + temp = realloc(dht->friends_list, sizeof(DHT_Friend) * (dht->num_friends + 1)); + + if (temp == NULL) + return 1; + + dht->friends_list = temp; + memset(&dht->friends_list[dht->num_friends], 0, sizeof(DHT_Friend)); + memcpy(dht->friends_list[dht->num_friends].client_id, client_id, CLIENT_ID_SIZE); + + dht->friends_list[dht->num_friends].nat.NATping_id = random_64b(); + ++dht->num_friends; +#ifdef ENABLE_ASSOC_DHT + + if (dht->assoc) { + /* get up to MAX_FRIEND_CLIENTS connectable nodes */ + DHT_Friend *friend = &dht->friends_list[dht->num_friends - 1]; + + Assoc_close_entries close_entries; + memset(&close_entries, 0, sizeof(close_entries)); + close_entries.wanted_id = client_id; + close_entries.count_good = MAX_FRIEND_CLIENTS / 2; + close_entries.count = MAX_FRIEND_CLIENTS; + close_entries.result = calloc(MAX_FRIEND_CLIENTS, sizeof(*close_entries.result)); + + uint8_t i, found = Assoc_get_close_entries(dht->assoc, &close_entries); + + for (i = 0; i < found; i++) + memcpy(&friend->client_list[i], close_entries.result[i], sizeof(*close_entries.result[i])); + + if (found) { + /* send getnodes to the "best" entry */ + Client_data *client = &friend->client_list[0]; + + if (ipport_isset(&client->assoc4.ip_port)) + getnodes(dht, client->assoc4.ip_port, client->client_id, friend->client_id, NULL); + + if (ipport_isset(&client->assoc6.ip_port)) + getnodes(dht, client->assoc6.ip_port, client->client_id, friend->client_id, NULL); + } + } + +#endif + /*this isn't really useful anymore. + get_bunchnodes(dht, dht->close_clientlist, LCLIENT_LIST, MAX_FRIEND_CLIENTS, client_id);*/ + + return 0; +} + +int DHT_delfriend(DHT *dht, const uint8_t *client_id) +{ + uint32_t i; + DHT_Friend *temp; + + for (i = 0; i < dht->num_friends; ++i) { + /* Equal */ + if (id_equal(dht->friends_list[i].client_id, client_id)) { + --dht->num_friends; + + if (dht->num_friends != i) { + memcpy( &dht->friends_list[i], + &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; + } + } + + return 1; +} + +/* TODO: Optimize this. */ +int DHT_getfriendip(const DHT *dht, const uint8_t *client_id, 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].client_id, client_id)) { + for (j = 0; j < MAX_FRIEND_CLIENTS; ++j) { + Client_data *client = &dht->friends_list[i].client_list[j]; + + if (id_equal(client->client_id, client_id)) { + 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 *client_id, + Client_data *list, uint32_t list_count, uint32_t *bootstrap_times) +{ + 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]; + + 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)) { + not_kill++; + + if (is_timeout(assoc->last_pinged, PING_INTERVAL)) { + send_ping_request(dht->ping, assoc->ip_port, client->client_id ); + 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; + } + } + } + + if ((num_nodes != 0) && (is_timeout(*lastgetnode, GET_NODE_INTERVAL) || *bootstrap_times < MAX_BOOTSTRAP_TIMES)) { + uint32_t rand_node = rand() % num_nodes; + getnodes(dht, assoc_list[rand_node]->ip_port, client_list[rand_node]->client_id, + client_id, 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) +{ + uint32_t i; + + for (i = 0; i < dht->num_friends; ++i) + do_ping_and_sendnode_requests(dht, &dht->friends_list[i].lastgetnode, dht->friends_list[i].client_id, + dht->friends_list[i].client_list, MAX_FRIEND_CLIENTS, &dht->friends_list[i].bootstrap_times); +} + +/* 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) +{ + 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); + + 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 client_id + * + * return -1 if failure. + */ +int route_packet(const DHT *dht, const uint8_t *client_id, const uint8_t *packet, uint32_t length) +{ + uint32_t i; + + for (i = 0; i < LCLIENT_LIST; ++i) { + if (id_equal(client_id, dht->close_clientlist[i].client_id)) { + 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->client_id, friend->client_id)) + 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, uint32_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, uint32_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; +} + +/* Puts all the different ips returned by the nodes for a friend_id into array ip_portlist. + * ip_portlist must be at least MAX_FRIEND_CLIENTS big. + * + * return number of ips returned. + * return 0 if we are connected to friend or if no ips were found. + * return -1 if no such friend. + */ +int friend_ips(const DHT *dht, IP_Port *ip_portlist, const uint8_t *friend_id) +{ + uint32_t i; + + for (i = 0; i < dht->num_friends; ++i) { + /* Equal */ + if (id_equal(dht->friends_list[i].client_id, friend_id)) + return friend_iplist(dht, ip_portlist, i); + } + + return -1; +} + +/*----------------------------------------------------------------------------------*/ +/*---------------------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, + uint32_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].client_id); + } 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].client_id); + } + + 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].client_id); + } + + 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].client_id, 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->client_id, 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) + CLIENT_ID_SIZE]; + memcpy(data, node_totest, sizeof(Node_format)); + memcpy(data + sizeof(Node_format), search_id, CLIENT_ID_SIZE); + return send_hardening_req(dht, dest, CHECK_TYPE_GETNODE_REQ, data, sizeof(Node_format) + CLIENT_ID_SIZE); +} + +/* 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 + CLIENT_ID_SIZE + nodes_data_length]; + data[0] = CHECK_TYPE_GETNODE_RES; + memcpy(data + 1, queried_client_id, CLIENT_ID_SIZE); + memcpy(data + 1 + CLIENT_ID_SIZE, nodes_data, nodes_data_length); + int len = create_request(dht->self_public_key, dht->self_secret_key, packet, sendto->client_id, 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 *client_id, sa_family_t sa_family) +{ + uint32_t i; + + for (i = 0; i < LCLIENT_LIST; ++i) { + if (memcmp(dht->close_clientlist[i].client_id, client_id, CLIENT_ID_SIZE) != 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].client_id, dht->self_public_key)) { + ++counter; + continue; + } + + IPPTsPng *temp = get_closelist_IPPTsPng(dht, nodes[i].client_id, 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, + uint32_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.client_id, source_pubkey, CLIENT_ID_SIZE); + memcpy(&tocheck_node, packet + 1, sizeof(Node_format)); + + if (getnodes(dht, tocheck_node.ip_port, tocheck_node.client_id, packet + 1 + sizeof(Node_format), &node) == -1) + return 1; + + return 0; + } + + case CHECK_TYPE_GETNODE_RES: { + if (length <= CLIENT_ID_SIZE + 1) + return 1; + + if (length > 1 + CLIENT_ID_SIZE + sizeof(Node_format) * MAX_SENT_NODES) + return 1; + + uint16_t length_nodes = length - 1 - CLIENT_ID_SIZE; + Node_format nodes[MAX_SENT_NODES]; + int num_nodes = unpack_nodes(nodes, MAX_SENT_NODES, 0, packet + 1 + CLIENT_ID_SIZE, 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 (memcmp(temp->hardening.send_nodes_pingedid, source_pubkey, CLIENT_ID_SIZE) != 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[CLIENT_ID_SIZE]; + uint32_t i; + + for (i = 0; i < CLIENT_ID_SIZE / 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 closelist_nodes(DHT *dht, Node_format *nodes, uint16_t max_num) +{ + if (max_num == 0) + return 0; + + uint16_t count = 0; + Client_data *list = dht->close_clientlist; + + uint32_t i; + + for (i = LCLIENT_LIST; 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].client_id, list[i - 1].client_id, CLIENT_ID_SIZE); + nodes[count].ip_port = assoc->ip_port; + ++count; + + if (count >= max_num) + return count; + } + } + + return count; +} + +/* Put a random node from list of list_size in node. LAN_ok is 1 if LAN ips are ok, 0 if we don't want them. */ +static int random_node_fromlist(Client_data *list, uint16_t list_size, Node_format *node, uint8_t LAN_ok) +{ + uint32_t i; + uint32_t num_nodes = 0; + Client_data *client_list[list_size * 2]; + IPPTsPng *assoc_list[list_size * 2]; + + for (i = 0; i < list_size; 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 node is good. */ + if (!is_timeout(assoc->timestamp, BAD_NODE_TIMEOUT)) { + if (!LAN_ok) { + if (LAN_ip(assoc->ip_port.ip) == 0) + continue; + } + + client_list[num_nodes] = client; + assoc_list[num_nodes] = assoc; + ++num_nodes; + } + } + } + + if (num_nodes == 0) + return -1; + + uint32_t rand_node = rand() % num_nodes; + node->ip_port = assoc_list[rand_node]->ip_port; + memcpy(node->client_id, client_list[rand_node]->client_id, CLIENT_ID_SIZE); + return 0; +} + +/* Put up to max_num random nodes in nodes. + * + * return the number of nodes. + * + * NOTE:this is used to pick nodes for paths. + * + * TODO: remove the LAN stuff from this. + */ +uint16_t random_nodes_path(const DHT *dht, Node_format *nodes, uint16_t max_num) +{ + if (max_num == 0) + return 0; + + if (dht->num_friends == 0) + return 0; + + uint16_t count = 0; + uint16_t list_size = 0; + uint32_t i; + + for (i = 0; i < max_num; ++i) { + Client_data *list = NULL; + uint16_t rand_num = rand() % (dht->num_friends); + list = dht->friends_list[rand_num].client_list; + list_size = MAX_FRIEND_CLIENTS; + + uint8_t LAN_ok = 1; + + if (count != 0 && LAN_ip(nodes[0].ip_port.ip) != 0) + LAN_ok = 0; + + if (random_node_fromlist(list, list_size, &nodes[count], LAN_ok) == 0) + ++count; + } + + return count; +} + +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 *client_id = dht->close_clientlist[i / 2].client_id; + + 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(client_id, rand_node.client_id)) + continue; + + Node_format to_test; + to_test.ip_port = cur_iptspng->ip_port; + memcpy(to_test.client_id, client_id, CLIENT_ID_SIZE); + + //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.client_id, CLIENT_ID_SIZE); + 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, uint32_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 (memcmp(packet + 1, dht->self_public_key, crypto_box_PUBLICKEYBYTES) == 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[CLIENT_ID_SIZE]; + randombytes(random_key_bytes, sizeof(random_key_bytes)); + DHT_addfriend(dht, random_key_bytes); + } + + return dht; +} + +void do_DHT(DHT *dht) +{ + unix_time_update(); + + if (dht->last_run == unix_time()) { + return; + } + + 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, 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); +} + +/* new DHT format for load/save, more robust and forward compatible */ + +#define DHT_STATE_COOKIE_GLOBAL 0x159000d + +#define DHT_STATE_COOKIE_TYPE 0x11ce +#define DHT_STATE_TYPE_FRIENDS_ASSOC46 3 +#define DHT_STATE_TYPE_CLIENTS_ASSOC46 4 + +/* Get the size of the DHT (for saving). */ +uint32_t DHT_size(const DHT *dht) +{ + uint32_t num = 0, i; + + for (i = 0; i < LCLIENT_LIST; ++i) + if ((dht->close_clientlist[i].assoc4.timestamp != 0) || + (dht->close_clientlist[i].assoc6.timestamp != 0)) + num++; + + uint32_t size32 = sizeof(uint32_t), sizesubhead = size32 * 2; + return size32 + + sizesubhead + sizeof(DHT_Friend) * dht->num_friends + + sizesubhead + sizeof(Client_data) * num; +} + +static uint8_t *z_state_save_subheader(uint8_t *data, uint32_t len, uint16_t type) +{ + uint32_t *data32 = (uint32_t *)data; + data32[0] = len; + data32[1] = (DHT_STATE_COOKIE_TYPE << 16) | type; + data += sizeof(uint32_t) * 2; + return data; +} + +/* Save the DHT in data where data is an array of size DHT_size(). */ +void DHT_save(DHT *dht, uint8_t *data) +{ + uint32_t len; + uint16_t type; + *(uint32_t *)data = DHT_STATE_COOKIE_GLOBAL; + data += sizeof(uint32_t); + + len = sizeof(DHT_Friend) * dht->num_friends; + type = DHT_STATE_TYPE_FRIENDS_ASSOC46; + data = z_state_save_subheader(data, len, type); + memcpy(data, dht->friends_list, len); + data += len; + + uint32_t num = 0, i; + + for (i = 0; i < LCLIENT_LIST; ++i) + if ((dht->close_clientlist[i].assoc4.timestamp != 0) || + (dht->close_clientlist[i].assoc6.timestamp != 0)) + num++; + + len = num * sizeof(Client_data); + type = DHT_STATE_TYPE_CLIENTS_ASSOC46; + data = z_state_save_subheader(data, len, type); + + if (num) { + Client_data *clients = (Client_data *)data; + + for (num = 0, i = 0; i < LCLIENT_LIST; ++i) + if ((dht->close_clientlist[i].assoc4.timestamp != 0) || + (dht->close_clientlist[i].assoc6.timestamp != 0)) + memcpy(&clients[num++], &dht->close_clientlist[i], sizeof(Client_data)); + } +} + +static int dht_load_state_callback(void *outer, const uint8_t *data, uint32_t length, uint16_t type) +{ + DHT *dht = outer; + uint32_t num, i, j; + + switch (type) { + case DHT_STATE_TYPE_FRIENDS_ASSOC46: + if (length % sizeof(DHT_Friend) != 0) + break; + + { /* localize declarations */ + DHT_Friend *friend_list = (DHT_Friend *)data; + num = length / sizeof(DHT_Friend); + + for (i = 0; i < num; ++i) { + + for (j = 0; j < MAX_FRIEND_CLIENTS; ++j) { + Client_data *client = &friend_list[i].client_list[j]; + + if (client->assoc4.timestamp != 0) + getnodes(dht, client->assoc4.ip_port, client->client_id, friend_list[i].client_id, NULL); + + if (client->assoc6.timestamp != 0) + getnodes(dht, client->assoc6.ip_port, client->client_id, friend_list[i].client_id, NULL); + } + } + } /* localize declarations */ + + break; + + case DHT_STATE_TYPE_CLIENTS_ASSOC46: + if ((length % sizeof(Client_data)) != 0) + break; + + { /* localize declarations */ + num = length / sizeof(Client_data); + Client_data *client_list = (Client_data *)data; + + for (i = 0; i < num; ++i) { + if (client_list[i].assoc4.timestamp != 0) + DHT_bootstrap(dht, client_list[i].assoc4.ip_port, client_list[i].client_id); + + if (client_list[i].assoc6.timestamp != 0) + DHT_bootstrap(dht, client_list[i].assoc6.ip_port, client_list[i].client_id); + } + } /* 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 = (uint32_t *)data; + + if (data32[0] == 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; +} diff --git a/protocols/Tox/toxcore/toxcore/DHT.h b/protocols/Tox/toxcore/toxcore/DHT.h new file mode 100644 index 0000000000..ea94e4ca91 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/DHT.h @@ -0,0 +1,392 @@ +/* DHT.h + * + * An implementation of the DHT as seen in http://wiki.tox.im/index.php/DHT + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifndef DHT_H +#define DHT_H + +#include "crypto_core.h" +#include "network.h" +#include "ping_array.h" + +/* Size of the client_id in bytes. */ +#define CLIENT_ID_SIZE crypto_box_PUBLICKEYBYTES + +/* Maximum number of clients stored per friend. */ +#define MAX_FRIEND_CLIENTS 8 + +/* A list of the clients mathematically closest to ours. */ +#define LCLIENT_LIST 32 + +/* The max number of nodes to send with send nodes. */ +#define MAX_SENT_NODES 4 + +/* Ping timeout in seconds */ +#define PING_TIMEOUT 3 + +/* 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 4 + +/* Functions to transfer ips safely across wire. */ +void to_net_family(IP *ip); +void 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[CLIENT_ID_SIZE]; + /* 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[CLIENT_ID_SIZE]; + /* 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[CLIENT_ID_SIZE]; +} 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 client_id[CLIENT_ID_SIZE]; + 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; + +typedef struct { + uint8_t client_id[CLIENT_ID_SIZE]; + 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; +} DHT_Friend; + +typedef struct __attribute__ ((__packed__)) +{ + uint8_t client_id[CLIENT_ID_SIZE]; + IP_Port ip_port; +} +Node_format; + +/* 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 client_id[CLIENT_ID_SIZE]; + 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, uint32_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; + + 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]; +} 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 *client_id); + +/* Copy shared_key to encrypt/decrypt DHT packet from client_id into shared_key + * for packets that we receive. + */ +void DHT_get_shared_key_recv(DHT *dht, uint8_t *shared_key, const uint8_t *client_id); + +/* Copy shared_key to encrypt/decrypt DHT packet from client_id into shared_key + * for packets that we send. + */ +void DHT_get_shared_key_sent(DHT *dht, uint8_t *shared_key, const uint8_t *client_id); + +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. + * client_id must be CLIENT_ID_SIZE bytes long. + * + * return 0 if success. + * return 1 if failure (friends list is full). + */ +int DHT_addfriend(DHT *dht, const uint8_t *client_id); + +/* Delete a friend from the friends list. + * client_id must be CLIENT_ID_SIZE bytes long. + * + * return 0 if success. + * return 1 if failure (client_id not in friends list). + */ +int DHT_delfriend(DHT *dht, const uint8_t *client_id); + +/* Get ip of friend. + * client_id must be CLIENT_ID_SIZE bytes long. + * ip must be 4 bytes long. + * port must be 2 bytes long. + * + * !!! Signature changed !!! + * + * OLD: IP_Port DHT_getfriendip(DHT *dht, uint8_t *client_id); + * + * return ip if success. + * return ip of 0 if failure (This means the friend is either offline or we have not found him yet). + * return ip of 1 if friend is not in list. + * + * NEW: int DHT_getfriendip(DHT *dht, uint8_t *client_id, IP_Port *ip_port); + * + * return -1, -- if client_id does NOT refer to a friend + * return 0, -- if client_id refers to a friend and we failed to find the friend (yet) + * return 1, ip if client_id refers to a friend and we found him + */ +int DHT_getfriendip(const DHT *dht, const uint8_t *client_id, IP_Port *ip_port); + +/* Compares client_id1 and client_id2 with client_id. + * + * return 0 if both are same distance. + * return 1 if client_id1 is closer. + * return 2 if client_id2 is closer. + */ +int id_closest(const uint8_t *id, const uint8_t *id1, const uint8_t *id2); + +/* Get the (maximum MAX_SENT_NODES) closest nodes to client_id 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 *client_id, 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 closelist. + * + * return the number of nodes. + */ +uint16_t closelist_nodes(DHT *dht, Node_format *nodes, uint16_t max_num); + +/* Put up to max_num random nodes in nodes. + * + * return the number of nodes. + * + * NOTE:this is used to pick nodes for paths. + */ +uint16_t random_nodes_path(const 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); + + +/* ROUTING FUNCTIONS */ + +/* Send the given packet to node with client_id. + * + * return -1 if failure. + */ +int route_packet(const DHT *dht, const uint8_t *client_id, const uint8_t *packet, uint32_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, uint32_t length); + +/* Function to handle crypto packets. + */ +void cryptopacket_registerhandler(DHT *dht, uint8_t byte, cryptopacket_handler_callback cb, void *object); + +/* NAT PUNCHING FUNCTIONS */ + +/* Puts all the different ips returned by the nodes for a friend_id into array ip_portlist. + * ip_portlist must be at least MAX_FRIEND_CLIENTS big. + * + * returns number of ips returned. + * returns -1 if no such friend. + */ +int friend_ips(const DHT *dht, IP_Port *ip_portlist, const uint8_t *friend_id); + +/* 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); + +int addto_lists(DHT *dht, IP_Port ip_port, const uint8_t *client_id); + +#endif + diff --git a/protocols/Tox/toxcore/toxcore/LAN_discovery.c b/protocols/Tox/toxcore/toxcore/LAN_discovery.c new file mode 100644 index 0000000000..3c05fe8dff --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/LAN_discovery.c @@ -0,0 +1,322 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "LAN_discovery.h" +#include "util.h" + +#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 <iphlpapi.h> + +static void fetch_broadcast_info(uint16_t port) +{ + broadcast_count = 0; + + IP_ADAPTER_INFO *pAdapterInfo = malloc(sizeof(pAdapterInfo)); + unsigned long ulOutBufLen = sizeof(pAdapterInfo); + + 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; + } + } +} + +#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; + 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; +} + +/* return 0 if ip is a LAN ip. + * return -1 if it is not. + */ +int LAN_ip(IP ip) +{ + if (ip.family == AF_INET) { + IP4 ip4 = ip.ip4; + + /* Loopback. */ + if (ip4.uint8[0] == 127) + return 0; + + /* 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); + } + + /* localhost in IPv6 (::1) */ + if (ip.ip6.uint64[0] == 0 && ip.ip6.uint32[2] == 0 && ip.ip6.uint32[3] == htonl(1)) + return 0; + } + + return -1; +} + +static int handle_LANdiscovery(void *object, IP_Port source, const uint8_t *packet, uint32_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); +} diff --git a/protocols/Tox/toxcore/toxcore/LAN_discovery.h b/protocols/Tox/toxcore/toxcore/LAN_discovery.h new file mode 100644 index 0000000000..fcb094e4d9 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/LAN_discovery.h @@ -0,0 +1,55 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + + +#ifndef LAN_DISCOVERY_H +#define LAN_DISCOVERY_H + + +#include "DHT.h" + +/* Used for get_broadcast(). */ +#ifdef __linux +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <linux/netdevice.h> +#endif + +/* 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); + +/* 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 diff --git a/protocols/Tox/toxcore/toxcore/Makefile.inc b/protocols/Tox/toxcore/toxcore/Makefile.inc new file mode 100644 index 0000000000..8e39f96e37 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/Makefile.inc @@ -0,0 +1,67 @@ +lib_LTLIBRARIES += libtoxcore.la + +libtoxcore_la_include_HEADERS = \ + ../toxcore/tox.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/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_chats.h \ + ../toxcore/group_chats.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/list.c \ + ../toxcore/list.h \ + ../toxcore/misc_tools.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) diff --git a/protocols/Tox/toxcore/toxcore/Messenger.c b/protocols/Tox/toxcore/toxcore/Messenger.c new file mode 100644 index 0000000000..5212b9c5a8 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/Messenger.c @@ -0,0 +1,2945 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef DEBUG +#include <assert.h> +#endif + +#include "logger.h" +#include "Messenger.h" +#include "assoc.h" +#include "network.h" +#include "util.h" + + +#define MIN(a,b) (((a)<(b))?(a):(b)) + + +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); + +// 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) +{ + return (unsigned int)friendnumber >= m->numfriends; +} + +static int add_online_friend(Messenger *m, int32_t friendnumber) +{ + if (friend_not_valid(m, friendnumber)) + return -1; + + ++m->numonline_friends; + return 0; +} + + +static int remove_online_friend(Messenger *m, int32_t friendnumber) +{ + if (friend_not_valid(m, friendnumber)) + return -1; + + --m->numonline_friends; + return 0; +} +/* 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 *client_id) +{ + uint32_t i; + + for (i = 0; i < m->numfriends; ++i) { + if (m->friendlist[i].status > 0) + if (id_equal(client_id, m->friendlist[i].client_id)) + return i; + } + + return -1; +} + +/* Copies the public key associated to that friend id into client_id buffer. + * Make sure that client_id is of size CLIENT_ID_SIZE. + * + * return 0 if success. + * return -1 if failure. + */ +int getclient_id(const Messenger *m, int32_t friendnumber, uint8_t *client_id) +{ + if (friend_not_valid(m, friendnumber)) + return -1; + + if (m->friendlist[friendnumber].status > 0) { + memcpy(client_id, m->friendlist[friendnumber].client_id, CLIENT_ID_SIZE); + return 0; + } + + return -1; +} +/* TODO: Another checksum algorithm might be better. + * + * 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: [client_id (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)); +} + +/* 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) +{ + Messenger *m = object; + + if (friend_not_valid(m, number)) + return -1; + + if (m->friendlist[number].crypt_connection_id != -1) { + return add_tcp_relay_peer(m->net_crypto, m->friendlist[number].crypt_connection_id, ip_port, public_key); + } else { + return add_tcp_relay(m->net_crypto, ip_port, public_key); + } +} + +/* + * 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_UNKNOWN for unknown error. + * 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 client_id[crypto_box_PUBLICKEYBYTES]; + id_copy(client_id, address); + + if (!public_key_valid(client_id)) + 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(client_id, m->net_crypto->self_public_key)) + return FAERR_OWNKEY; + + int32_t friend_id = getfriend_id(m, client_id); + + 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; + } + + /* 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)); + + int32_t onion_friendnum = onion_addfriend(m->onion_c, client_id); + + if (onion_friendnum == -1) + return FAERR_UNKNOWN; + + uint32_t i; + + for (i = 0; i <= m->numfriends; ++i) { + if (m->friendlist[i].status == NOFRIEND) { + m->friendlist[i].onion_friendnum = onion_friendnum; + m->friendlist[i].status = FRIEND_ADDED; + m->friendlist[i].crypt_connection_id = -1; + m->friendlist[i].friendrequest_lastsent = 0; + m->friendlist[i].friendrequest_timeout = FRIENDREQUEST_TIMEOUT; + id_copy(m->friendlist[i].client_id, client_id); + m->friendlist[i].statusmessage = calloc(1, 1); + m->friendlist[i].statusmessage_length = 1; + m->friendlist[i].userstatus = USERSTATUS_NONE; + m->friendlist[i].is_typing = 0; + memcpy(m->friendlist[i].info, data, length); + m->friendlist[i].info_size = length; + m->friendlist[i].message_id = 0; + m->friendlist[i].receives_read_receipts = 1; /* Default: YES. */ + memcpy(&(m->friendlist[i].friendrequest_nospam), address + crypto_box_PUBLICKEYBYTES, sizeof(uint32_t)); + recv_tcp_relay_handler(m->onion_c, onion_friendnum, &tcp_relay_node_callback, m, i); + + if (m->numfriends == i) + ++m->numfriends; + + return i; + } + } + + return FAERR_UNKNOWN; +} + +int32_t m_addfriend_norequest(Messenger *m, const uint8_t *client_id) +{ + if (getfriend_id(m, client_id) != -1) + return -1; + + if (!public_key_valid(client_id)) + return -1; + + /* Resize the friend list if necessary. */ + if (realloc_friendlist(m, m->numfriends + 1) != 0) + return -1; + + if (id_equal(client_id, m->net_crypto->self_public_key)) + return -1; + + memset(&(m->friendlist[m->numfriends]), 0, sizeof(Friend)); + + int32_t onion_friendnum = onion_addfriend(m->onion_c, client_id); + + if (onion_friendnum == -1) + return -1; + + uint32_t i; + + for (i = 0; i <= m->numfriends; ++i) { + if (m->friendlist[i].status == NOFRIEND) { + m->friendlist[i].onion_friendnum = onion_friendnum; + m->friendlist[i].status = FRIEND_CONFIRMED; + m->friendlist[i].crypt_connection_id = -1; + m->friendlist[i].friendrequest_lastsent = 0; + id_copy(m->friendlist[i].client_id, client_id); + m->friendlist[i].statusmessage = calloc(1, 1); + m->friendlist[i].statusmessage_length = 1; + m->friendlist[i].userstatus = USERSTATUS_NONE; + m->friendlist[i].is_typing = 0; + m->friendlist[i].message_id = 0; + m->friendlist[i].receives_read_receipts = 1; /* Default: YES. */ + recv_tcp_relay_handler(m->onion_c, onion_friendnum, &tcp_relay_node_callback, m, i); + + if (m->numfriends == i) + ++m->numfriends; + + return i; + } + } + + return -1; +} + +/* 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->friendlist[friendnumber].status == FRIEND_ONLINE) + remove_online_friend(m, friendnumber); + + onion_delfriend(m->onion_c, m->friendlist[friendnumber].onion_friendnum); + crypto_kill(m->net_crypto, m->friendlist[friendnumber].crypt_connection_id); + free(m->friendlist[friendnumber].statusmessage); + remove_request_received(&(m->fr), m->friendlist[friendnumber].client_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; + + return m->friendlist[friendnumber].status == FRIEND_ONLINE; +} + +int m_friend_exists(const Messenger *m, int32_t friendnumber) +{ + if (friend_not_valid(m, friendnumber)) + return 0; + + return m->friendlist[friendnumber].status > NOFRIEND; +} + +/* Send a text chat message to an online friend. + * + * return the message id if packet was successfully put into the send queue. + * return 0 if it was not. + */ +uint32_t m_sendmessage(Messenger *m, int32_t friendnumber, const uint8_t *message, uint32_t length) +{ + if (friend_not_valid(m, friendnumber)) + return 0; + + uint32_t msgid = ++m->friendlist[friendnumber].message_id; + + if (msgid == 0) + msgid = 1; // Otherwise, false error + + if (m_sendmessage_withid(m, friendnumber, msgid, message, length)) { + return msgid; + } + + return 0; +} + +uint32_t m_sendmessage_withid(Messenger *m, int32_t friendnumber, uint32_t theid, const uint8_t *message, + uint32_t length) +{ + if (length >= (MAX_CRYPTO_DATA_SIZE - sizeof(theid))) + return 0; + + uint8_t temp[MAX_CRYPTO_DATA_SIZE]; + theid = htonl(theid); + memcpy(temp, &theid, sizeof(theid)); + memcpy(temp + sizeof(theid), message, length); + return write_cryptpacket_id(m, friendnumber, PACKET_ID_MESSAGE, temp, length + sizeof(theid)); +} + +/* Send an action to an online friend. + * + * return the message id if packet was successfully put into the send queue. + * return 0 if it was not. + */ +uint32_t m_sendaction(Messenger *m, int32_t friendnumber, const uint8_t *action, uint32_t length) +{ + if (friend_not_valid(m, friendnumber)) + return 0; + + uint32_t msgid = ++m->friendlist[friendnumber].message_id; + + if (msgid == 0) + msgid = 1; // Otherwise, false error + + if (m_sendaction_withid(m, friendnumber, msgid, action, length)) { + return msgid; + } + + return 0; +} + +uint32_t m_sendaction_withid(const Messenger *m, int32_t friendnumber, uint32_t theid, const uint8_t *action, + uint32_t length) +{ + if (length >= (MAX_CRYPTO_DATA_SIZE - sizeof(theid))) + return 0; + + uint8_t temp[MAX_CRYPTO_DATA_SIZE]; + theid = htonl(theid); + memcpy(temp, &theid, sizeof(theid)); + memcpy(temp + sizeof(theid), action, length); + return write_cryptpacket_id(m, friendnumber, PACKET_ID_ACTION, temp, length + sizeof(theid)); +} + +/* 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 || length == 0) + return 0; + + return write_cryptpacket_id(m, friendnumber, PACKET_ID_NICKNAME, name, length); +} + +/* 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 || length == 0) + return -1; + + 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; + + for (i = 0; i < m->numchats; i++) + if (m->chats[i] != NULL) + set_nick(m->chats[i], name, length); /* TODO: remove this (group nicks should not be tied to the global one) */ + + 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; + + 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; + } + + 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, uint32_t maxlen) +{ + int msglen = MIN(maxlen, m->statusmessage_length); + memcpy(buf, m->statusmessage, msglen); + memset(buf + msglen, 0, maxlen - msglen); + return msglen; +} + +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 -1; + + return m->friendlist[friendnumber].ping_lastrecv; +} + +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; + + m->friendlist[friendnumber].user_istyping = is_typing; + m->friendlist[friendnumber].user_istyping_sent = 0; + + return 0; +} + +uint8_t 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); +} + +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)); +} + +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)); +} + +static int send_ping(const Messenger *m, int32_t friendnumber) +{ + int ret = write_cryptpacket_id(m, friendnumber, PACKET_ID_ALIVE, 0, 0); + + if (ret == 1) + m->friendlist[friendnumber].ping_lastsent = unix_time(); + + return ret; +} + +static int send_relays(const Messenger *m, int32_t friendnumber) +{ + Node_format nodes[MAX_SHARED_RELAYS]; + uint8_t data[1024]; + int n, length; + + n = copy_connected_tcp_relays(m->net_crypto, nodes, MAX_SHARED_RELAYS); + length = pack_nodes(data, sizeof(data), nodes, n); + + int ret = write_cryptpacket_id(m, friendnumber, PACKET_ID_SHARE_RELAYS, data, length); + + if (ret == 1) + m->friendlist[friendnumber].share_relays_lastsent = unix_time(); + + return ret; +} + + + +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; + + uint8_t *newstatus = calloc(length + 1, 1); + memcpy(newstatus, status, length); + free(m->friendlist[friendnumber].statusmessage); + m->friendlist[friendnumber].statusmessage = newstatus; + 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; +} + +/* Sets whether we send read receipts for friendnumber. */ +void m_set_sends_receipts(Messenger *m, int32_t friendnumber, int yesno) +{ + if (yesno != 0 && yesno != 1) + return; + + if (friend_not_valid(m, friendnumber)) + return; + + m->friendlist[friendnumber].receives_read_receipts = yesno; +} + +/* static void (*friend_request)(uint8_t *, uint8_t *, uint16_t); */ +/* 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 *, uint16_t, + void *), void *userdata) +{ + void (*handle_friendrequest)(void *, const uint8_t *, const uint8_t *, uint16_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, int32_t, const uint8_t *, uint16_t, void *), + void *userdata) +{ + m->friend_message = function; + m->friend_message_userdata = userdata; +} + +void m_callback_action(Messenger *m, void (*function)(Messenger *m, int32_t, const uint8_t *, uint16_t, void *), + void *userdata) +{ + m->friend_action = function; + m->friend_action_userdata = userdata; +} + +void m_callback_namechange(Messenger *m, void (*function)(Messenger *m, int32_t, const uint8_t *, uint16_t, void *), + void *userdata) +{ + m->friend_namechange = function; + m->friend_namechange_userdata = userdata; +} + +void m_callback_statusmessage(Messenger *m, void (*function)(Messenger *m, int32_t, const uint8_t *, uint16_t, void *), + void *userdata) +{ + m->friend_statusmessagechange = function; + m->friend_statuschange_userdata = userdata; +} + +void m_callback_userstatus(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, void *), void *userdata) +{ + m->friend_userstatuschange = function; + m->friend_userstatuschange_userdata = userdata; +} + +void m_callback_typingchange(Messenger *m, void(*function)(Messenger *m, int32_t, uint8_t, void *), void *userdata) +{ + m->friend_typingchange = function; + m->friend_typingchange_userdata = userdata; +} + +void m_callback_read_receipt(Messenger *m, void (*function)(Messenger *m, int32_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, int32_t, uint8_t, void *), void *userdata) +{ + m->friend_connectionstatuschange = function; + m->friend_connectionstatuschange_userdata = userdata; +} + +void m_callback_connectionstatus_internal_av(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, void *), + void *userdata) +{ + m->friend_connectionstatuschange_internal = function; + m->friend_connectionstatuschange_internal_userdata = userdata; +} + +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; + + onion_set_friend_online(m->onion_c, m->friendlist[friendnumber].onion_friendnum, is_online); + + if (is_online != was_online) { + if (was_online) { + break_files(m, friendnumber); + remove_online_friend(m, friendnumber); + } else { + add_online_friend(m, friendnumber); + } + + m->friendlist[friendnumber].status = status; + + if (m->friend_connectionstatuschange) + m->friend_connectionstatuschange(m, friendnumber, is_online, m->friend_connectionstatuschange_userdata); + + 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; +} + +int write_cryptpacket_id(const Messenger *m, int32_t friendnumber, uint8_t packet_id, const uint8_t *data, + uint32_t length) +{ + 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, m->friendlist[friendnumber].crypt_connection_id, packet, length + 1) != -1; +} + +/**********GROUP CHATS************/ + +/* return 1 if the groupnumber is not valid. + * return 0 if the groupnumber is valid. + */ +static uint8_t groupnumber_not_valid(const Messenger *m, int groupnumber) +{ + if ((unsigned int)groupnumber >= m->numchats) + return 1; + + if (m->chats == NULL) + return 1; + + if (m->chats[groupnumber] == NULL) + return 1; + + return 0; +} + + +/* returns valid ip port of connected friend on success + * returns zeroed out IP_Port on failure + */ +static IP_Port get_friend_ipport(const Messenger *m, int32_t friendnumber) +{ + IP_Port zero; + memset(&zero, 0, sizeof(zero)); + + if (friend_not_valid(m, friendnumber)) + return zero; + + int crypt_id = m->friendlist[friendnumber].crypt_connection_id; + + uint8_t direct_connected; + + if (crypto_connection_status(m->net_crypto, crypt_id, &direct_connected) != CRYPTO_CONN_ESTABLISHED) + return zero; + + if (direct_connected == 0) + return zero; + + return m->net_crypto->crypto_connections[crypt_id].ip_port; +} + +/* returns the group number of the chat with public key group_public_key. + * returns -1 on failure. + */ +static int group_num(const Messenger *m, const uint8_t *group_public_key) +{ + uint32_t i; + + for (i = 0; i < m->numchats; ++i) { + if (m->chats[i] != NULL) + if (id_equal(m->chats[i]->self_public_key, group_public_key)) + return i; + } + + return -1; +} + +/* Set the callback for group invites. + * + * Function(Messenger *m, int32_t friendnumber, uint8_t *group_public_key, void *userdata) + */ +void m_callback_group_invite(Messenger *m, void (*function)(Messenger *m, int32_t, const uint8_t *, void *), + void *userdata) +{ + m->group_invite = function; + m->group_invite_userdata = userdata; +} + +/* Set the callback for group messages. + * + * Function(Tox *tox, int groupnumber, int friendgroupnumber, uint8_t * message, uint16_t length, void *userdata) + */ +void m_callback_group_message(Messenger *m, void (*function)(Messenger *m, int, int, const uint8_t *, uint16_t, void *), + void *userdata) +{ + m->group_message = function; + m->group_message_userdata = userdata; +} + +/* Set the callback for group actions. + * + * Function(Tox *tox, int groupnumber, int friendgroupnumber, uint8_t * message, uint16_t length, void *userdata) + */ +void m_callback_group_action(Messenger *m, void (*function)(Messenger *m, int, int, const uint8_t *, uint16_t, void *), + void *userdata) +{ + m->group_action = function; + m->group_action_userdata = 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 m_callback_group_namelistchange(Messenger *m, void (*function)(Messenger *m, int, int, uint8_t, void *), + void *userdata) +{ + m->group_namelistchange = function; + m->group_namelistchange_userdata = userdata; +} + +static int get_chat_num(const Messenger *m, const Group_Chat *chat) +{ + uint32_t i; + + for (i = 0; i < m->numchats; ++i) { //TODO: remove this + if (m->chats[i] == chat) + return i; + } + + return -1; +} + +static void group_message_function(Group_Chat *chat, int peer_number, const uint8_t *message, uint16_t length, + void *userdata) +{ + Messenger *m = userdata; + int i = get_chat_num(m, chat); + + if (i == -1) + return; + + uint8_t message_terminated[length + 1]; + memcpy(message_terminated, message, length); + message_terminated[length] = 0; /* Force NULL terminator */ + + if (m->group_message) + (*m->group_message)(m, i, peer_number, message_terminated, length, m->group_message_userdata); +} + +static void group_action_function(Group_Chat *chat, int peer_number, const uint8_t *action, uint16_t length, + void *userdata) +{ + Messenger *m = userdata; + int i = get_chat_num(m, chat); + + if (i == -1) + return; + + uint8_t action_terminated[length + 1]; + memcpy(action_terminated, action, length); + action_terminated[length] = 0; /* Force NULL terminator */ + + if (m->group_action) + (*m->group_action)(m, i, peer_number, action_terminated, length, m->group_action_userdata); +} + +static void group_namelistchange_function(Group_Chat *chat, int peer, uint8_t change, void *userdata) +{ + Messenger *m = userdata; + int i = get_chat_num(m, chat); + + if (i == -1) + return; + + if (m->group_namelistchange) + (*m->group_namelistchange)(m, i, peer, change, m->group_namelistchange_userdata); +} + + +/* Creates a new groupchat and puts it in the chats array. + * + * return group number on success. + * return -1 on failure. + */ +int add_groupchat(Messenger *m) +{ + uint32_t i; + + for (i = 0; i < m->numchats; ++i) { + if (m->chats[i] == NULL) { + Group_Chat *newchat = new_groupchat(m->net); + + if (newchat == NULL) + return -1; + + callback_groupmessage(newchat, &group_message_function, m); + callback_groupaction(newchat, &group_action_function, m); + callback_namelistchange(newchat, &group_namelistchange_function, m); + /* TODO: remove this (group nicks should not be tied to the global one) */ + set_nick(newchat, m->name, m->name_length); + m->chats[i] = newchat; + return i; + } + } + + Group_Chat **temp; + temp = realloc(m->chats, sizeof(Group_Chat *) * (m->numchats + 1)); + + if (temp == NULL) + return -1; + + m->chats = temp; + temp[m->numchats] = new_groupchat(m->net); + + if (temp[m->numchats] == NULL) + return -1; + + callback_groupmessage(temp[m->numchats], &group_message_function, m); + callback_groupaction(temp[m->numchats], &group_action_function, m); + callback_namelistchange(temp[m->numchats], &group_namelistchange_function, m); + /* TODO: remove this (group nicks should not be tied to the global one) */ + set_nick(temp[m->numchats], m->name, m->name_length); + ++m->numchats; + return (m->numchats - 1); +} + +/* Delete a groupchat from the chats array. + * + * return 0 on success. + * return -1 if failure. + */ +int del_groupchat(Messenger *m, int groupnumber) +{ + if ((unsigned int)groupnumber >= m->numchats) + return -1; + + if (m->chats == NULL) + return -1; + + if (m->chats[groupnumber] == NULL) + return -1; + + kill_groupchat(m->chats[groupnumber]); + m->chats[groupnumber] = NULL; + + uint32_t i; + + for (i = m->numchats; i != 0; --i) { + if (m->chats[i - 1] != NULL) + break; + } + + m->numchats = i; + + if (i == 0) { + free(m->chats); + m->chats = NULL; + } else { + Group_Chat **temp = realloc(m->chats, sizeof(Group_Chat *) * i); + + if (temp != NULL) + m->chats = temp; + } + + return 0; +} + +/* 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 m_group_peername(const Messenger *m, int groupnumber, int peernumber, uint8_t *name) +{ + if ((unsigned int)groupnumber >= m->numchats) + return -1; + + if (m->chats == NULL) + return -1; + + if (m->chats[groupnumber] == NULL) + return -1; + + return group_peername(m->chats[groupnumber], peernumber, name); +} + +/* Store the fact that we invited a specific friend. + */ +static void group_store_friendinvite(Messenger *m, int32_t friendnumber, int groupnumber) +{ + /* Add 1 to the groupchat number because 0 (default value in invited_groups) is a valid groupchat number */ + m->friendlist[friendnumber].invited_groups[m->friendlist[friendnumber].invited_groups_num % MAX_INVITED_GROUPS] = + groupnumber + 1; + ++m->friendlist[friendnumber].invited_groups_num; +} + +/* return 1 if that friend was invited to the group + * return 0 if the friend was not or error. + */ +static uint8_t group_invited(const Messenger *m, int32_t friendnumber, int groupnumber) +{ + + uint32_t i; + uint16_t num = MAX_INVITED_GROUPS; + + if (MAX_INVITED_GROUPS > m->friendlist[friendnumber].invited_groups_num) + num = m->friendlist[friendnumber].invited_groups_num; + + for (i = 0; i < num; ++i) { + if (m->friendlist[friendnumber].invited_groups[i] == groupnumber + 1) { + return 1; + } + } + + return 0; +} + +/* invite friendnumber to groupnumber + * return 0 on success + * return -1 on failure + */ +int invite_friend(Messenger *m, int32_t friendnumber, int groupnumber) +{ + if (friend_not_valid(m, friendnumber) || (unsigned int)groupnumber >= m->numchats) + return -1; + + if (m->chats == NULL) + return -1; + + if (m->friendlist[friendnumber].status == NOFRIEND || m->chats[groupnumber] == NULL) + return -1; + + group_store_friendinvite(m, friendnumber, groupnumber); + + if (write_cryptpacket_id(m, friendnumber, PACKET_ID_INVITE_GROUPCHAT, m->chats[groupnumber]->self_public_key, + crypto_box_PUBLICKEYBYTES) == 0) + return -1; + + return 0; +} + + +/* Join a group (you need to have been invited first.) + * + * returns group number on success + * returns -1 on failure. + */ +int join_groupchat(Messenger *m, int32_t friendnumber, const uint8_t *friend_group_public_key) +{ + if (friend_not_valid(m, friendnumber)) + return -1; + + uint8_t data[crypto_box_PUBLICKEYBYTES * 2]; + int groupnum = add_groupchat(m); + + if (groupnum == -1) + return -1; + + IP_Port friend_ip = get_friend_ipport(m, friendnumber); + + if (friend_ip.ip.family == 0) { + del_groupchat(m, groupnum); + return -1; + } + + id_copy(data, friend_group_public_key); + id_copy(data + crypto_box_PUBLICKEYBYTES, m->chats[groupnum]->self_public_key); + + if (write_cryptpacket_id(m, friendnumber, PACKET_ID_JOIN_GROUPCHAT, data, sizeof(data))) { + chat_bootstrap_nonlazy(m->chats[groupnum], get_friend_ipport(m, friendnumber), + friend_group_public_key); //TODO: check if ip returned is zero? + return groupnum; + } + + del_groupchat(m, groupnum); + return -1; +} + + +/* send a group message + * return 0 on success + * return -1 on failure + */ +int group_message_send(const Messenger *m, int groupnumber, const uint8_t *message, uint32_t length) +{ + if (groupnumber_not_valid(m, groupnumber)) + return -1; + + if (group_sendmessage(m->chats[groupnumber], message, length) > 0) + return 0; + + return -1; +} + +/* send a group action + * return 0 on success + * return -1 on failure + */ +int group_action_send(const Messenger *m, int groupnumber, const uint8_t *action, uint32_t length) +{ + if (groupnumber_not_valid(m, groupnumber)) + return -1; + + if (group_sendaction(m->chats[groupnumber], action, length) > 0) + return 0; + + return -1; +} + +/* Return the number of peers in the group chat on success. + * return -1 on failure + */ +int group_number_peers(const Messenger *m, int groupnumber) +{ + if (groupnumber_not_valid(m, groupnumber)) + return -1; + + return group_numpeers(m->chats[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 group_names(const Messenger *m, int groupnumber, uint8_t names[][MAX_NICK_BYTES], uint16_t lengths[], + uint16_t length) +{ + if (groupnumber_not_valid(m, groupnumber)) + return -1; + + return group_client_names(m->chats[groupnumber], names, lengths, length); +} + +static int handle_group(void *object, IP_Port source, const uint8_t *packet, uint32_t length) +{ + Messenger *m = object; + + if (length < crypto_box_PUBLICKEYBYTES + 1) { + return 1; + } + + uint32_t i; + + for (i = 0; i < m->numchats; ++i) { + if (m->chats[i] == NULL) + continue; + + if (id_equal(packet + 1, m->chats[i]->self_public_key)) + return handle_groupchatpacket(m->chats[i], source, packet, length); + } + + return 1; +} + +static void do_allgroupchats(Messenger *m) +{ + uint32_t i; + + for (i = 0; i < m->numchats; ++i) { + if (m->chats[i] != NULL) + do_groupchat(m->chats[i]); + } +} + +/****************FILE SENDING*****************/ + + +/* Set the callback for file send requests. + * + * Function(Tox *tox, int32_t friendnumber, uint8_t filenumber, uint64_t filesize, uint8_t *filename, uint16_t filename_length, void *userdata) + */ +void callback_file_sendrequest(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint64_t, const uint8_t *, + uint16_t, void *), void *userdata) +{ + m->file_sendrequest = function; + m->file_sendrequest_userdata = userdata; +} + +/* Set the callback for file control requests. + * + * Function(Tox *tox, int32_t friendnumber, uint8_t send_receive, uint8_t filenumber, uint8_t control_type, uint8_t *data, uint16_t length, void *userdata) + * + */ +void callback_file_control(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint8_t, uint8_t, + const uint8_t *, uint16_t, void *), void *userdata) +{ + m->file_filecontrol = function; + m->file_filecontrol_userdata = userdata; +} + +/* Set the callback for file data. + * + * Function(Tox *tox, int32_t friendnumber, uint8_t filenumber, uint8_t *data, uint16_t length, void *userdata) + * + */ +void callback_file_data(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, const uint8_t *, uint16_t length, + void *), void *userdata) +{ + m->file_filedata = function; + m->file_filedata_userdata = userdata; +} + +#define MAX_FILENAME_LENGTH 255 + +/* Send a file send request. + * Maximum filename length is 255 bytes. + * return 1 on success + * return 0 on failure + */ +int file_sendrequest(const Messenger *m, int32_t friendnumber, uint8_t filenumber, uint64_t filesize, + 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[MAX_FILENAME_LENGTH + 1 + sizeof(filesize)]; + packet[0] = filenumber; + host_to_net((uint8_t *)&filesize, sizeof(filesize)); + memcpy(packet + 1, &filesize, sizeof(filesize)); + memcpy(packet + 1 + sizeof(filesize), filename, filename_length); + return write_cryptpacket_id(m, friendnumber, PACKET_ID_FILE_SENDREQUEST, packet, + 1 + sizeof(filesize) + filename_length); +} + +/* Send a file send request. + * Maximum filename length is 255 bytes. + * return file number on success + * return -1 on failure + */ +int new_filesender(const Messenger *m, int32_t friendnumber, uint64_t filesize, const uint8_t *filename, + uint16_t filename_length) +{ + if (friend_not_valid(m, friendnumber)) + return -1; + + 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 -1; + + if (file_sendrequest(m, friendnumber, i, filesize, filename, filename_length) == 0) + return -1; + + m->friendlist[friendnumber].file_sending[i].status = FILESTATUS_NOT_ACCEPTED; + m->friendlist[friendnumber].file_sending[i].size = filesize; + m->friendlist[friendnumber].file_sending[i].transferred = 0; + return i; +} + +/* Send a file control request. + * send_receive is 0 if we want the control packet to target a sending file, 1 if it targets a receiving file. + * + * return 0 on success + * return -1 on failure + */ +int file_control(const Messenger *m, int32_t friendnumber, uint8_t send_receive, uint8_t filenumber, uint8_t message_id, + const uint8_t *data, uint16_t length) +{ + if (length > MAX_CRYPTO_DATA_SIZE - 3) + return -1; + + if (friend_not_valid(m, friendnumber)) + return -1; + + if (send_receive == 1) { + if (m->friendlist[friendnumber].file_receiving[filenumber].status == FILESTATUS_NONE) + return -1; + } else { + if (m->friendlist[friendnumber].file_sending[filenumber].status == FILESTATUS_NONE) + return -1; + } + + if (send_receive > 1) + return -1; + + uint8_t packet[MAX_CRYPTO_DATA_SIZE]; + packet[0] = send_receive; + packet[1] = filenumber; + packet[2] = message_id; + uint64_t transferred = 0; + + if (message_id == FILECONTROL_RESUME_BROKEN) { + if (length != sizeof(uint64_t)) + return -1; + + uint8_t remaining[sizeof(uint64_t)]; + memcpy(remaining, data, sizeof(uint64_t)); + host_to_net(remaining, sizeof(uint64_t)); + memcpy(packet + 3, remaining, sizeof(uint64_t)); + memcpy(&transferred, data, sizeof(uint64_t)); + } else { + memcpy(packet + 3, data, length); + } + + if (write_cryptpacket_id(m, friendnumber, PACKET_ID_FILE_CONTROL, packet, length + 3)) { + if (send_receive == 1) + switch (message_id) { + case FILECONTROL_ACCEPT: + m->friendlist[friendnumber].file_receiving[filenumber].status = FILESTATUS_TRANSFERRING; + break; + + case FILECONTROL_PAUSE: + m->friendlist[friendnumber].file_receiving[filenumber].status = FILESTATUS_PAUSED_BY_US; + break; + + case FILECONTROL_KILL: + case FILECONTROL_FINISHED: + m->friendlist[friendnumber].file_receiving[filenumber].status = FILESTATUS_NONE; + break; + + case FILECONTROL_RESUME_BROKEN: + m->friendlist[friendnumber].file_receiving[filenumber].status = FILESTATUS_PAUSED_BY_OTHER; + m->friendlist[friendnumber].file_receiving[filenumber].transferred = transferred; + break; + } + else + switch (message_id) { + case FILECONTROL_ACCEPT: + m->friendlist[friendnumber].file_sending[filenumber].status = FILESTATUS_TRANSFERRING; + break; + + case FILECONTROL_PAUSE: + m->friendlist[friendnumber].file_sending[filenumber].status = FILESTATUS_PAUSED_BY_US; + break; + + case FILECONTROL_KILL: + m->friendlist[friendnumber].file_sending[filenumber].status = FILESTATUS_NONE; + break; + + case FILECONTROL_FINISHED: + break; + } + + return 0; + } else { + return -1; + } +} + +#define MIN_SLOTS_FREE (CRYPTO_MIN_QUEUE_LENGTH / 2) +/* Send file data. + * + * return 0 on success + * return -1 on failure + */ +int file_data(const Messenger *m, int32_t friendnumber, uint8_t filenumber, const uint8_t *data, uint16_t length) +{ + if (length > MAX_CRYPTO_DATA_SIZE - 1) + return -1; + + if (friend_not_valid(m, friendnumber)) + return -1; + + if (m->friendlist[friendnumber].file_sending[filenumber].status != FILESTATUS_TRANSFERRING) + return -1; + + /* Prevent file sending from filling up the entire buffer preventing messages from being sent. */ + if (crypto_num_free_sendqueue_slots(m->net_crypto, m->friendlist[friendnumber].crypt_connection_id) < MIN_SLOTS_FREE) + return -1; + + uint8_t packet[MAX_CRYPTO_DATA_SIZE]; + packet[0] = filenumber; + memcpy(packet + 1, data, length); + + if (write_cryptpacket_id(m, friendnumber, PACKET_ID_FILE_DATA, packet, length + 1)) { + m->friendlist[friendnumber].file_sending[filenumber].transferred += length; + return 0; + } + + return -1; + +} + +/* 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; + } +} + +/* Run this when the friend disconnects. + * Sets all current file transfers to broken. + */ +static void break_files(const Messenger *m, int32_t friendnumber) +{ + uint32_t i; + + 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_BROKEN; + + if (m->friendlist[friendnumber].file_receiving[i].status != FILESTATUS_NONE) + m->friendlist[friendnumber].file_receiving[i].status = FILESTATUS_BROKEN; + } +} + +static int handle_filecontrol(const Messenger *m, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber, + uint8_t message_id, uint8_t *data, + uint16_t length) +{ + if (receive_send > 1) + return -1; + + if (receive_send == 0) { + if (m->friendlist[friendnumber].file_receiving[filenumber].status == FILESTATUS_NONE) { + /* Tell the other to kill the file sending if we don't know this one. */ + m->friendlist[friendnumber].file_receiving[filenumber].status = FILESTATUS_TEMPORARY; + file_control(m, friendnumber, !receive_send, filenumber, FILECONTROL_KILL, NULL, 0); + m->friendlist[friendnumber].file_receiving[filenumber].status = FILESTATUS_NONE; + return -1; + + } + + switch (message_id) { + case FILECONTROL_ACCEPT: + if (m->friendlist[friendnumber].file_receiving[filenumber].status != FILESTATUS_PAUSED_BY_US) { + m->friendlist[friendnumber].file_receiving[filenumber].status = FILESTATUS_TRANSFERRING; + return 0; + } + + return -1; + + case FILECONTROL_PAUSE: + if (m->friendlist[friendnumber].file_receiving[filenumber].status != FILESTATUS_PAUSED_BY_US) { + m->friendlist[friendnumber].file_receiving[filenumber].status = FILESTATUS_PAUSED_BY_OTHER; + return 0; + } + + return -1; + + case FILECONTROL_KILL: + m->friendlist[friendnumber].file_receiving[filenumber].status = FILESTATUS_NONE; + + case FILECONTROL_FINISHED: + return 0; + } + } else { + if (m->friendlist[friendnumber].file_sending[filenumber].status == FILESTATUS_NONE) { + /* Tell the other to kill the file sending if we don't know this one. */ + m->friendlist[friendnumber].file_sending[filenumber].status = FILESTATUS_TEMPORARY; + file_control(m, friendnumber, !receive_send, filenumber, FILECONTROL_KILL, NULL, 0); + m->friendlist[friendnumber].file_sending[filenumber].status = FILESTATUS_NONE; + return -1; + } + + switch (message_id) { + case FILECONTROL_ACCEPT: + if (m->friendlist[friendnumber].file_sending[filenumber].status != FILESTATUS_PAUSED_BY_US) { + m->friendlist[friendnumber].file_sending[filenumber].status = FILESTATUS_TRANSFERRING; + return 0; + } + + return -1; + + case FILECONTROL_PAUSE: + if (m->friendlist[friendnumber].file_sending[filenumber].status != FILESTATUS_PAUSED_BY_US) { + m->friendlist[friendnumber].file_sending[filenumber].status = FILESTATUS_PAUSED_BY_OTHER; + } + + return 0; + + case FILECONTROL_KILL: + case FILECONTROL_FINISHED: + m->friendlist[friendnumber].file_sending[filenumber].status = FILESTATUS_NONE; + return 0; + + case FILECONTROL_RESUME_BROKEN: { + if (m->friendlist[friendnumber].file_sending[filenumber].status == FILESTATUS_BROKEN && length == sizeof(uint64_t)) { + m->friendlist[friendnumber].file_sending[filenumber].status = FILESTATUS_PAUSED_BY_US; + net_to_host(data, sizeof(uint64_t)); + return 0; + } + + return -1; + } + } + } + + return -1; +} + +/**************************************/ + +/* 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, int32_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); +} + +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 (m->friendlist[friend_num].lossy_packethandlers[packet[0] % PACKET_ID_LOSSY_RANGE_SIZE].function) + return m->friendlist[friend_num].lossy_packethandlers[packet[0] % PACKET_ID_LOSSY_RANGE_SIZE].function( + m->friendlist[friend_num].lossy_packethandlers[packet[0] % PACKET_ID_LOSSY_RANGE_SIZE].object, packet, length); + + return 1; +} + +int custom_lossy_packet_registerhandler(Messenger *m, int32_t friendnumber, uint8_t byte, + int (*packet_handler_callback)(void *object, const uint8_t *data, uint32_t len), 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_ID_LOSSY_RANGE_SIZE)) + return -1; + + m->friendlist[friendnumber].lossy_packethandlers[byte % PACKET_ID_LOSSY_RANGE_SIZE].function = packet_handler_callback; + m->friendlist[friendnumber].lossy_packethandlers[byte % PACKET_ID_LOSSY_RANGE_SIZE].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 (m->friendlist[friendnumber].status != FRIEND_ONLINE) + return -1; + + if (m->friendlist[friendnumber].crypt_connection_id == -1) + return -1; + + return send_lossy_cryptpacket(m->net_crypto, m->friendlist[friendnumber].crypt_connection_id, data, length); +} + +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->friendlist[friend_num].lossless_packethandlers[packet[0] % PACKET_ID_LOSSLESS_RANGE_SIZE].function) + return m->friendlist[friend_num].lossless_packethandlers[packet[0] % PACKET_ID_LOSSLESS_RANGE_SIZE].function( + m->friendlist[friend_num].lossless_packethandlers[packet[0] % PACKET_ID_LOSSLESS_RANGE_SIZE].object, packet, length); + + return 1; +} + +int custom_lossless_packet_registerhandler(Messenger *m, int32_t friendnumber, uint8_t byte, + int (*packet_handler_callback)(void *object, const uint8_t *data, uint32_t len), void *object) +{ + if (friend_not_valid(m, friendnumber)) + return -1; + + if (byte < PACKET_ID_LOSSLESS_RANGE_START) + return -1; + + if (byte >= (PACKET_ID_LOSSLESS_RANGE_START + PACKET_ID_LOSSLESS_RANGE_SIZE)) + return -1; + + m->friendlist[friendnumber].lossless_packethandlers[byte % PACKET_ID_LOSSLESS_RANGE_SIZE].function = + packet_handler_callback; + m->friendlist[friendnumber].lossless_packethandlers[byte % PACKET_ID_LOSSLESS_RANGE_SIZE].object = object; + return 0; +} + +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 (m->friendlist[friendnumber].status != FRIEND_ONLINE) + return -1; + + if (m->friendlist[friendnumber].crypt_connection_id == -1) + return -1; + + return write_cryptpacket(m->net_crypto, m->friendlist[friendnumber].crypt_connection_id, data, length) != -1; +} + +/* Function to filter out some friend requests*/ +static int friend_already_added(const uint8_t *client_id, void *data) +{ + const Messenger *m = data; + + if (getfriend_id(m, client_id) == -1) + return 0; + + return -1; +} + +/* Send a LAN discovery packet every LAN_DISCOVERY_INTERVAL seconds. */ +static void LANdiscovery(Messenger *m) +{ + if (m->last_LANdiscovery + LAN_DISCOVERY_INTERVAL < unix_time()) { + send_LANdiscovery(htons(TOX_PORT_DEFAULT), m->dht); + m->last_LANdiscovery = unix_time(); + } +} + +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_new_connections(void *object, New_Connection *n_c) +{ + Messenger *m = object; + int friend_id = getfriend_id(m, n_c->public_key); + + if (friend_id != -1) { + if (m->friendlist[friend_id].crypt_connection_id != -1) + return -1; + + int id = accept_crypto_connection(m->net_crypto, n_c); + connection_status_handler(m->net_crypto, id, &handle_status, m, friend_id); + connection_data_handler(m->net_crypto, id, &handle_packet, m, friend_id); + connection_lossy_data_handler(m->net_crypto, id, &handle_custom_lossy_packet, m, friend_id); + m->friendlist[friend_id].crypt_connection_id = id; + set_friend_status(m, friend_id, FRIEND_CONFIRMED); + return 0; + } + + return -1; +} + + +/* Run this at startup. */ +Messenger *new_messenger(uint8_t ipv6enabled) +{ + Messenger *m = calloc(1, sizeof(Messenger)); + + if ( ! m ) + return NULL; + + IP ip; + ip_init(&ip, ipv6enabled); + m->net = new_networking(ip, TOX_PORT_DEFAULT); + + if (m->net == NULL) { + free(m); + 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); + + if (m->net_crypto == NULL) { + kill_networking(m->net); + kill_DHT(m->dht); + free(m); + return NULL; + } + + new_connection_handler(m->net_crypto, &handle_new_connections, m); + + m->onion = new_onion(m->dht); + m->onion_a = new_onion_announce(m->dht); + m->onion_c = new_onion_client(m->net_crypto); + + if (!(m->onion && m->onion_a && m->onion_c)) { + kill_onion(m->onion); + kill_onion_announce(m->onion_a); + kill_onion_client(m->onion_c); + kill_DHT(m->dht); + kill_net_crypto(m->net_crypto); + kill_networking(m->net); + free(m); + return NULL; + } + + friendreq_init(&(m->fr), m->onion_c); + LANdiscovery_init(m->dht); + set_nospam(&(m->fr), random_int()); + set_filter_function(&(m->fr), &friend_already_added, m); + + networking_registerhandler(m->net, NET_PACKET_GROUP_CHATS, &handle_group, m); + + return m; +} + +/* Run this before closing shop. */ +void kill_messenger(Messenger *m) +{ + /* FIXME TODO: ideally cleanupMessenger will mirror initMessenger. + * This requires the other modules to expose cleanup functions. + */ + uint32_t i, numchats = m->numchats; + + for (i = 0; i < numchats; ++i) + del_groupchat(m, i); + + 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) { + if (m->friendlist[i].statusmessage) + free(m->friendlist[i].statusmessage); + } + + 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 everytime if friendrequest is assumed + * to have been sent unsuccessfully. + */ + f->friendrequest_timeout *= 2; + } +} + +static int handle_status(void *object, int i, uint8_t status) +{ + uint64_t temp_time = unix_time(); + Messenger *m = object; + + if (status) { /* Went online. */ + set_friend_status(m, i, FRIEND_ONLINE); + m->friendlist[i].name_sent = 0; + m->friendlist[i].userstatus_sent = 0; + m->friendlist[i].statusmessage_sent = 0; + m->friendlist[i].ping_lastrecv = temp_time; + } else { /* Went offline. */ + m->friendlist[i].crypt_connection_id = -1; + + 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; + uint64_t temp_time = unix_time(); + uint8_t packet_id = temp[0]; + uint8_t *data = temp + 1; + uint32_t data_length = len - 1; + + if (m->friendlist[i].status != FRIEND_ONLINE) + return -1; + + switch (packet_id) { + case PACKET_ID_ALIVE: { + m->friendlist[i].ping_lastrecv = temp_time; + break; + } + + case PACKET_ID_NICKNAME: { + if (data_length > MAX_NAME_LENGTH || data_length == 0) + 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 == 0 || 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_statuschange_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; + + uint8_t 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: { + const uint8_t *message_id = data; + uint8_t message_id_length = 4; + + if (data_length <= message_id_length) + break; + + const uint8_t *message = data + message_id_length; + uint16_t message_length = data_length - message_id_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; + + if (m->friendlist[i].receives_read_receipts) { + write_cryptpacket_id(m, i, PACKET_ID_RECEIPT, message_id, message_id_length); + } + + if (m->friend_message) + (*m->friend_message)(m, i, message_terminated, message_length, m->friend_message_userdata); + + break; + } + + case PACKET_ID_ACTION: { + const uint8_t *message_id = data; + uint8_t message_id_length = 4; + + if (data_length <= message_id_length) + break; + + const uint8_t *action = data + message_id_length; + uint16_t action_length = data_length - message_id_length; + + /* Make sure the NULL terminator is present. */ + uint8_t action_terminated[action_length + 1]; + memcpy(action_terminated, action, action_length); + action_terminated[action_length] = 0; + + if (m->friendlist[i].receives_read_receipts) { + write_cryptpacket_id(m, i, PACKET_ID_RECEIPT, message_id, message_id_length); + } + + if (m->friend_action) + (*m->friend_action)(m, i, action_terminated, action_length, m->friend_action_userdata); + + + break; + } + + case PACKET_ID_RECEIPT: { + uint32_t msgid; + + if (data_length < sizeof(msgid)) + break; + + memcpy(&msgid, data, sizeof(msgid)); + msgid = ntohl(msgid); + + if (m->read_receipt) + (*m->read_receipt)(m, i, msgid, m->read_receipt_userdata); + + break; + } + + case PACKET_ID_INVITE_GROUPCHAT: { + if (data_length != crypto_box_PUBLICKEYBYTES) + break; + + if (m->group_invite) + (*m->group_invite)(m, i, data, m->group_invite_userdata); + + break; + } + + case PACKET_ID_JOIN_GROUPCHAT: { + if (data_length != crypto_box_PUBLICKEYBYTES * 2) + break; + + int groupnum = group_num(m, data); + + if (groupnum == -1) + break; + + if (!group_invited(m, i, groupnum)) + break; + + group_newpeer(m->chats[groupnum], data + crypto_box_PUBLICKEYBYTES); + /* This is just there to speedup joining. */ + chat_bootstrap(m->chats[groupnum], get_friend_ipport(m, i), data + crypto_box_PUBLICKEYBYTES); + break; + } + + case PACKET_ID_FILE_SENDREQUEST: { + if (data_length < 1 + sizeof(uint64_t) + 1) + break; + + uint8_t filenumber = data[0]; + uint64_t filesize; + memcpy(&filesize, data + 1, sizeof(filesize)); + net_to_host((uint8_t *) &filesize, sizeof(filesize)); + m->friendlist[i].file_receiving[filenumber].status = FILESTATUS_NOT_ACCEPTED; + m->friendlist[i].file_receiving[filenumber].size = filesize; + m->friendlist[i].file_receiving[filenumber].transferred = 0; + + /* Force NULL terminate file name. */ + uint8_t filename_terminated[data_length - 1 - sizeof(uint64_t) + 1]; + memcpy(filename_terminated, data + 1 + sizeof(uint64_t), data_length - 1 - sizeof(uint64_t)); + filename_terminated[data_length - 1 - sizeof(uint64_t)] = 0; + + if (m->file_sendrequest) + (*m->file_sendrequest)(m, i, filenumber, filesize, filename_terminated, data_length - 1 - sizeof(uint64_t), + 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 (handle_filecontrol(m, i, send_receive, filenumber, control_type, data + 3, data_length - 3) == -1) + break; + + if (m->file_filecontrol) + (*m->file_filecontrol)(m, i, send_receive, filenumber, control_type, data + 3, data_length - 3, + m->file_filecontrol_userdata); + + break; + } + + case PACKET_ID_FILE_DATA: { + if (data_length < 2) + break; + + uint8_t filenumber = data[0]; + + if (m->friendlist[i].file_receiving[filenumber].status == FILESTATUS_NONE) + break; + + m->friendlist[i].file_receiving[filenumber].transferred += (data_length - 1); + + if (m->file_filedata) + (*m->file_filedata)(m, i, filenumber, data + 1, data_length - 1, m->file_filedata_userdata); + + 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; + } + + case PACKET_ID_SHARE_RELAYS: { + Node_format nodes[MAX_SHARED_RELAYS]; + int n; + + if ((n = unpack_nodes(nodes, MAX_SHARED_RELAYS, NULL, data, data_length, 1)) == -1) + break; + + int i; + + for (i = 0; i < n; i++) { + add_tcp_relay(m->net_crypto, nodes[i].ip_port, nodes[i].client_id); + } + + break; + } + + default: { + handle_custom_lossless_packet(object, i, temp, len); + break; + } + } + + return 0; +} + +static int friend_new_connection(Messenger *m, int32_t friendnumber, const uint8_t *real_public_key) +{ + if (friend_not_valid(m, friendnumber)) + return -1; + + if (m->friendlist[friendnumber].crypt_connection_id != -1) { + return -1; + } + + int id = new_crypto_connection(m->net_crypto, real_public_key); + + if (id == -1) + return -1; + + m->friendlist[friendnumber].crypt_connection_id = id; + connection_status_handler(m->net_crypto, id, &handle_status, m, friendnumber); + connection_data_handler(m->net_crypto, id, &handle_packet, m, friendnumber); + connection_lossy_data_handler(m->net_crypto, id, &handle_custom_lossy_packet, m, friendnumber); + return 0; +} + +/* TODO: Make this function not suck. */ +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_friendrequest(m->onion_c, m->friendlist[i].client_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); + } + + friend_new_connection(m, i, m->friendlist[i].client_id); + } + + if (m->friendlist[i].crypt_connection_id != -1) { + uint8_t dht_public_key1[crypto_box_PUBLICKEYBYTES]; + uint64_t timestamp1 = onion_getfriend_DHT_pubkey(m->onion_c, m->friendlist[i].onion_friendnum, dht_public_key1); + uint8_t dht_public_key2[crypto_box_PUBLICKEYBYTES]; + uint64_t timestamp2 = get_connection_dht_key(m->net_crypto, m->friendlist[i].crypt_connection_id, dht_public_key2); + + if (timestamp1 > timestamp2) { + set_connection_dht_public_key(m->net_crypto, m->friendlist[i].crypt_connection_id, dht_public_key1, timestamp1); + } else if (timestamp1 < timestamp2) { + onion_set_friend_DHT_pubkey(m->onion_c, m->friendlist[i].onion_friendnum, dht_public_key2, timestamp2); + } + + uint8_t direct_connected; + unsigned int status = crypto_connection_status(m->net_crypto, m->friendlist[i].crypt_connection_id, &direct_connected); + + if (direct_connected == 0 || status == CRYPTO_CONN_COOKIE_REQUESTING) { + IP_Port friendip; + + if (onion_getfriendip(m->onion_c, m->friendlist[i].onion_friendnum, &friendip) == 1) { + set_direct_ip_port(m->net_crypto, m->friendlist[i].crypt_connection_id, friendip); + } + } + } + + 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; + } + + if (m->friendlist[i].ping_lastsent + FRIEND_PING_INTERVAL < temp_time) { + send_ping(m, i); + } + + if (m->friendlist[i].ping_lastrecv + FRIEND_CONNECTION_TIMEOUT < temp_time) { + /* If we stopped receiving ping packets, kill it. */ + crypto_kill(m->net_crypto, m->friendlist[i].crypt_connection_id); + m->friendlist[i].crypt_connection_id = -1; + set_friend_status(m, i, FRIEND_CONFIRMED); + } + + if (m->friendlist[i].share_relays_lastsent + FRIEND_SHARE_RELAYS_INTERVAL < temp_time) { + send_relays(m, i); + } + } + } +} + + + + +#ifdef LOGGING +#define DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS 60UL +static time_t lastdump = 0; +static char IDString[CLIENT_ID_SIZE * 2 + 1]; +static char *ID2String(const uint8_t *client_id) +{ + uint32_t i; + + for (i = 0; i < CLIENT_ID_SIZE; i++) + sprintf(&IDString[i * 2], "%02X", client_id[i]); + + IDString[CLIENT_ID_SIZE * 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(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) +{ + unix_time_update(); + + networking_poll(m->net); + + do_DHT(m->dht); + do_net_crypto(m->net_crypto); + do_onion_client(m->onion_c); + do_friends(m); + do_allgroupchats(m); + LANdiscovery(m); + +#ifdef LOGGING + + if (unix_time() > lastdump + DUMPING_CLIENTS_FRIENDS_EVERY_N_SECONDS) { + +#ifdef ENABLE_ASSOC_DHT + Assoc_status(m->dht->assoc); +#endif + + if (m->numchats > 0) { + size_t c; + + for (c = 0; c < m->numchats; c++) { + if (m->chats[c]) + Assoc_status(m->chats[c]->assoc); + } + } + + + 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_INFO("C[%2u] %s:%u [%3u] %s", + client, ip_ntoa(&assoc->ip_port.ip), ntohs(assoc->ip_port.port), + last_pinged, ID2String(cptr->client_id)); + } + } + + + 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].client_id, m->dht->friends_list[dhtfriend].client_id)) { + 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_INFO("Friend num in DHT %u != friend num in msger %u\n", m->dht->num_friends, m->numfriends); + } + + uint32_t ping_lastrecv; + 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) { + ping_lastrecv = lastdump - msgfptr->ping_lastrecv; + + if (ping_lastrecv > 999) + ping_lastrecv = 999; + + LOGGER_INFO("F[%2u:%2u] <%s> %02i [%03u] %s", + dht2m[friend], friend, msgfptr->name, msgfptr->crypt_connection_id, + ping_lastrecv, ID2String(msgfptr->client_id)); + } else { + LOGGER_INFO("F[--:%2u] %s", friend, ID2String(dhtfptr->client_id)); + } + + 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_INFO("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->client_id)); + } + } + } + } + +#endif /* LOGGING */ +} + +/* 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 SAVED_FRIEND_REQUEST_SIZE 1024 +#define NUM_SAVED_TCP_RELAYS 8 +struct SAVED_FRIEND { + uint8_t status; + uint8_t client_id[CLIENT_ID_SIZE]; + 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 ping_lastrecv; +}; + +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.client_id, m->friendlist[i].client_id, CLIENT_ID_SIZE); + + 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 lastonline[sizeof(uint64_t)]; + memcpy(lastonline, &m->friendlist[i].ping_lastrecv, sizeof(uint64_t)); + host_to_net(lastonline, sizeof(uint64_t)); + memcpy(&temp.ping_lastrecv, lastonline, 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.client_id); + + 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 lastonline[sizeof(uint64_t)]; + memcpy(lastonline, &temp.ping_lastrecv, sizeof(uint64_t)); + net_to_host(lastonline, sizeof(uint64_t)); + memcpy(&m->friendlist[fnum].ping_lastrecv, lastonline, 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.client_id); + 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 * sizeof(Node_format) //TCP relays + ; +} + +static uint8_t *z_state_save_subheader(uint8_t *data, uint32_t len, uint16_t type) +{ + uint32_t *data32 = (uint32_t *)data; + data32[0] = len; + data32[1] = (MESSENGER_STATE_COOKIE_TYPE << 16) | type; + data += sizeof(uint32_t) * 2; + return data; +} + + +/* Save the messenger in data of size Messenger_size(). */ +void messenger_save(const Messenger *m, uint8_t *data) +{ + uint32_t len; + uint16_t type; + uint32_t *data32, size32 = sizeof(uint32_t); + + data32 = (uint32_t *)data; + data32[0] = 0; + data32[1] = MESSENGER_STATE_COOKIE_GLOBAL; + data += size32 * 2; + +#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 = DHT_size(m->dht); + type = MESSENGER_STATE_TYPE_DHT; + data = z_state_save_subheader(data, len, type); + DHT_save(m->dht, data); + 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; + + Node_format relays[NUM_SAVED_TCP_RELAYS]; + len = sizeof(relays); + type = MESSENGER_STATE_TYPE_TCP_RELAY; + data = z_state_save_subheader(data, len, type); + memset(relays, 0, len); + copy_connected_tcp_relays(m->net_crypto, relays, NUM_SAVED_TCP_RELAYS); + memcpy(data, relays, len); +} + +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_keys(m->net_crypto, &data[sizeof(uint32_t)]); +#ifdef ENABLE_ASSOC_DHT + + if (m->dht->assoc) + Assoc_self_client_id_changed(m->dht->assoc, m->net_crypto->self_public_key); + +#endif + } 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: { + Node_format relays[NUM_SAVED_TCP_RELAYS]; + + if (length != sizeof(relays)) { + return -1; + } + + memcpy(relays, data, length); + uint32_t i; + + for (i = 0; i < NUM_SAVED_TCP_RELAYS; ++i) { + add_tcp_relay(m->net_crypto, relays[i].ip_port, relays[i].client_id); + } + } + 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(data32)); + + 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; +} + +/* Return the number of online friends in the instance m. */ +uint32_t get_num_online_friends(const Messenger *m) +{ + return m->numonline_friends; +} + +/* 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, int32_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; +} + +/* Allocate and return a list of valid friend id's. List must be freed by the + * caller. + * + * retun 0 if success. + * return -1 if failure. + */ +int get_friendlist(const Messenger *m, int32_t **out_list, uint32_t *out_list_length) +{ + uint32_t i; + + *out_list_length = 0; + + if (m->numfriends == 0) { + *out_list = NULL; + return 0; + } + + *out_list = malloc(m->numfriends * sizeof(int32_t)); + + if (*out_list == NULL) { + return -1; + } + + for (i = 0; i < m->numfriends; i++) { + if (m->friendlist[i].status > 0) { + (*out_list)[i] = i; + (*out_list_length)++; + } + } + + return 0; +} + +/* 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(const Messenger *m) +{ + uint32_t ret = 0; + uint32_t i; + + for (i = 0; i < m->numchats; i++) { + if (m->chats[i]) { + 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(const Messenger *m, int *out_list, uint32_t list_size) +{ + if (!out_list) + return 0; + + if (m->numchats == 0) { + return 0; + } + + uint32_t i; + uint32_t ret = 0; + + for (i = 0; i < m->numchats; i++) { + if (ret >= list_size) { + break; /* Abandon ship */ + } + + if (m->chats[i]) { + out_list[ret] = i; + ret++; + } + } + + return ret; +} diff --git a/protocols/Tox/toxcore/toxcore/Messenger.h b/protocols/Tox/toxcore/toxcore/Messenger.h new file mode 100644 index 0000000000..ce0e5825e9 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/Messenger.h @@ -0,0 +1,806 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifndef MESSENGER_H +#define MESSENGER_H + +#include "net_crypto.h" +#include "DHT.h" +#include "friend_requests.h" +#include "LAN_discovery.h" +#include "group_chats.h" +#include "onion_client.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)) + +/* NOTE: Packet ids below 16 must never be used. */ +#define PACKET_ID_ALIVE 16 +#define PACKET_ID_SHARE_RELAYS 17 +#define PACKET_ID_NICKNAME 48 +#define PACKET_ID_STATUSMESSAGE 49 +#define PACKET_ID_USERSTATUS 50 +#define PACKET_ID_TYPING 51 +#define PACKET_ID_RECEIPT 63 +#define PACKET_ID_MESSAGE 64 +#define PACKET_ID_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 144 +#define PACKET_ID_JOIN_GROUPCHAT 145 +#define PACKET_ID_ACCEPT_GROUPCHAT 146 + +/* Max number of groups we can invite someone at the same time to. */ +#define MAX_INVITED_GROUPS 64 + +/* Max number of tcp relays sent to friends */ +#define MAX_SHARED_RELAYS 16 + +/* 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 + +/* 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_UNKNOWN = -5, + FAERR_BADCHECKSUM = -6, + FAERR_SETNEWNOSPAM = -7, + FAERR_NOMEM = -8 +}; + +/* Default start timeout in seconds between friend requests. */ +#define FRIENDREQUEST_TIMEOUT 5; + +/* Interval between the sending of ping packets. */ +#define FRIEND_PING_INTERVAL 5 + +/* Interval between the sending of tcp relay information */ +#define FRIEND_SHARE_RELAYS_INTERVAL (5 * 60) + +/* If no packets are received from friend in this time interval, kill the connection. */ +#define FRIEND_CONNECTION_TIMEOUT (FRIEND_PING_INTERVAL * 3) + + +/* USERSTATUS - + * Represents userstatuses someone can have. + */ + +typedef enum { + USERSTATUS_NONE, + USERSTATUS_AWAY, + USERSTATUS_BUSY, + USERSTATUS_INVALID +} +USERSTATUS; + +struct File_Transfers { + uint64_t size; + uint64_t transferred; + uint8_t status; /* 0 == no transfer, 1 = not accepted, 2 = paused by the other, 3 = transferring, 4 = broken, 5 = paused by us */ +}; +enum { + FILESTATUS_NONE, + FILESTATUS_NOT_ACCEPTED, + FILESTATUS_PAUSED_BY_OTHER, + FILESTATUS_TRANSFERRING, + FILESTATUS_BROKEN, + FILESTATUS_PAUSED_BY_US, + FILESTATUS_TEMPORARY +}; +/* This cannot be bigger than 256 */ +#define MAX_CONCURRENT_FILE_PIPES 256 + +enum { + FILECONTROL_ACCEPT, + FILECONTROL_PAUSE, + FILECONTROL_KILL, + FILECONTROL_FINISHED, + FILECONTROL_RESUME_BROKEN +}; + +typedef struct { + uint8_t client_id[CLIENT_ID_SIZE]; + uint32_t onion_friendnum; + int crypt_connection_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; + 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. + uint8_t receives_read_receipts; // shall we send read receipts to this person? + uint32_t friendrequest_nospam; // The nospam number used in the friend request. + uint64_t ping_lastrecv; + uint64_t ping_lastsent; + uint64_t share_relays_lastsent; + struct File_Transfers file_sending[MAX_CONCURRENT_FILE_PIPES]; + struct File_Transfers file_receiving[MAX_CONCURRENT_FILE_PIPES]; + int invited_groups[MAX_INVITED_GROUPS]; + uint16_t invited_groups_num; + + struct { + int (*function)(void *object, const uint8_t *data, uint32_t len); + void *object; + } lossy_packethandlers[PACKET_ID_LOSSY_RANGE_SIZE]; + + struct { + int (*function)(void *object, const uint8_t *data, uint32_t len); + void *object; + } lossless_packethandlers[PACKET_ID_LOSSLESS_RANGE_SIZE]; +} Friend; + + +typedef struct Messenger { + + Networking_Core *net; + Net_Crypto *net_crypto; + DHT *dht; + + Onion *onion; + Onion_Announce *onion_a; + Onion_Client *onion_c; + + 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; + + uint32_t numonline_friends; + + Group_Chat **chats; + uint32_t numchats; + + uint64_t last_LANdiscovery; + + void (*friend_message)(struct Messenger *m, int32_t, const uint8_t *, uint16_t, void *); + void *friend_message_userdata; + void (*friend_action)(struct Messenger *m, int32_t, const uint8_t *, uint16_t, void *); + void *friend_action_userdata; + void (*friend_namechange)(struct Messenger *m, int32_t, const uint8_t *, uint16_t, void *); + void *friend_namechange_userdata; + void (*friend_statusmessagechange)(struct Messenger *m, int32_t, const uint8_t *, uint16_t, void *); + void *friend_statusmessagechange_userdata; + void (*friend_userstatuschange)(struct Messenger *m, int32_t, uint8_t, void *); + void *friend_userstatuschange_userdata; + void (*friend_typingchange)(struct Messenger *m, int32_t, uint8_t, void *); + void *friend_typingchange_userdata; + void (*read_receipt)(struct Messenger *m, int32_t, uint32_t, void *); + void *read_receipt_userdata; + void (*friend_statuschange)(struct Messenger *m, int32_t, uint8_t, void *); + void *friend_statuschange_userdata; + void (*friend_connectionstatuschange)(struct Messenger *m, int32_t, uint8_t, void *); + void *friend_connectionstatuschange_userdata; + void (*friend_connectionstatuschange_internal)(struct Messenger *m, int32_t, uint8_t, void *); + void *friend_connectionstatuschange_internal_userdata; + + void (*group_invite)(struct Messenger *m, int32_t, const uint8_t *, void *); + void *group_invite_userdata; + void (*group_message)(struct Messenger *m, int, int, const uint8_t *, uint16_t, void *); + void *group_message_userdata; + void (*group_action)(struct Messenger *m, int, int, const uint8_t *, uint16_t, void *); + void *group_action_userdata; + void (*group_namelistchange)(struct Messenger *m, int, int, uint8_t, void *); + void *group_namelistchange_userdata; + + void (*file_sendrequest)(struct Messenger *m, int32_t, uint8_t, uint64_t, const uint8_t *, uint16_t, void *); + void *file_sendrequest_userdata; + void (*file_filecontrol)(struct Messenger *m, int32_t, uint8_t, uint8_t, uint8_t, const uint8_t *, uint16_t, void *); + void *file_filecontrol_userdata; + void (*file_filedata)(struct Messenger *m, int32_t, uint8_t, const uint8_t *, uint16_t length, void *); + void *file_filedata_userdata; + + void (*msi_packet)(struct Messenger *m, int32_t, const uint8_t *, uint16_t, void *); + void *msi_packet_userdata; + +} Messenger; + +/* Format: [client_id (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 -5 for unknown error. + * 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 -1 if failure. + */ +int32_t m_addfriend_norequest(Messenger *m, const uint8_t *client_id); + +/* 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 *client_id); + +/* Copies the public key associated to that friend id into client_id buffer. + * Make sure that client_id is of size CLIENT_ID_SIZE. + * + * return 0 if success + * return -1 if failure + */ +int getclient_id(const Messenger *m, int32_t friendnumber, uint8_t *client_id); + +/* 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 1 if friend is connected to us (Online). + * return 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 text chat message to an online friend. + * + * return the message id if packet was successfully put into the send queue. + * return 0 if it was not. + * + * You will want to retain the return value, it will be passed to your read_receipt callback + * if one is received. + * m_sendmessage_withid will send a message with the id of your choosing, + * however we can generate an id for you by calling plain m_sendmessage. + */ +uint32_t m_sendmessage(Messenger *m, int32_t friendnumber, const uint8_t *message, uint32_t length); +uint32_t m_sendmessage_withid(Messenger *m, int32_t friendnumber, uint32_t theid, const uint8_t *message, + uint32_t length); + +/* Send an action to an online friend. + * + * return the message id if packet was successfully put into the send queue. + * return 0 if it was not. + * + * You will want to retain the return value, it will be passed to your read_receipt callback + * if one is received. + * m_sendaction_withid will send an action message with the id of your choosing, + * however we can generate an id for you by calling plain m_sendaction. + */ +uint32_t m_sendaction(Messenger *m, int32_t friendnumber, const uint8_t *action, uint32_t length); +uint32_t m_sendaction_withid(const Messenger *m, int32_t friendnumber, uint32_t theid, const uint8_t *action, + uint32_t length); + +/* 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, uint32_t maxlen); + +/* 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. + * returns -1 on error. + */ +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. + */ +uint8_t m_get_istyping(const Messenger *m, int32_t friendnumber); + +/* Sets whether we send read receipts for friendnumber. + * This function is not lazy, and it will fail if yesno is not (0 or 1). + */ +void m_set_sends_receipts(Messenger *m, int32_t friendnumber, int yesno); + +/* Set the function that will be executed when a friend request is received. + * Function format is function(uint8_t * public_key, uint8_t * data, uint16_t length) + */ +void m_callback_friendrequest(Messenger *m, void (*function)(Messenger *m, const uint8_t *, const uint8_t *, uint16_t, + void *), void *userdata); + +/* Set the function that will be executed when a message from a friend is received. + * Function format is: function(int32_t friendnumber, uint8_t * message, uint32_t length) + */ +void m_callback_friendmessage(Messenger *m, void (*function)(Messenger *m, int32_t, const uint8_t *, uint16_t, void *), + void *userdata); + +/* Set the function that will be executed when an action from a friend is received. + * Function format is: function(int32_t friendnumber, uint8_t * action, uint32_t length) + */ +void m_callback_action(Messenger *m, void (*function)(Messenger *m, int32_t, const uint8_t *, uint16_t, void *), + void *userdata); + +/* Set the callback for name changes. + * Function(int32_t friendnumber, uint8_t *newname, uint16_t length) + * You are not responsible for freeing newname. + */ +void m_callback_namechange(Messenger *m, void (*function)(Messenger *m, int32_t, const uint8_t *, uint16_t, void *), + void *userdata); + +/* Set the callback for status message changes. + * Function(int32_t friendnumber, uint8_t *newstatus, uint16_t length) + * + * You are not responsible for freeing newstatus + */ +void m_callback_statusmessage(Messenger *m, void (*function)(Messenger *m, int32_t, const uint8_t *, uint16_t, void *), + void *userdata); + +/* Set the callback for status type changes. + * Function(int32_t friendnumber, USERSTATUS kind) + */ +void m_callback_userstatus(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, void *), void *userdata); + +/* Set the callback for typing changes. + * Function(int32_t friendnumber, uint8_t is_typing) + */ +void m_callback_typingchange(Messenger *m, void(*function)(Messenger *m, int32_t, uint8_t, void *), void *userdata); + +/* Set the callback for read receipts. + * Function(int32_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, int32_t, uint32_t, void *), void *userdata); + +/* Set the callback for connection status changes. + * function(int32_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, int32_t, uint8_t, 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, int32_t, uint8_t, void *), + void *userdata); + +/**********GROUP CHATS************/ + +/* Set the callback for group invites. + * + * Function(Messenger *m, int32_t friendnumber, uint8_t *group_public_key, void *userdata) + */ +void m_callback_group_invite(Messenger *m, void (*function)(Messenger *m, int32_t, const uint8_t *, void *), + void *userdata); + +/* Set the callback for group messages. + * + * Function(Tox *tox, int groupnumber, int friendgroupnumber, uint8_t * message, uint16_t length, void *userdata) + */ +void m_callback_group_message(Messenger *m, void (*function)(Messenger *m, int, int, const uint8_t *, uint16_t, void *), + void *userdata); + +/* Set the callback for group actions. + * + * Function(Tox *tox, int groupnumber, int friendgroupnumber, uint8_t * message, uint16_t length, void *userdata) + */ +void m_callback_group_action(Messenger *m, void (*function)(Messenger *m, int, int, const uint8_t *, uint16_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, void *userdata) + */ +void m_callback_group_namelistchange(Messenger *m, void (*function)(Messenger *m, 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 add_groupchat(Messenger *m); + +/* Delete a groupchat from the chats array. + * + * return 0 on success. + * return -1 if failure. + */ +int del_groupchat(Messenger *m, int 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 m_group_peername(const Messenger *m, int groupnumber, int peernumber, uint8_t *name); + +/* invite friendnumber to groupnumber + * return 0 on success + * return -1 on failure + */ +int invite_friend(Messenger *m, int32_t friendnumber, int groupnumber); + +/* Join a group (you need to have been invited first.) + * + * returns group number on success + * returns -1 on failure. + */ +int join_groupchat(Messenger *m, int32_t friendnumber, const uint8_t *friend_group_public_key); + +/* send a group message + * return 0 on success + * return -1 on failure + */ +int group_message_send(const Messenger *m, int groupnumber, const uint8_t *message, uint32_t length); + +/* send a group action + * return 0 on success + * return -1 on failure + */ +int group_action_send(const Messenger *m, int groupnumber, const uint8_t *action, uint32_t length); + +/* Return the number of peers in the group chat on success. + * return -1 on failure + */ +int group_number_peers(const Messenger *m, int 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 group_names(const Messenger *m, int groupnumber, uint8_t names[][MAX_NICK_BYTES], uint16_t lengths[], + uint16_t length); + +/****************FILE SENDING*****************/ + + +/* Set the callback for file send requests. + * + * Function(Tox *tox, int32_t friendnumber, uint8_t filenumber, uint64_t filesize, uint8_t *filename, uint16_t filename_length, void *userdata) + */ +void callback_file_sendrequest(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint64_t, const uint8_t *, + uint16_t, void *), void *userdata); + +/* Set the callback for file control requests. + * + * Function(Tox *tox, int32_t friendnumber, uint8_t send_receive, uint8_t filenumber, uint8_t control_type, uint8_t *data, uint16_t length, void *userdata) + * + */ +void callback_file_control(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, uint8_t, uint8_t, + const uint8_t *, uint16_t, void *), void *userdata); + +/* Set the callback for file data. + * + * Function(Tox *tox, int32_t friendnumber, uint8_t filenumber, uint8_t *data, uint16_t length, void *userdata) + * + */ +void callback_file_data(Messenger *m, void (*function)(Messenger *m, int32_t, uint8_t, const uint8_t *, uint16_t length, + void *), void *userdata); + +/* Send a file send request. + * Maximum filename length is 255 bytes. + * return 1 on success + * return 0 on failure + */ +int file_sendrequest(const Messenger *m, int32_t friendnumber, uint8_t filenumber, uint64_t filesize, + const uint8_t *filename, uint16_t filename_length); + +/* Send a file send request. + * Maximum filename length is 255 bytes. + * return file number on success + * return -1 on failure + */ +int new_filesender(const Messenger *m, int32_t friendnumber, uint64_t filesize, const uint8_t *filename, + uint16_t filename_length); + +/* Send a file control request. + * send_receive is 0 if we want the control packet to target a sending file, 1 if it targets a receiving file. + * + * return 1 on success + * return 0 on failure + */ +int file_control(const Messenger *m, int32_t friendnumber, uint8_t send_receive, uint8_t filenumber, uint8_t message_id, + const uint8_t *data, uint16_t length); + +/* Send file data. + * + * return 1 on success + * return 0 on failure + */ +int file_data(const Messenger *m, int32_t friendnumber, uint8_t filenumber, 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, int32_t friendnumber, uint8_t *data, uint16_t length, void *userdata) + */ +void m_callback_msi_packet(Messenger *m, void (*function)(Messenger *m, int32_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 custom lossy packets (RTP packets for example.) + * + * return -1 on failure. + * return 0 on success. + */ +int custom_lossy_packet_registerhandler(Messenger *m, int32_t friendnumber, uint8_t byte, + int (*packet_handler_callback)(void *object, const uint8_t *data, uint32_t len), void *object); + +/* High level function to send custom lossy packets. + * + * return -1 on failure. + * 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. + * + * byte must be in PACKET_ID_LOSSLESS_RANGE_START PACKET_ID_LOSSLESS_RANGE_SIZE range. + * + * return -1 on failure. + * return 0 on success. + */ +int custom_lossless_packet_registerhandler(Messenger *m, int32_t friendnumber, uint8_t byte, + int (*packet_handler_callback)(void *object, const uint8_t *data, uint32_t len), void *object); + +/* High level function to send custom lossless packets. + * + * return -1 on failure. + * return 0 on success. + */ +int send_custom_lossless_packet(const Messenger *m, int32_t friendnumber, const uint8_t *data, uint32_t length); + +/**********************************************/ +/* Run this at startup. + * return allocated instance of Messenger on success. + * return 0 if there are problems. + */ +Messenger *new_messenger(uint8_t ipv6enabled); + +/* 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(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); + +/* Return the number of online friends in the instance m. */ +uint32_t get_num_online_friends(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, int32_t *out_list, uint32_t list_size); + +/* Allocate and return a list of valid friend id's. List must be freed by the + * caller. + * + * retun 0 if success. + * return -1 if failure. + */ +int get_friendlist(const Messenger *m, int **out_list, uint32_t *out_list_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(const Messenger *m); + +/* 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(const Messenger *m, int *out_list, uint32_t list_size); + +#endif diff --git a/protocols/Tox/toxcore/toxcore/TCP_client.c b/protocols/Tox/toxcore/toxcore/TCP_client.c new file mode 100644 index 0000000000..ff92d21553 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/TCP_client.c @@ -0,0 +1,725 @@ +/* +* 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 <http://www.gnu.org/licenses/>. +* +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "TCP_client.h" + +#if !defined(_WIN32) && !defined(__WIN32__) && !defined (WIN32) +#include <sys/ioctl.h> +#endif + +#include "util.h" + +/* return 1 on success + * return 0 on failure + */ +static int connect_sock_to(sock_t sock, IP_Port 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 0 on success. + * return -1 on failure. + */ +static int generate_handshake(TCP_Client_Connection *TCP_conn, const uint8_t *self_public_key, + const uint8_t *self_secret_key) +{ + uint8_t plain[crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES]; + crypto_box_keypair(plain, TCP_conn->temp_secret_key); + encrypt_precompute(TCP_conn->public_key, self_secret_key, TCP_conn->shared_key); + random_nonce(TCP_conn->sent_nonce); + memcpy(plain + crypto_box_PUBLICKEYBYTES, TCP_conn->sent_nonce, crypto_box_NONCEBYTES); + memcpy(TCP_conn->last_packet, 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); + memset(TCP_conn->temp_secret_key, 0, 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 (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) +{ + if (networking_at_startup() != 0) { + return NULL; + } + + if (ip_port.ip.family != AF_INET && ip_port.ip.family != AF_INET6) + return NULL; + + sock_t sock = socket(ip_port.ip.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))) { + kill_sock(sock); + return NULL; + } + + TCP_Client_Connection *temp = calloc(sizeof(TCP_Client_Connection), 1); + + if (temp == NULL) { + kill_sock(sock); + return NULL; + } + + temp->status = TCP_CLIENT_CONNECTING; + temp->sock = sock; + memcpy(temp->public_key, public_key, crypto_box_PUBLICKEYBYTES); + temp->ip_port = ip_port; + + if (generate_handshake(temp, self_public_key, self_secret_key) == -1) { + kill_sock(sock); + free(temp); + return NULL; + } + + 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_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; + + kill_sock(TCP_connection->sock); + memset(TCP_connection, 0, sizeof(TCP_Client_Connection)); + free(TCP_connection); +} diff --git a/protocols/Tox/toxcore/toxcore/TCP_client.h b/protocols/Tox/toxcore/toxcore/TCP_client.h new file mode 100644 index 0000000000..9998d20cef --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/TCP_client.h @@ -0,0 +1,148 @@ +/* +* 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 <http://www.gnu.org/licenses/>. +* +*/ + + +#ifndef TCP_CLIENT_H +#define TCP_CLIENT_H + +#include "crypto_core.h" +#include "TCP_server.h" + +#define TCP_CONNECTION_TIMEOUT 10 + +enum { + TCP_CLIENT_NO_STATUS, + TCP_CLIENT_CONNECTING, + TCP_CLIENT_UNCONFIRMED, + TCP_CLIENT_CONFIRMED, + TCP_CLIENT_DISCONNECTED, +}; +typedef struct { + uint8_t status; + sock_t sock; + uint8_t public_key[crypto_box_PUBLICKEYBYTES]; /* public key of the server */ + IP_Port ip_port; /* The ip and port of the server */ + 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; + + void *net_crypto_pointer; + uint32_t net_crypto_location; + 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; +} 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); + +/* 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 diff --git a/protocols/Tox/toxcore/toxcore/TCP_server.c b/protocols/Tox/toxcore/toxcore/TCP_server.c new file mode 100644 index 0000000000..4817384926 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/TCP_server.c @@ -0,0 +1,1330 @@ +/* +* 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 <http://www.gnu.org/licenses/>. +* +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "TCP_server.h" + +#if !defined(_WIN32) && !defined(__WIN32__) && !defined (WIN32) +#include <sys/ioctl.h> +#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; + + memset(&TCP_server->accepted_connection_array[index], 0, sizeof(TCP_Secure_Connection)); + --TCP_server->num_accepted_connections; + + if (TCP_server->num_accepted_connections == 0) + realloc_connection(TCP_server, 0); + + return 0; +} + +/* 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) +{ +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) + unsigned long count = 0; + ioctlsocket(sock, FIONREAD, &count); +#else + int count = 0; + ioctl(sock, FIONREAD, &count); +#endif + + if ((unsigned int)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) +{ +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) + unsigned long count = 0; + ioctlsocket(sock, FIONREAD, &count); +#else + int count = 0; + ioctl(sock, FIONREAD, &count); +#endif + + 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; + +} + +/* 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 (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) +{ + kill_sock(con->sock); + memset(con, 0, 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 (memcmp(con->public_key, public_key, crypto_box_PUBLICKEYBYTES) == 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 (memcmp(public_key, con->connections[i].public_key, crypto_box_PUBLICKEYBYTES) == 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 + && memcmp(other_conn->connections[i].public_key, con->public_key, crypto_box_PUBLICKEYBYTES) == 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.ip.family = TCP_ONION_FAMILY; + source.ip.ip6.uint32[0] = con_id; + 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) + return -1; + + if (handle_TCP_packet(TCP_server, index, data, length) == -1) { + kill_accepted(TCP_server, index); + } + + return index; +} + +/* return 1 on success + * return 0 on failure + */ +static int accept_connection(TCP_Server *TCP_server, sock_t sock) +{ + if (!sock_valid(sock)) + return 0; + + if (!set_socket_nonblock(sock)) { + kill_sock(sock); + return 0; + } + + if (!set_socket_nosigpipe(sock)) { + kill_sock(sock); + return 0; + } + + TCP_Secure_Connection *conn = + &TCP_server->incomming_connection_queue[TCP_server->incomming_connection_queue_index % MAX_INCOMMING_CONNECTIONS]; + + 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 1; +} + +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; + } + +#ifndef TCP_SERVER_USE_EPOLL + int ok = set_socket_nonblock(sock); +#else + int ok = 1; +#endif + + if (ok && family == AF_INET6) { + ok = set_socket_dualstack(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 *public_key, + 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); + 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; + 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); + return NULL; + } + + if (onion) { + temp->onion = onion; + set_callback_handle_recv_1(onion, &handle_onion_recv_1, temp); + } + + memcpy(temp->public_key, public_key, crypto_box_PUBLICKEYBYTES); + memcpy(temp->secret_key, secret_key, crypto_box_SECRETKEYBYTES); + + 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)); + } +} + +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)); + memset(conn_old, 0, 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 { + int index_new; + + if ((index_new = confirm_TCP_connection(TCP_server, conn, packet, len)) == -1) { + kill_TCP_connection(conn); + } else { + memset(conn, 0, sizeof(TCP_Secure_Connection)); + } + + return index_new; + } +} + +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) & 0xFFFF, index = (events[n].data.u64 >> 48); + + if ((events[n].events & EPOLLERR) || (events[n].events & EPOLLHUP)) { + 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); + sock_t sock_new; + + sock_new = accept(sock, (struct sockaddr *)&addr, &addrlen); + + int index_new = TCP_server->incomming_connection_queue_index % MAX_INCOMMING_CONNECTIONS; + + if (!accept_connection(TCP_server, sock_new)) { + break; + } + + struct epoll_event ev = { + .events = EPOLLIN | EPOLLET, + .data.u64 = sock_new | ((uint64_t)TCP_SOCKET_INCOMING << 32) | ((uint64_t)index_new << 48) + }; + + if (epoll_ctl(TCP_server->efd, EPOLL_CTL_ADD, sock_new, &ev) == -1) { + kill_TCP_connection(&TCP_server->incomming_connection_queue[index_new]); + break; + } + + break; + } + + case TCP_SOCKET_INCOMING: { + int index_new; + + if ((index_new = do_incoming(TCP_server, index)) != -1) { + events[n].events = EPOLLIN | EPOLLET; + events[n].data.u64 = sock | ((uint64_t)TCP_SOCKET_UNCONFIRMED << 32) | ((uint64_t)index_new << 48); + + 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; + events[n].data.u64 = sock | ((uint64_t)TCP_SOCKET_CONFIRMED << 32) | ((uint64_t)index_new << 48); + + 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); +} diff --git a/protocols/Tox/toxcore/toxcore/TCP_server.h b/protocols/Tox/toxcore/toxcore/TCP_server.h new file mode 100644 index 0000000000..81507acbad --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/TCP_server.h @@ -0,0 +1,181 @@ +/* +* 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 <http://www.gnu.org/licenses/>. +* +*/ + +#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 32 + +#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[0]; +}; + +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 *public_key, + const uint8_t *secret_key, Onion *onion); + +/* Run the TCP_server + */ +void do_TCP_server(TCP_Server *TCP_server); + +/* Kill the TCP server + */ +void kill_TCP_server(TCP_Server *TCP_server); + +/* 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 diff --git a/protocols/Tox/toxcore/toxcore/assoc.c b/protocols/Tox/toxcore/toxcore/assoc.c new file mode 100644 index 0000000000..4d837aa318 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/assoc.c @@ -0,0 +1,1021 @@ + +#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 <assert.h> +#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[CLIENT_ID_SIZE]; /* 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 CLIENT_ID_SIZE 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 client_id 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.client_id; + + 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 < CLIENT_ID_SIZE; 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.client_id, 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.client_id, 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 || !assoc->self_client_id) + return; + + if (!assoc->self_hash) { + 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 i, 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.client_id, 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.client_id); + 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); + candidates_bucket *lists = malloc(sizeof(*lists) * assoc->candidates_bucket_count); + + 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 LOGGING +static char *idpart2str(uint8_t *id, size_t len); +#endif /* LOGGING */ + +/* 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.client_id; + + 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.client_id, 8), ip_ntoa(&ippts->ip_port.ip), htons(ippts->ip_port.port)); + + DHT_getnodes(dht, &ippts->ip_port, seen->client.client_id, 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.client_id, 8), ip_ntoa(&ipp->ip), htons(ipp->port)); + + DHT_getnodes(dht, ipp, heard->client.client_id, 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 LOGGING + +static char buffer[CLIENT_ID_SIZE * 2 + 1]; +static char *idpart2str(uint8_t *id, size_t len) +{ + if (len > CLIENT_ID_SIZE) + len = CLIENT_ID_SIZE; + + 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_INFO("Assoc status: no assoc"); + return; + } + + LOGGER_INFO("[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_INFO("[%3i:%3i] %08x => [%s...] %i, %i(%c), %i(%c)\n", + (int)bid, (int)cid, entry->hash, idpart2str(entry->client.client_id, 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_INFO("Total: %i entries, table usage %i%%.\n", (int)total, + (int)(total * 100 / (assoc->candidates_bucket_count * assoc->candidates_bucket_size))); + } +} + +#endif /* LOGGING */ diff --git a/protocols/Tox/toxcore/toxcore/assoc.h b/protocols/Tox/toxcore/toxcore/assoc.h new file mode 100644 index 0000000000..1b4e1ff9e8 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/assoc.h @@ -0,0 +1,104 @@ + +#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 LOGGING +void Assoc_status(const Assoc *assoc); +#endif /* LOGGING */ + +#endif /* !__ASSOC_H__ */ diff --git a/protocols/Tox/toxcore/toxcore/crypto_core.c b/protocols/Tox/toxcore/toxcore/crypto_core.c new file mode 100644 index 0000000000..1799b60060 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/crypto_core.c @@ -0,0 +1,278 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "crypto_core.h" + + +/* Use this instead of memcmp; not vulnerable to timing attacks. + returns 0 if both mem locations of length are equal, + return -1 if they are not. */ +int crypto_cmp(const uint8_t *mem1, const uint8_t *mem2, uint32_t length) +{ + if (length == 16) { + return crypto_verify_16(mem1, mem2); + } else if (length == 32) { + return crypto_verify_32(mem1, mem2); + } + + unsigned int i, check = 0; + + for (i = 0; i < length; ++i) { + check |= mem1[i] ^ mem2[i]; + } + + return (1 & ((check - 1) >> 8)) - 1; +} + +/* 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) + 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) + 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) +{ + uint8_t k[crypto_box_BEFORENMBYTES]; + encrypt_precompute(public_key, secret_key, k); + return encrypt_data_symmetric(k, nonce, plain, length, encrypted); +} + +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) +{ + uint8_t k[crypto_box_BEFORENMBYTES]; + encrypt_precompute(public_key, secret_key, k); + return decrypt_data_symmetric(k, nonce, encrypted, length, plain); +} + + +/* Increment the given nonce by 1. */ +void increment_nonce(uint8_t *nonce) +{ + uint32_t i; + + for (i = crypto_box_NONCEBYTES; i != 0; --i) { + ++nonce[i - 1]; + + if (nonce[i - 1] != 0) + break; + } +} +/* increment the given nonce by num */ +void increment_nonce_number(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)); +} + +/* 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); +} + +static uint8_t base_nonce[crypto_box_NONCEBYTES]; +static uint8_t nonce_set = 0; + +/* Gives a nonce guaranteed to be different from previous ones.*/ +void new_nonce(uint8_t *nonce) +{ + if (nonce_set == 0) { + random_nonce(base_nonce); + nonce_set = 1; + } + + increment_nonce(base_nonce); + memcpy(nonce, base_nonce, crypto_box_NONCEBYTES); +} + +/* 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 reciever. + * 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 (MAX_CRYPTO_REQUEST_SIZE < length + 1 + crypto_box_PUBLICKEYBYTES * 2 + crypto_box_NONCEBYTES + 1 + + crypto_box_MACBYTES) + return -1; + + uint8_t nonce[crypto_box_NONCEBYTES]; + uint8_t temp[MAX_CRYPTO_REQUEST_SIZE]; + memcpy(temp + 1, data, length); + temp[0] = request_id; + new_nonce(nonce); + 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); + memcpy(packet + 1 + crypto_box_PUBLICKEYBYTES * 2, nonce, crypto_box_NONCEBYTES); + + 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 (length > crypto_box_PUBLICKEYBYTES * 2 + crypto_box_NONCEBYTES + 1 + crypto_box_MACBYTES && + length <= MAX_CRYPTO_REQUEST_SIZE) { + if (memcmp(packet + 1, self_public_key, crypto_box_PUBLICKEYBYTES) == 0) { + memcpy(public_key, packet + 1 + crypto_box_PUBLICKEYBYTES, crypto_box_PUBLICKEYBYTES); + uint8_t nonce[crypto_box_NONCEBYTES]; + uint8_t temp[MAX_CRYPTO_REQUEST_SIZE]; + memcpy(nonce, packet + 1 + crypto_box_PUBLICKEYBYTES * 2, crypto_box_NONCEBYTES); + 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; + } + } + + return -1; +} diff --git a/protocols/Tox/toxcore/toxcore/crypto_core.h b/protocols/Tox/toxcore/toxcore/crypto_core.h new file mode 100644 index 0000000000..7362d49eca --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/crypto_core.h @@ -0,0 +1,151 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ +#ifndef CORE_CRYPTO_H +#define CORE_CRYPTO_H + +#include "network.h" + +#ifndef VANILLA_NACL +/* We use libsodium by default. */ +#include <sodium.h> +#else +#include <crypto_box.h> +#include <randombytes.h> +#include <crypto_hash_sha256.h> +#include <crypto_hash_sha512.h> +#include <crypto_verify_16.h> +#include <crypto_verify_32.h> +#define crypto_box_MACBYTES (crypto_box_ZEROBYTES - crypto_box_BOXZEROBYTES) +#endif + +#define crypto_box_KEYBYTES (crypto_box_BEFORENMBYTES) + +/* Use this instead of memcmp; not vulnerable to timing attacks. + returns 0 if both mem locations of length are equal, + return -1 if they are not. */ +int crypto_cmp(const uint8_t *mem1, const uint8_t *mem2, uint32_t length); + +/* 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 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_NAT_PING 254 /* NAT ping crypto packet ID. */ +#define CRYPTO_PACKET_GROUP_CHAT_GET_NODES 48 /* Group chat get Nodes packet */ +#define CRYPTO_PACKET_GROUP_CHAT_SEND_NODES 49 /* Group chat send Nodes packet */ +#define CRYPTO_PACKET_GROUP_CHAT_BROADCAST 50 /* Group chat broadcast packet */ + +/* 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 reciever. + * 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 diff --git a/protocols/Tox/toxcore/toxcore/friend_requests.c b/protocols/Tox/toxcore/toxcore/friend_requests.c new file mode 100644 index 0000000000..a662b629e8 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/friend_requests.c @@ -0,0 +1,175 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "friend_requests.h" +#include "util.h" + +/* Try to send a friend request to peer with public_key. + * data is the data in the request and length is the length. + * + * 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_friendrequest(const Onion_Client *onion_c, const uint8_t *public_key, uint32_t nospam_num, const uint8_t *data, + uint32_t length) +{ + if (1 + sizeof(nospam_num) + length > ONION_CLIENT_MAX_DATA_SIZE || length == 0) + return -1; + + uint8_t temp[1 + sizeof(nospam_num) + length]; + temp[0] = CRYPTO_PACKET_FRIEND_REQ; + memcpy(temp + 1, &nospam_num, sizeof(nospam_num)); + memcpy(temp + 1 + sizeof(nospam_num), data, length); + + int friend_num = onion_friend_num(onion_c, public_key); + + if (friend_num == -1) + return -1; + + int num = send_onion_data(onion_c, friend_num, temp, sizeof(temp)); + + if (num <= 0) + return -1; + + return num; +} + + +/* 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 *, uint16_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 *client_id) +{ + if (fr->received_requests_index >= MAX_RECEIVED_STORED) + fr->received_requests_index = 0; + + id_copy(fr->received_requests[fr->received_requests_index], client_id); + ++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 *client_id) +{ + uint32_t i; + + for (i = 0; i < MAX_RECEIVED_STORED; ++i) + if (id_equal(fr->received_requests[i], client_id)) + return 1; + + return 0; +} + +/* Remove client id 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 *client_id) +{ + uint32_t i; + + for (i = 0; i < MAX_RECEIVED_STORED; ++i) { + if (id_equal(fr->received_requests[i], client_id)) { + memset(fr->received_requests[i], 0, crypto_box_PUBLICKEYBYTES); + return 0; + } + } + + return -1; +} + + +static int friendreq_handlepacket(void *object, const uint8_t *source_pubkey, const uint8_t *packet, uint32_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, Onion_Client *onion_c) +{ + oniondata_registerhandler(onion_c, CRYPTO_PACKET_FRIEND_REQ, &friendreq_handlepacket, fr); +} diff --git a/protocols/Tox/toxcore/toxcore/friend_requests.h b/protocols/Tox/toxcore/toxcore/friend_requests.h new file mode 100644 index 0000000000..c3e31f3671 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/friend_requests.h @@ -0,0 +1,83 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifndef FRIEND_REQUESTS_H +#define FRIEND_REQUESTS_H + +#include "onion_client.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 *, uint16_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; + +/* Try to send a friendrequest to peer with public_key. + * data is the data in the request and length is the length. + * Maximum length of data is MAX_FRIEND_REQUEST_DATA_SIZE. + */ +int send_friendrequest(const Onion_Client *onion_c, const uint8_t *public_key, uint32_t nospam_num, const uint8_t *data, + uint32_t length); +/* 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 client id 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 *client_id); + +/* 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, uint16_t length, void * userdata) + */ +void callback_friendrequest(Friend_Requests *fr, void (*function)(void *, const uint8_t *, const uint8_t *, uint16_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, Onion_Client *onion_c); + + +#endif diff --git a/protocols/Tox/toxcore/toxcore/group_chats.c b/protocols/Tox/toxcore/toxcore/group_chats.c new file mode 100644 index 0000000000..77fa6acddd --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/group_chats.c @@ -0,0 +1,837 @@ +/* group_chats.c + * + * An implementation of massive text only group chats. + * + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "DHT.h" +#include "assoc.h" +#include "group_chats.h" +#include "LAN_discovery.h" +#include "util.h" + +#define GROUPCHAT_MAXDATA_LENGTH (MAX_CRYPTO_REQUEST_SIZE - (1 + crypto_box_PUBLICKEYBYTES * 2 + crypto_box_NONCEBYTES)) +#define GROUPCHAT_MAXPLAINDATA_LENGTH (GROUPCHAT_MAXDATA_LENGTH - crypto_box_MACBYTES) + +#define GROUP_MAX_SENDNODES (GROUP_CLOSE_CONNECTIONS * 2) + +typedef struct { + uint64_t pingid; + //uint8_t client_id[crypto_box_PUBLICKEYBYTES]; + +} getnodes_data; + +typedef struct { + uint8_t client_id[crypto_box_PUBLICKEYBYTES]; + IP_Port ip_port; + +} groupchat_nodes; + +typedef struct { + uint64_t pingid; + groupchat_nodes nodes[GROUP_CLOSE_CONNECTIONS]; + //uint8_t client_id[crypto_box_PUBLICKEYBYTES]; + +} sendnodes_data; + +/* + * check if peer with client_id 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 peer_in_chat(const Group_Chat *chat, const uint8_t *client_id) +{ + uint32_t i; + + for (i = 0; i < chat->numpeers; ++i) + if (id_equal(chat->group[i].client_id, client_id)) + return i; + + return -1; +} + +/* Compares client_id1 and client_id2 with client_id. + * + * return 0 if both are same distance. + * return 1 if client_id1 is closer. + * return 2 if client_id2 is closer. + */ +static int id_closest_groupchats(const uint8_t *id, const uint8_t *id1, const uint8_t *id2) +{ + size_t i; + uint8_t distance1, distance2; + + for (i = 0; i < CLIENT_ID_SIZE; ++i) { + + distance1 = abs(((int8_t *)id)[i] - ((int8_t *)id1)[i]); + distance2 = abs(((int8_t *)id)[i] - ((int8_t *)id2)[i]); + + if (distance1 < distance2) + return 1; + + if (distance1 > distance2) + return 2; + } + + return 0; +} + +#define BAD_GROUPNODE_TIMEOUT 30 + +/* + * Check if peer is closer to us that the other peers in the list and if the peer is in the list. + * Return the number of peers it is closer to if it is not in the closelist. + * Return -1 if the peer is in the closelist. + */ + +static int peer_okping(const Group_Chat *chat, const uint8_t *client_id) +{ + uint32_t i, j = 0; + + if (id_equal(chat->self_public_key, client_id)) + return -1; + + for (i = 0; i < GROUP_CLOSE_CONNECTIONS; ++i) { + if (is_timeout(chat->close[i].last_recv, BAD_GROUPNODE_TIMEOUT)) { + ++j; + continue; + } + + /* Equal */ + if (id_equal(chat->close[i].client_id, client_id)) + return -1; + + if (id_closest_groupchats(chat->self_public_key, chat->close[i].client_id, client_id) == 2) + ++j; + } + + return j; +} + + + +/* Attempt to add a peer to the close list. + * Update last_recv if it is in list. + * Attempt to add it to list if it is not. + * + * Return 0 if success. + * Return -1 if peer was not put in list/updated. + */ +static int add_closepeer(Group_Chat *chat, const uint8_t *client_id, IP_Port ip_port) +{ + uint32_t i; + + for (i = 0; i < GROUP_CLOSE_CONNECTIONS; ++i) { /* Check if node is already in list, if it is update its last_recv */ + if (id_equal(chat->close[i].client_id, client_id)) { + chat->close[i].last_recv = unix_time(); + return 0; + } + } + + for (i = 0; i < GROUP_CLOSE_CONNECTIONS; ++i) { /* Try replacing bad nodes first */ + if (is_timeout(chat->close[i].last_recv, BAD_GROUPNODE_TIMEOUT)) { + id_copy(chat->close[i].client_id, client_id); + chat->close[i].ip_port = ip_port; + chat->close[i].last_recv = unix_time(); + return 0; + } + } + + for (i = 0; i < GROUP_CLOSE_CONNECTIONS; ++i) { /* Replace nodes if given one is closer. */ + if (id_closest_groupchats(chat->self_public_key, chat->close[i].client_id, client_id) == 2) { + id_copy(chat->close[i].client_id, client_id); + chat->close[i].ip_port = ip_port; + chat->close[i].last_recv = unix_time(); + return 0; + } + } + + return -1; +} + +static int send_groupchatpacket(const Group_Chat *chat, IP_Port ip_port, const uint8_t *public_key, const uint8_t *data, + uint32_t length, uint8_t request_id) +{ + if (id_equal(chat->self_public_key, public_key)) + return -1; + + uint8_t packet[MAX_CRYPTO_REQUEST_SIZE]; + int len = create_request(chat->self_public_key, chat->self_secret_key, packet, public_key, data, length, request_id); + packet[0] = NET_PACKET_GROUP_CHATS; + + if (len == -1) + return -1; + + if (sendpacket(chat->net, ip_port, packet, len) == len) + return 0; + + return -1; + +} + +/* + * Send data to all peers in close peer list. + * + * return the number of peers the packet was sent to. + */ +static uint8_t sendto_allpeers(const Group_Chat *chat, const uint8_t *data, uint16_t length, uint8_t request_id) +{ + uint16_t sent = 0; + uint32_t i; + + for (i = 0; i < GROUP_CLOSE_CONNECTIONS; ++i) { + if (ip_isset(&chat->close[i].ip_port.ip) && + !is_timeout(chat->close[i].last_recv, BAD_GROUPNODE_TIMEOUT)) { + if (send_groupchatpacket(chat, chat->close[i].ip_port, chat->close[i].client_id, + data, length, request_id) == 0) + ++sent; + } + } + + return sent; +} + + +/* + * Add a peer to the group chat. + * + * return peernum if success or peer already in chat. + * return -1 if error. + */ +static int addpeer(Group_Chat *chat, const uint8_t *client_id) +{ + int peernum = peer_in_chat(chat, client_id); + + if (peernum != -1) + return peernum; + + Group_Peer *temp; + temp = realloc(chat->group, sizeof(Group_Peer) * (chat->numpeers + 1)); + + if (temp == NULL) + return -1; + + memset(&(temp[chat->numpeers]), 0, sizeof(Group_Peer)); + chat->group = temp; + + id_copy(chat->group[chat->numpeers].client_id, client_id); + chat->group[chat->numpeers].last_recv = unix_time(); + chat->group[chat->numpeers].last_recv_msgping = unix_time(); + ++chat->numpeers; + + if (chat->peer_namelistchange != NULL) + (*chat->peer_namelistchange)(chat, chat->numpeers - 1, CHAT_CHANGE_PEER_ADD, chat->group_namelistchange_userdata); + + return (chat->numpeers - 1); +} + +/* + * Set a peer from the group chat to deleted. + * + * return 0 if success + * return -1 if error. + */ +static int del_peer_set(Group_Chat *chat, int peernum) +{ + if ((uint32_t)peernum >= chat->numpeers) + return -1; + + chat->group[peernum].deleted = 1; + chat->group[peernum].deleted_time = unix_time(); + return 0; +} + +/* + * Delete a peer from the group chat. + * + * return 0 if success + * return -1 if error. + */ +static int delpeer(Group_Chat *chat, int peernum) +{ + if ((uint32_t)peernum >= chat->numpeers) + return -1; + + uint32_t i; + + for (i = 0; i < GROUP_CLOSE_CONNECTIONS; ++i) { /* If peer is in close list, time it out forcefully. */ + if (id_equal(chat->close[i].client_id, chat->group[peernum].client_id)) { + chat->close[i].last_recv = 0; + break; + } + } + + Group_Peer *temp; + --chat->numpeers; + + if (chat->numpeers == 0) { + free(chat->group); + chat->group = NULL; + return 0; + } + + if (chat->numpeers != (uint32_t)peernum) + memcpy(&chat->group[peernum], &chat->group[chat->numpeers], sizeof(Group_Peer)); + + temp = realloc(chat->group, sizeof(Group_Peer) * (chat->numpeers)); + + if (temp == NULL) + return -1; + + chat->group = temp; + + if (chat->peer_namelistchange != NULL) { + (*chat->peer_namelistchange)(chat, peernum, CHAT_CHANGE_PEER_DEL, chat->group_namelistchange_userdata); + } + + return 0; +} + +/* Copy the name of peernum to name. + * name must be at least MAX_NICK_BYTES long. + * + * return length of name if success + * return -1 if failure + */ +int group_peername(const Group_Chat *chat, int peernum, uint8_t *name) +{ + if ((uint32_t)peernum >= chat->numpeers) + return -1; + + if (chat->group[peernum].nick_len == 0) { + /* memcpy(name, "NSA agent", 10); */ /* Srsly? */ /* Kindly remind the user that someone with no name might be a moronic NSA agent.*/ + name[0] = 0; + return 0; + } + + memcpy(name, chat->group[peernum].nick, chat->group[peernum].nick_len); + return chat->group[peernum].nick_len; +} + +static void setnick(Group_Chat *chat, int peernum, const uint8_t *contents, uint16_t contents_len) +{ + if (contents_len > MAX_NICK_BYTES || contents_len == 0) + return; + + /* same name as already stored? */ + if (chat->group[peernum].nick_len == contents_len) + if (!memcmp(chat->group[peernum].nick, contents, contents_len)) + return; + + memcpy(chat->group[peernum].nick, contents, contents_len); + chat->group[peernum].nick_len = contents_len; + + if (chat->peer_namelistchange != NULL) + (*chat->peer_namelistchange)(chat, peernum, CHAT_CHANGE_PEER_NAME, chat->group_namelistchange_userdata); +} + +/* min time between pings sent to one peer in seconds */ +/* TODO: move this to global section */ +#define GROUP_PING_TIMEOUT 5 + +static int send_getnodes(const Group_Chat *chat, IP_Port ip_port, int peernum) +{ + if ((uint32_t)peernum >= chat->numpeers) + return -1; + + if (!is_timeout(chat->group[peernum].last_pinged, GROUP_PING_TIMEOUT)) + return -1; + + getnodes_data contents; + contents.pingid = random_64b(); + + chat->group[peernum].last_pinged = unix_time(); + chat->group[peernum].pingid = contents.pingid; + chat->group[peernum].ping_via = ip_port; + + if (chat->assoc) { + IPPTs ippts; + ippts.timestamp = unix_time(); + ippts.ip_port = ip_port; + + Assoc_add_entry(chat->assoc, chat->group[peernum].client_id, &ippts, NULL, 1); + } + + return send_groupchatpacket(chat, ip_port, chat->group[peernum].client_id, (uint8_t *)&contents, sizeof(contents), + CRYPTO_PACKET_GROUP_CHAT_GET_NODES); +} + +static int send_sendnodes(const Group_Chat *chat, IP_Port ip_port, int peernum, uint64_t pingid) +{ + if ((uint32_t)peernum >= chat->numpeers) + return -1; + + sendnodes_data contents; + contents.pingid = pingid; + uint32_t i, j = 0; + + for (i = 0; i < GROUP_CLOSE_CONNECTIONS; ++i) { + if (!is_timeout(chat->close[i].last_recv, BAD_GROUPNODE_TIMEOUT)) { + id_copy(contents.nodes[j].client_id, chat->close[i].client_id); + contents.nodes[j].ip_port = chat->close[i].ip_port; + to_net_family(&contents.nodes[j].ip_port.ip); + ++j; + } + } + + return send_groupchatpacket(chat, ip_port, chat->group[peernum].client_id, (uint8_t *)&contents, + sizeof(contents.pingid) + sizeof(groupchat_nodes) * j, CRYPTO_PACKET_GROUP_CHAT_SEND_NODES); +} + +static int handle_getnodes(const Group_Chat *chat, IP_Port source, int peernum, const uint8_t *data, uint32_t len) +{ + if (len != sizeof(getnodes_data)) + return 1; + + if ((uint32_t)peernum >= chat->numpeers) + return 1; + + getnodes_data contents; + memcpy(&contents, data, sizeof(contents)); + send_sendnodes(chat, source, peernum, contents.pingid); + + if (peer_okping(chat, chat->group[peernum].client_id) > 0) + send_getnodes(chat, source, peernum); + + return 0; +} + +static int handle_sendnodes(Group_Chat *chat, IP_Port source, int peernum, const uint8_t *data, uint32_t len) +{ + if ((uint32_t)peernum >= chat->numpeers) + return 1; + + if (len > sizeof(sendnodes_data) || len < sizeof(uint64_t)) + return 1; + + if ((len - sizeof(uint64_t)) % sizeof(groupchat_nodes) != 0) + return 1; + + if (is_timeout(chat->group[peernum].last_pinged, GROUP_PING_TIMEOUT)) + return 1; + + sendnodes_data contents; + memcpy(&contents, data, len); + + if (contents.pingid != chat->group[peernum].pingid) + return 1; + + uint16_t numnodes = (len - sizeof(contents.pingid)) / sizeof(groupchat_nodes); + uint32_t i; + + IPPTs ippts_send; + ippts_send.timestamp = unix_time(); + + for (i = 0; i < numnodes; ++i) { + if (peer_okping(chat, contents.nodes[i].client_id) > 0) { + int peern = peer_in_chat(chat, contents.nodes[i].client_id); + + if (peern == -1) { /*NOTE: This is just for testing and will be removed later.*/ + peern = addpeer(chat, contents.nodes[i].client_id); + } + + if (peern == -1) + continue; + + to_host_family(&contents.nodes[i].ip_port.ip); + send_getnodes(chat, contents.nodes[i].ip_port, peern); + + if (chat->assoc) { + ippts_send.ip_port = contents.nodes[i].ip_port; + Assoc_add_entry(chat->assoc, contents.nodes[i].client_id, &ippts_send, NULL, 0); + } + } + } + + int ok = add_closepeer(chat, chat->group[peernum].client_id, source); + + return 0; +} + +#define GROUP_DATA_MIN_SIZE (crypto_box_PUBLICKEYBYTES + sizeof(uint32_t) + 1) +static void send_names_new_peer(Group_Chat *chat); + +static int handle_data(Group_Chat *chat, const uint8_t *data, uint32_t len) +{ + if (len < GROUP_DATA_MIN_SIZE) + return 1; + +//TODO: + int peernum = peer_in_chat(chat, data); + + if (peernum == -1) { /*NOTE: This is just for testing and will be removed later.*/ + if (data[crypto_box_PUBLICKEYBYTES + sizeof(uint32_t)] != GROUP_CHAT_QUIT) + peernum = addpeer(chat, data); + } + + if (peernum == -1) + return 1; + + if (chat->group[peernum].deleted) + return 1; + + /* Spam prevention (1 message per peer per second limit.) + + if (chat->group[peernum].last_recv == temp_time) + return 1; + */ + chat->group[peernum].last_recv = unix_time(); + + uint32_t message_num; + memcpy(&message_num, data + crypto_box_PUBLICKEYBYTES, sizeof(uint32_t)); + message_num = ntohl(message_num); + + if (chat->group[peernum].last_message_number == 0) { + chat->group[peernum].last_message_number = message_num; + } else if (message_num - chat->group[peernum].last_message_number > 64 || + message_num == chat->group[peernum].last_message_number) + return 1; + + chat->group[peernum].last_message_number = message_num; + + int handled = 1; + const uint8_t *contents = data + GROUP_DATA_MIN_SIZE; + uint16_t contents_len = len - GROUP_DATA_MIN_SIZE; + + switch (data[crypto_box_PUBLICKEYBYTES + sizeof(message_num)]) { + case GROUP_CHAT_PING: /* If message is ping */ + if (contents_len != 0) + return 1; + + chat->group[peernum].last_recv_msgping = unix_time(); + break; + + case GROUP_CHAT_NEW_PEER: /* If message is new peer */ + if (contents_len != crypto_box_PUBLICKEYBYTES) + return 1; + + addpeer(chat, contents); + send_names_new_peer(chat); + break; + + case GROUP_CHAT_QUIT: /* If peer tells us he is quitting */ + if (contents_len != 0) + return 1; + + del_peer_set(chat, peernum); + break; + + case GROUP_CHAT_PEER_NICK: + if (contents_len > MAX_NICK_BYTES || contents_len == 0) + return 1; + + setnick(chat, peernum, contents, contents_len); + break; + + case GROUP_CHAT_CHAT_MESSAGE: /* If message is chat message */ + if (chat->group_message != NULL) + (*chat->group_message)(chat, peernum, contents, contents_len, chat->group_message_userdata); + + break; + + case GROUP_CHAT_ACTION: /* if message is a peer action */ + if (chat->group_action != NULL) + (*chat->group_action)(chat, peernum, contents, contents_len, chat->group_action_userdata); + + break; + + default: + handled = 0; + break; + + } + + if (handled == 1) { + sendto_allpeers(chat, data, len, CRYPTO_PACKET_GROUP_CHAT_BROADCAST); + return 0; + } + + return 1; +} + +static uint8_t send_data(Group_Chat *chat, const uint8_t *data, uint32_t len, uint8_t message_id) +{ + if (len + GROUP_DATA_MIN_SIZE > MAX_CRYPTO_REQUEST_SIZE) /*NOTE: not the real maximum len.*/ + return 1; + + uint8_t packet[MAX_CRYPTO_REQUEST_SIZE]; + ++chat->message_number; + + if (chat->message_number == 0) + chat->message_number = 1; + + uint32_t message_num = htonl(chat->message_number); +//TODO + id_copy(packet, chat->self_public_key); + memcpy(packet + crypto_box_PUBLICKEYBYTES, &message_num, sizeof(message_num)); + + if (len != 0) + memcpy(packet + GROUP_DATA_MIN_SIZE, data, len); + + packet[crypto_box_PUBLICKEYBYTES + sizeof(message_num)] = message_id; + return sendto_allpeers(chat, packet, len + GROUP_DATA_MIN_SIZE, CRYPTO_PACKET_GROUP_CHAT_BROADCAST); +} +/* + * Handle get nodes group packet. + * + * return 0 if handled correctly. + * return 1 if error. + */ + +int handle_groupchatpacket(Group_Chat *chat, IP_Port source, const uint8_t *packet, uint32_t length) +{ + if (length > MAX_CRYPTO_REQUEST_SIZE) + return 1; + + uint8_t public_key[crypto_box_PUBLICKEYBYTES]; + uint8_t data[MAX_CRYPTO_REQUEST_SIZE]; + uint8_t number; + int len = handle_request(chat->self_public_key, chat->self_secret_key, public_key, data, &number, packet, length); + + if (len <= 0) + return 1; + + if (id_equal(chat->self_public_key, public_key)) + return 1; + + int peernum = peer_in_chat(chat, public_key); + + if (peernum == -1) + return 1; + + switch (number) { + case CRYPTO_PACKET_GROUP_CHAT_GET_NODES: + return handle_getnodes(chat, source, peernum, data, len); + + case CRYPTO_PACKET_GROUP_CHAT_SEND_NODES: + return handle_sendnodes(chat, source, peernum, data, len); + + case CRYPTO_PACKET_GROUP_CHAT_BROADCAST: + return handle_data(chat, data, len); + + default: + return 1; + } + + return 1; +} + +uint32_t group_sendmessage(Group_Chat *chat, const uint8_t *message, uint32_t length) +{ + return send_data(chat, message, length, GROUP_CHAT_CHAT_MESSAGE); //TODO: better return values? +} + +uint32_t group_sendaction(Group_Chat *chat, const uint8_t *action, uint32_t length) +{ + return send_data(chat, action, length, GROUP_CHAT_ACTION); +} + +/* + * Send id/nick combo to the group. + * + * returns the number of peers it has sent it to. + */ +static uint32_t group_send_nick(Group_Chat *chat, uint8_t *nick, uint16_t nick_len) +{ + if (nick_len > MAX_NICK_BYTES) + return 0; + + return send_data(chat, nick, nick_len, GROUP_CHAT_PEER_NICK); +} + +int set_nick(Group_Chat *chat, const uint8_t *nick, uint16_t nick_len) +{ + if (nick_len > MAX_NICK_BYTES || nick_len == 0) + return -1; + + memcpy(chat->nick, nick, nick_len); + chat->nick_len = nick_len; + group_send_nick(chat, chat->nick, chat->nick_len); + return 0; +} + +uint32_t group_newpeer(Group_Chat *chat, const uint8_t *client_id) +{ + addpeer(chat, client_id); + return send_data(chat, client_id, crypto_box_PUBLICKEYBYTES, GROUP_CHAT_NEW_PEER); //TODO: better return values? +} + +void callback_groupmessage(Group_Chat *chat, void (*function)(Group_Chat *chat, int, const uint8_t *, uint16_t, void *), + void *userdata) +{ + chat->group_message = function; + chat->group_message_userdata = userdata; +} + +void callback_groupaction(Group_Chat *chat, void (*function)(Group_Chat *chat, int, const uint8_t *, uint16_t, void *), + void *userdata) +{ + chat->group_action = function; + chat->group_action_userdata = userdata; +} + +void callback_namelistchange(Group_Chat *chat, void (*function)(Group_Chat *chat, int peer, uint8_t change, void *), + void *userdata) +{ + chat->peer_namelistchange = function; + chat->group_namelistchange_userdata = userdata; +} + +uint32_t group_numpeers(const Group_Chat *chat) +{ + return chat->numpeers; +} + +uint32_t group_client_names(const Group_Chat *chat, uint8_t names[][MAX_NICK_BYTES], uint16_t lengths[], + uint16_t length) +{ + uint32_t i; + + for (i = 0; i < chat->numpeers && i < length; ++i) { + lengths[i] = group_peername(chat, i, names[i]); + } + + return i; +} + +Group_Chat *new_groupchat(Networking_Core *net) +{ + unix_time_update(); + + if (net == 0) + return 0; + + Group_Chat *chat = calloc(1, sizeof(Group_Chat)); + chat->net = net; + crypto_box_keypair(chat->self_public_key, chat->self_secret_key); + + /* (2^4) * 5 = 80 entries seems to be a moderate size */ + chat->assoc = new_Assoc(4, 5, chat->self_public_key); + + return chat; +} + +#define NODE_PING_INTERVAL 10 + +static void ping_close(Group_Chat *chat) +{ + uint32_t i; + + for (i = 0; i < GROUP_CLOSE_CONNECTIONS; ++i) { + if (!is_timeout(chat->close[i].last_recv, BAD_GROUPNODE_TIMEOUT)) { + int peernum = peer_in_chat(chat, chat->close[i].client_id); + + if (peernum == -1) + continue; + + if (is_timeout(chat->group[peernum].last_pinged, NODE_PING_INTERVAL)) + send_getnodes(chat, chat->close[i].ip_port, peernum); + } + } +} + +/* Interval in seconds to send ping messages */ +#define GROUP_PING_INTERVAL 30 + +static void ping_group(Group_Chat *chat) +{ + if (is_timeout(chat->last_sent_ping, GROUP_PING_INTERVAL)) { + if (send_data(chat, 0, 0, GROUP_CHAT_PING) != 0) /* Ping */ + chat->last_sent_ping = unix_time(); + } +} + +#define DEL_PEER_DELAY 3 +static void del_dead_peers(Group_Chat *chat) +{ + uint32_t i; + + for (i = 0; i < chat->numpeers; ++i) { + if (is_timeout(chat->group[i].last_recv_msgping, GROUP_PING_INTERVAL * 4)) { + delpeer(chat, i); + } + + if (chat->group == NULL || i >= chat->numpeers) + break; + + if (chat->group[i].deleted) { + if (is_timeout(chat->group[i].deleted_time, DEL_PEER_DELAY)) + delpeer(chat, i); + } + } +} + +#define NICK_SEND_INTERVAL 180 +static void send_names_new_peer(Group_Chat *chat) +{ + group_send_nick(chat, chat->nick, chat->nick_len); + chat->last_sent_nick = (unix_time() - NICK_SEND_INTERVAL) + 15; +} +static void send_names(Group_Chat *chat) +{ + /* send own nick from time to time, to let newly added peers be informed + * first time only: use a shorter timeframe, because we might not be in our own + * peer list yet */ + if (is_timeout(chat->last_sent_nick, 180)) + if (group_send_nick(chat, chat->nick, chat->nick_len) > 0) { + if (!chat->last_sent_nick) + chat->last_sent_nick = (unix_time() - NICK_SEND_INTERVAL) + 10; + else + chat->last_sent_nick = unix_time(); + } +} + +void do_groupchat(Group_Chat *chat) +{ + unix_time_update(); + ping_close(chat); + ping_group(chat); + /* TODO: Maybe run this less? */ + del_dead_peers(chat); + send_names(chat); +} + +void kill_groupchat(Group_Chat *chat) +{ + send_data(chat, 0, 0, GROUP_CHAT_QUIT); + kill_Assoc(chat->assoc); + free(chat->group); + free(chat); +} + +void chat_bootstrap(Group_Chat *chat, IP_Port ip_port, const uint8_t *client_id) +{ + send_getnodes(chat, ip_port, addpeer(chat, client_id)); +} + +void chat_bootstrap_nonlazy(Group_Chat *chat, IP_Port ip_port, const uint8_t *client_id) +{ + send_getnodes(chat, ip_port, addpeer(chat, client_id)); + add_closepeer(chat, client_id, ip_port); +} diff --git a/protocols/Tox/toxcore/toxcore/group_chats.h b/protocols/Tox/toxcore/toxcore/group_chats.h new file mode 100644 index 0000000000..1a7a2e047c --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/group_chats.h @@ -0,0 +1,199 @@ +/* group_chats.h + * + * An implementation of massive text only group chats. + * + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifndef GROUP_CHATS_H +#define GROUP_CHATS_H + +#define MAX_NICK_BYTES 128 + +typedef struct { + uint8_t client_id[crypto_box_PUBLICKEYBYTES]; + uint64_t pingid; + uint64_t last_pinged; + IP_Port ping_via; + + uint64_t last_recv; + uint64_t last_recv_msgping; + uint32_t last_message_number; + + uint8_t nick[MAX_NICK_BYTES]; + uint16_t nick_len; + + uint8_t deleted; + uint64_t deleted_time; +} Group_Peer; + +typedef struct { + uint8_t client_id[crypto_box_PUBLICKEYBYTES]; + IP_Port ip_port; + uint64_t last_recv; +} Group_Close; + +#define GROUP_CLOSE_CONNECTIONS 6 + +typedef struct Group_Chat { + Networking_Core *net; + uint8_t self_public_key[crypto_box_PUBLICKEYBYTES]; + uint8_t self_secret_key[crypto_box_SECRETKEYBYTES]; + + Group_Peer *group; + Group_Close close[GROUP_CLOSE_CONNECTIONS]; + uint32_t numpeers; + + uint32_t message_number; + void (*group_message)(struct Group_Chat *m, int, const uint8_t *, uint16_t, void *); + void *group_message_userdata; + void (*group_action)(struct Group_Chat *m, int, const uint8_t *, uint16_t, void *); + void *group_action_userdata; + void (*peer_namelistchange)(struct Group_Chat *m, int peer, uint8_t change, void *); + void *group_namelistchange_userdata; + + uint64_t last_sent_ping; + + uint8_t nick[MAX_NICK_BYTES]; + uint16_t nick_len; + uint64_t last_sent_nick; + + struct Assoc *assoc; +} Group_Chat; + +#define GROUP_CHAT_PING 0 +#define GROUP_CHAT_NEW_PEER 16 +#define GROUP_CHAT_QUIT 24 +#define GROUP_CHAT_PEER_NICK 48 +#define GROUP_CHAT_CHAT_MESSAGE 64 +#define GROUP_CHAT_ACTION 63 + +/* Copy the name of peernum to name. + * name must be at least MAX_NICK_BYTES long. + * + * return length of name if success + * return -1 if failure + */ +int group_peername(const Group_Chat *chat, int peernum, uint8_t *name); + +/* + * Set callback function for chat messages. + * + * format of function is: function(Group_Chat *chat, peer number, message, message length, userdata) + */ +void callback_groupmessage(Group_Chat *chat, void (*function)(Group_Chat *chat, int, const uint8_t *, uint16_t, void *), + void *userdata); + +/* + * Set callback function for actions. + * + * format of function is: function(Group_Chat *chat, peer number, action, action length, userdata) + */ +void callback_groupaction(Group_Chat *chat, void (*function)(Group_Chat *chat, int, const uint8_t *, uint16_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) + * + * format of function is: function(Group_Chat *chat, userdata) + */ +typedef enum { + CHAT_CHANGE_PEER_ADD, + CHAT_CHANGE_PEER_DEL, + CHAT_CHANGE_PEER_NAME, +} CHAT_CHANGE; + +void callback_namelistchange(Group_Chat *chat, void (*function)(Group_Chat *chat, int peer, uint8_t change, void *), + void *userdata); + +/* + * Send a message to the group. + * + * returns the number of peers it has sent it to. + */ +uint32_t group_sendmessage(Group_Chat *chat, const uint8_t *message, uint32_t length); + +/* + * Send an action to the group. + * + * returns the number of peers it has sent it to. + */ +uint32_t group_sendaction(Group_Chat *chat, const uint8_t *action, uint32_t length); + +/* + * Set our nick for this group. + * + * returns -1 on failure, 0 on success. + */ +int set_nick(Group_Chat *chat, const uint8_t *nick, uint16_t nick_len); + +/* + * Tell everyone about a new peer (a person we are inviting for example.) + * + */ +uint32_t group_newpeer(Group_Chat *chat, const uint8_t *client_id); + + +/* Create a new group chat. + * + * Returns a new group chat instance if success. + * + * Returns a NULL pointer if fail. + */ +Group_Chat *new_groupchat(Networking_Core *net); + + +/* Return the number of peers in the group chat. + */ +uint32_t group_numpeers(const Group_Chat *chat); + +/* List all the peers in the group chat. + * + * Copies the names of the peers to the name[length][MAX_NICK_BYTES] array. + * + * returns the number of peers. + */ +uint32_t group_client_names(const Group_Chat *chat, uint8_t names[][MAX_NICK_BYTES], uint16_t lengths[], + uint16_t length); + +/* Kill a group chat + * + * Frees the memory and everything. + */ +void kill_groupchat(Group_Chat *chat); + +/* + * This is the main loop. + */ +void do_groupchat(Group_Chat *chat); + +/* if we receive a group chat packet we call this function so it can be handled. + return 0 if packet is handled correctly. + return 1 if it didn't handle the packet or if the packet was shit. */ +int handle_groupchatpacket(Group_Chat *chat, IP_Port source, const uint8_t *packet, uint32_t length); + + +void chat_bootstrap(Group_Chat *chat, IP_Port ip_port, const uint8_t *client_id); +void chat_bootstrap_nonlazy(Group_Chat *chat, IP_Port ip_port, const uint8_t *client_id); + + +#endif diff --git a/protocols/Tox/toxcore/toxcore/list.c b/protocols/Tox/toxcore/toxcore/list.c new file mode 100644 index 0000000000..301e56f832 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/list.c @@ -0,0 +1,256 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#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 void *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 void *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 void *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 void *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; +} diff --git a/protocols/Tox/toxcore/toxcore/list.h b/protocols/Tox/toxcore/toxcore/list.h new file mode 100644 index 0000000000..03ac04ddbe --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/list.h @@ -0,0 +1,85 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifndef LIST_H +#define LIST_H + +#include <stdlib.h> +#include <stdint.h> +#include <string.h> + +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 + void *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 void *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 void *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 void *data, int id); + +/* Removes the memory overhead + * + * return value: + * 1 : success + * 0 : failure + */ +int bs_list_trim(BS_LIST *list); + +#endif diff --git a/protocols/Tox/toxcore/toxcore/logger.c b/protocols/Tox/toxcore/toxcore/logger.c new file mode 100644 index 0000000000..48a2f30328 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/logger.c @@ -0,0 +1,153 @@ +/* logger.c + * + * Wrapping logger functions in nice macros + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "logger.h" + +#ifdef LOGGING + +#include "network.h" /* for time */ + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <stdarg.h> +#include <inttypes.h> +#include <time.h> + +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) +#define strerror_r(errno,buf,len) strerror_s(buf,len,errno) +#endif + +static struct logger_config { + FILE *log_file; + LoggerLevel level; + uint64_t start_time; /* Time when lib loaded */ +} +logger = { + NULL, + DEBUG, + 0 +}; + +void __attribute__((destructor)) terminate_logger() +{ + if ( !logger.log_file ) return; + + time_t tim = time(NULL); + + logger_write(ERROR, "\n============== Closing logger [%u] ==============\n" + "Time: %s", logger_get_pid(), asctime(localtime(&tim))); + + fclose(logger.log_file); +} + +unsigned logger_get_pid() +{ + return +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) + GetCurrentProcessId(); +#else + getpid(); +#endif +} + +const char *logger_stringify_level(LoggerLevel level) +{ + static const char *strings [] = { + "INFO", + "DEBUG", + "WARNING", + "ERROR" + }; + + return strings[level]; +} + + +int logger_init(const char *file_name, LoggerLevel level) +{ + char *final_l = calloc(sizeof(char), strlen(file_name) + 32); + sprintf(final_l, "%s"/*.%u"*/, file_name/*, logger_get_pid()*/); + + if ( logger.log_file ) { + fprintf(stderr, "Error opening logger name: %s with level %d: file already opened!\n", final_l, level); + free (final_l); + return -1; + } + + logger.log_file = fopen(final_l, "ab"); + + if ( logger.log_file == NULL ) { + fprintf(stderr, "Error opening logger file: %s; info: %s\n", final_l, strerror(errno)); + + free (final_l); + return -1; + } + + + logger.level = level; + logger.start_time = current_time_monotonic(); + + + time_t tim = time(NULL); + logger_write(ERROR, "\n============== Starting logger [%u] ==============\n" + "Time: %s", logger_get_pid(), asctime(localtime(&tim))); + + + + free (final_l); + return 0; +} + + +void logger_write (LoggerLevel level, const char *format, ...) +{ + if (logger.log_file == NULL) { + /*fprintf(stderr, "Logger file is NULL!\n");*/ + return; + } + + if (logger.level > level) return; /* Don't print some levels xuh */ + + va_list _arg; + va_start (_arg, format); + vfprintf (logger.log_file, format, _arg); + va_end (_arg); + + fflush(logger.log_file); +} + +char *logger_timestr(char *dest, size_t max_size) +{ + uint64_t diff = (current_time_monotonic() - logger.start_time); /* ms */ + snprintf(dest, max_size, "%"PRIu64"", diff); + + return dest; +} + + +#endif /* LOGGING */ diff --git a/protocols/Tox/toxcore/toxcore/logger.h b/protocols/Tox/toxcore/toxcore/logger.h new file mode 100644 index 0000000000..56fe086d89 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/logger.h @@ -0,0 +1,84 @@ +/* logger.h + * + * Wrapping logger functions in nice macros + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + + +#ifndef __TOXLOGGER +#define __TOXLOGGER + +#include <string.h> + +#ifdef LOGGING + +typedef enum _LoggerLevel { + INFO, + DEBUG, + WARNING, + ERROR +} LoggerLevel; + +/* + * Set 'level' as the lowest printable level + */ +int logger_init(const char *file_name, LoggerLevel level); +const char *logger_stringify_level(LoggerLevel level); +unsigned logger_get_pid(); +void logger_write (LoggerLevel level, const char *format, ...); +char *logger_timestr (char *dest, size_t max_size); + +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) +#define _SFILE (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) +#else +#define _SFILE (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#endif + +#define WRITE_FORMAT(__LEVEL__, format) char __time__[20]; char* the_str = calloc(sizeof(char), strlen(format)+ 500); sprintf(the_str, "\n[%u] [%s] [%s] [%s:%d %s()] %s", \ + logger_get_pid(), logger_stringify_level(__LEVEL__), logger_timestr(__time__, 20), _SFILE, __LINE__, __func__, format) + +/* Use these macros */ + +#define LOGGER_INIT(name, level) logger_init(name, level); +#define LOGGER_INFO(format, ...) do { WRITE_FORMAT(INFO, format); logger_write( INFO, the_str, ##__VA_ARGS__ ); free(the_str); } while (0) +#define LOGGER_DEBUG(format, ...) do { WRITE_FORMAT(DEBUG, format); logger_write( DEBUG, the_str, ##__VA_ARGS__ ); free(the_str); } while (0) +#define LOGGER_WARNING(format, ...) do { WRITE_FORMAT(WARNING, format); logger_write( WARNING, the_str, ##__VA_ARGS__ ); free(the_str); } while (0) +#define LOGGER_ERROR(format, ...) do { WRITE_FORMAT(ERROR, format); logger_write( ERROR, the_str, ##__VA_ARGS__ ); free(the_str); } while (0) + +/* To do some checks or similar only when logging use this */ +#define LOGGER_SCOPE(__SCOPE_DO__) do { __SCOPE_DO__ } while(0) + +#else + + +#define LOGGER_INIT(name, level) do {} while(0) +#define LOGGER_INFO(format, ...) do {} while(0) +#define LOGGER_DEBUG(format, ...) do {} while(0) +#define LOGGER_WARNING(format, ...) do {} while(0) +#define LOGGER_ERROR(format, ...) do {} while(0) + +#define LOGGER_SCOPE(__SCOPE_DO__) do {} while(0) + +#endif /* LOGGING */ + + + + +#endif /* __TOXLOGGER */ diff --git a/protocols/Tox/toxcore/toxcore/misc_tools.h b/protocols/Tox/toxcore/toxcore/misc_tools.h new file mode 100644 index 0000000000..543338b50b --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/misc_tools.h @@ -0,0 +1,70 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#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 diff --git a/protocols/Tox/toxcore/toxcore/net_crypto.c b/protocols/Tox/toxcore/toxcore/net_crypto.c new file mode 100644 index 0000000000..8b5de6326d --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/net_crypto.c @@ -0,0 +1,2679 @@ +/* net_crypto.c + * + * Functions for the core network crypto. + * See also: http://wiki.tox.im/index.php/DHT + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "net_crypto.h" +#include "util.h" +#include "math.h" + +static uint8_t crypt_connection_id_not_valid(const Net_Crypto *c, int crypt_connection_id) +{ + return (uint32_t)crypt_connection_id >= c->crypto_connections_length; +} + +/* cookie timeout in seconds */ +#define COOKIE_TIMEOUT 10 +#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, uint32_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(const Net_Crypto *c, TCP_Client_Connection *TCP_con, uint8_t conn_id, + const uint8_t *packet, uint32_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; + + if (send_data(TCP_con, conn_id, data, sizeof(data)) != 1) + return -1; + + return 0; +} + +/* Handle the cookie request packet (for TCP oob packets) + */ +static int tcp_oob_handle_cookie_request(const Net_Crypto *c, TCP_Client_Connection *TCP_con, + const uint8_t *dht_public_key, const uint8_t *packet, uint32_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 (memcmp(dht_public_key, dht_public_key_temp, crypto_box_PUBLICKEYBYTES) != 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 (send_oob_packet(TCP_con, dht_public_key, data, sizeof(data)) != 1) + return -1; + + return 0; +} + +/* 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, uint32_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, uint32_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 (crypto_cmp(cookie_plain, expected_real_pk, crypto_box_PUBLICKEYBYTES) != 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 (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]; +} + + +/* Sends a packet to the peer using the fastest route. + * + * return -1 on failure. + * return 0 on success. + */ +static int send_packet_to(const 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; + + //TODO: on bad networks, direct connections might not last indefinitely. + if (conn->ip_port.ip.family != 0) { + uint8_t direct_connected = 0; + crypto_connection_status(c, crypt_connection_id, &direct_connected); + + if (direct_connected && (uint32_t)sendpacket(c->dht->net, conn->ip_port, data, length) == length) + return 0; + + //TODO: a better way of sending packets directly to confirm the others ip. + if (length < 96 || data[0] == NET_PACKET_COOKIE_REQUEST || data[0] == NET_PACKET_CRYPTO_HS) { + if ((uint32_t)sendpacket(c->dht->net, conn->ip_port, data, length) == length) + direct_send_attempt = 1; + } + + } + + //TODO: detect and kill bad relays. + uint32_t i; + + unsigned int r = crypt_connection_id; + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + if (conn->status_tcp[(i + r) % MAX_TCP_CONNECTIONS] == STATUS_TCP_ONLINE) {/* friend is connected to this relay. */ + if (send_data(c->tcp_connections[(i + r) % MAX_TCP_CONNECTIONS], conn->con_number_tcp[(i + r) % MAX_TCP_CONNECTIONS], + data, length) == 1) + return 0; + } + } + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + if (conn->status_tcp[(i + r) % MAX_TCP_CONNECTIONS] == STATUS_TCP_INVISIBLE) { + if (send_oob_packet(c->tcp_connections[(i + r) % MAX_TCP_CONNECTIONS], conn->dht_public_key, data, length) == 1) + return 0; + } + } + + if (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; +} + +/* 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 recieved 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) +{ + 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; + + 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]) { + send_array->buffer[num]->time = 0; + } + + ++data; + --length; + n = 0; + ++requested; + } else { + 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; + } + } + + 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(const 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); + int ret = send_packet_to(c, crypt_connection_id, packet, sizeof(packet)); + pthread_mutex_unlock(&conn->mutex); + return ret; +} + +/* 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(const Net_Crypto *c, int crypt_connection_id, uint32_t buffer_start, uint32_t num, + const uint8_t *data, uint32_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), 0, padding_length); + memcpy(packet + (sizeof(uint32_t) * 2) + padding_length, data, length); + + return send_data_packet(c, crypt_connection_id, packet, sizeof(packet)); +} + +/* 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(const Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint32_t length) +{ + 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; + + uint64_t temp_time = current_time_monotonic(); + + /* 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); + + if (ret == 1) { + if (!dt->time) { + if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, dt->data, + dt->length) != 0) { + return -1; + } + + dt->time = temp_time; + } + } + } + + Packet_Data dt; + dt.time = temp_time; + dt.length = length; + memcpy(dt.data, data, length); + int64_t packet_num = add_data_end_of_buffer(&conn->send_array, &dt); + + if (packet_num == -1) + return -1; + + conn->maximum_speed_reached = 0; + + if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, data, length) != 0) { + Packet_Data *dt1 = NULL; + get_data_pointer(&conn->send_array, &dt1, packet_num); + dt1->time = 0; + conn->maximum_speed_reached = 1; + fprintf(stderr, "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(const 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(const Net_Crypto *c, int crypt_connection_id, uint16_t max_num) +{ + if (max_num == 0) + return -1; + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) + return -1; + + 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->time != 0) { + continue; + } + + dt->time = current_time_monotonic(); + + if (send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, packet_num, dt->data, + dt->length) == 0) + ++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(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) + 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(const 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(const 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)); +} + +/* Handle a recieved data packet. + * + * return -1 on failure. + * return 0 on success. + */ +static int handle_data_packet_helper(const Net_Crypto *c, int crypt_connection_id, const uint8_t *packet, + uint16_t length) +{ + 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); + + if (buffer_start != conn->send_array.buffer_start && 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] == 0) { /* Remove Padding */ + ++real_data; + --real_length; + + if (real_length == 0) + return -1; + } + + if (real_data[0] == PACKET_ID_KILL) { + conn->killed = 1; + 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) { + int requested = handle_request_packet(&conn->send_array, real_data, real_length); + + 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.time = current_time_monotonic(); + 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 (read_data_beg_buffer(&conn->recv_array, &dt) != -1) { + if (conn->connection_data_callback) + conn->connection_data_callback(conn->connection_data_callback_object, conn->connection_data_callback_id, dt.data, + dt.length); + } + + /* 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)) { + + 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); + + set_buffer_end(&conn->recv_array, num); + } else { + return -1; + } + + return 0; +} + +/* Handle a packet that was recieved 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) +{ + 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; + + 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; + /* Status needs to be CRYPTO_CONN_NOT_CONFIRMED for this to work. */ + set_connection_dht_public_key(c, crypt_connection_id, dht_public_key, current_time_monotonic()); + } 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); + } 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) { + memset(&(c->crypto_connections[c->crypto_connections_length]), 0, sizeof(Crypto_Connection)); + id = c->crypto_connections_length; + pthread_mutex_init(&c->crypto_connections[id].mutex, NULL); + ++c->crypto_connections_length; + } + + 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; + pthread_mutex_destroy(&c->crypto_connections[crypt_connection_id].mutex); + memset(&(c->crypto_connections[crypt_connection_id]), 0 , sizeof(Crypto_Connection)); + + for (i = c->crypto_connections_length; i != 0; --i) { + if (c->crypto_connections[i - 1].status != CRYPTO_CONN_NO_CONNECTION) + 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 (memcmp(public_key, c->crypto_connections[i].public_key, crypto_box_PUBLICKEYBYTES) == 0) + return i; + } + + return -1; +} + +/* 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_dht_pubkey(const Net_Crypto *c, const uint8_t *dht_public_key) +{ + uint32_t i; + + for (i = 0; i < c->crypto_connections_length; ++i) { + if (c->crypto_connections[i].status != CRYPTO_CONN_NO_CONNECTION && c->crypto_connections[i].dht_public_key_set) + if (memcmp(dht_public_key, c->crypto_connections[i].dht_public_key, crypto_box_PUBLICKEYBYTES) == 0) + return i; + } + + return -1; +} + +/* Add a source to the crypto connection. + * This is to be used only when we have recieved a packet from that source. + * + * return -1 on failure. + * return positive number on success. + * 0 if source was a direct UDP connection. + * TODO + */ +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 (!ipport_equal(&source, &conn->ip_port)) { + if (!bs_list_add(&c->ip_port_list, &source, crypt_connection_id)) + return -1; + + bs_list_remove(&c->ip_port_list, &conn->ip_port, crypt_connection_id); + conn->ip_port = source; + } + + conn->direct_lastrecv_time = current_time_monotonic(); + return 0; + } + + 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) { + int ret = -1; + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn != 0 && (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; + /* Status needs to be CRYPTO_CONN_NOT_CONFIRMED for this to work. */ + set_connection_dht_public_key(c, crypt_connection_id, n_c.dht_public_key, current_time_monotonic()); + 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 = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) + return -1; + + 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); + + if (n_c->cookie_length != COOKIE_LENGTH) + return -1; + + if (create_send_handshake(c, crypt_connection_id, n_c->cookie, n_c->dht_public_key) != 0) + return -1; + + conn->status = CRYPTO_CONN_NOT_CONFIRMED; + /* Status needs to be CRYPTO_CONN_NOT_CONFIRMED for this to work. */ + set_connection_dht_public_key(c, crypt_connection_id, n_c->dht_public_key, current_time_monotonic()); + conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; + conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; + 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) +{ + 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 = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) + return -1; + + 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->packets_left = CRYPTO_MIN_QUEUE_LENGTH; + return crypt_connection_id; +} + +/* Disconnect peer from all associated TCP connections. + * + * return -1 on failure. + * return 0 on success. + */ +static int disconnect_peer_tcp(const Net_Crypto *c, int crypt_connection_id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) + return -1; + + uint32_t i; + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + if (conn->status_tcp[i] != STATUS_TCP_NULL) { + send_disconnect_request(c->tcp_connections[i], conn->con_number_tcp[i]); + conn->status_tcp[i] = STATUS_TCP_NULL; + conn->con_number_tcp[i] = 0; + } + } + + return 0; +} + +/* Connect peer to all associated TCP connections. + * + * return -1 on failure. + * return 0 on success. + */ +static int connect_peer_tcp(const Net_Crypto *c, int crypt_connection_id) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) + return -1; + + uint32_t i; + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + if (c->tcp_connections[i] == NULL) + continue; + + //TODO check function return? + send_routing_request(c->tcp_connections[i], conn->dht_public_key); + } + + return 0; +} + +/* Copy friends DHT public key into dht_key. + * + * return 0 on failure (no key copied). + * return timestamp on success (key copied). + */ +uint64_t get_connection_dht_key(const Net_Crypto *c, int crypt_connection_id, uint8_t *dht_public_key) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) + return 0; + + if (conn->dht_public_key_set == 0) + return 0; + + memcpy(dht_public_key, conn->dht_public_key, crypto_box_PUBLICKEYBYTES); + return conn->dht_public_key_timestamp; +} + + +/* Set the DHT public key of the crypto connection. + * 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 set_connection_dht_public_key(const Net_Crypto *c, int crypt_connection_id, const uint8_t *dht_public_key, + uint64_t timestamp) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) + return -1; + + if (timestamp <= conn->dht_public_key_timestamp) + return -1; + + if (conn->dht_public_key_set == 1 && memcmp(conn->dht_public_key, dht_public_key, crypto_box_PUBLICKEYBYTES) == 0) + return -1; + + if (conn->dht_public_key_set == 1) { + disconnect_peer_tcp(c, crypt_connection_id); + } + + memcpy(conn->dht_public_key, dht_public_key, crypto_box_PUBLICKEYBYTES); + conn->dht_public_key_set = 1; + conn->dht_public_key_timestamp = timestamp; + + if (conn->status == CRYPTO_CONN_COOKIE_REQUESTING) { + 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)) + return -1; + + if (new_temp_packet(c, crypt_connection_id, cookie_request, sizeof(cookie_request)) != 0) + return -1; + }//TODO + + connect_peer_tcp(c, crypt_connection_id); + return 0; +} + +/* Set the direct ip of the crypto connection. + * + * return -1 on failure. + * return 0 on success. + */ +int set_direct_ip_port(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 (!ipport_equal(&ip_port, &conn->ip_port)) { + if (bs_list_add(&c->ip_port_list, &ip_port, crypt_connection_id)) { + bs_list_remove(&c->ip_port_list, &conn->ip_port, crypt_connection_id); + conn->ip_port = ip_port; + conn->direct_lastrecv_time = 0; + return 0; + } + } + + return -1; +} + +static int tcp_response_callback(void *object, uint8_t connection_id, const uint8_t *public_key) +{ + TCP_Client_Connection *TCP_con = object; + Net_Crypto *c = TCP_con->net_crypto_pointer; + + int crypt_connection_id = getcryptconnection_id_dht_pubkey(c, public_key); + + if (crypt_connection_id == -1) + return -1; + + set_tcp_connection_number(TCP_con, connection_id, crypt_connection_id); + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) + return -1; + + uint32_t location = TCP_con->net_crypto_location; + + if (location >= MAX_TCP_CONNECTIONS) + return -1; + + if (c->tcp_connections[location] != TCP_con) + return -1; + + conn->con_number_tcp[location] = connection_id; + uint32_t i; + + for (i = 0; i < conn->num_tcp_relays; ++i) { + if (memcmp(TCP_con->public_key, conn->tcp_relays[i].client_id, crypto_box_PUBLICKEYBYTES) == 0) { + conn->status_tcp[location] = STATUS_TCP_INVISIBLE; + return 0; + } + } + + conn->status_tcp[location] = STATUS_TCP_OFFLINE; + return 0; +} + +static int tcp_status_callback(void *object, uint32_t number, uint8_t connection_id, uint8_t status) +{ + TCP_Client_Connection *TCP_con = object; + Net_Crypto *c = TCP_con->net_crypto_pointer; + + Crypto_Connection *conn = get_crypto_connection(c, number); + + if (conn == 0) + return -1; + + uint32_t location = TCP_con->net_crypto_location; + + if (location >= MAX_TCP_CONNECTIONS) + return -1; + + if (c->tcp_connections[location] != TCP_con) + return -1; + + if (status == 1) { + conn->status_tcp[location] = STATUS_TCP_OFFLINE; + } else if (status == 2) { + conn->status_tcp[location] = STATUS_TCP_ONLINE; + } + + conn->con_number_tcp[location] = connection_id; + 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_con = object; + Net_Crypto *c = TCP_con->net_crypto_pointer; + + if (data[0] == NET_PACKET_COOKIE_REQUEST) { + return tcp_handle_cookie_request(c, TCP_con, connection_id, data, length); + } + + Crypto_Connection *conn = get_crypto_connection(c, number); + + if (conn == 0) + return -1; + + if (handle_packet_connection(c, number, data, length) != 0) + return -1; + + //TODO detect and kill bad TCP connections. + return 0; +} + +static int tcp_oob_callback(void *object, const uint8_t *public_key, const uint8_t *data, uint16_t length) +{ + if (length == 0 || length > MAX_CRYPTO_PACKET_SIZE) + return -1; + + TCP_Client_Connection *TCP_con = object; + Net_Crypto *c = TCP_con->net_crypto_pointer; + uint32_t location = TCP_con->net_crypto_location; + + if (data[0] == NET_PACKET_COOKIE_REQUEST) { + return tcp_oob_handle_cookie_request(c, TCP_con, public_key, data, length); + } + + int crypt_connection_id = getcryptconnection_id_dht_pubkey(c, public_key); + + if (crypt_connection_id == -1) { + IP_Port source; + source.ip.family = TCP_FAMILY; + source.ip.ip6.uint32[0] = location; + + if (data[0] != NET_PACKET_CRYPTO_HS) { + fprintf(stderr, "tcp snhappen %u\n", data[0]); + return -1; + } + + if (handle_new_connection_handshake(c, source, data, length) != 0) + return -1; + + return 0; + } + + if (handle_packet_connection(c, crypt_connection_id, data, length) != 0) + return -1; + + return 0; +} + +/* Check if tcp connection to public key can be created. + * + * return -1 if it can't. + * return 0 if it can. + */ +static int tcp_connection_check(const Net_Crypto *c, const uint8_t *public_key) +{ + uint32_t i; + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + if (c->tcp_connections_new[i] == NULL) + continue; + + if (memcmp(c->tcp_connections_new[i]->public_key, public_key, crypto_box_PUBLICKEYBYTES) == 0) + return -1; + } + + uint32_t num = 0; + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + if (c->tcp_connections[i] == NULL) + continue; + + if (memcmp(c->tcp_connections[i]->public_key, public_key, crypto_box_PUBLICKEYBYTES) == 0) + return -1; + + ++num; + } + + if (num == MAX_TCP_CONNECTIONS) + return -1; + + return 0; +} + +/* 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; + + 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; + + uint32_t i; + + for (i = 0; i < conn->num_tcp_relays; ++i) { + if (memcmp(conn->tcp_relays[i].client_id, public_key, crypto_box_PUBLICKEYBYTES) == 0) { + conn->tcp_relays[i].ip_port = ip_port; + return 0; + } + } + + if (conn->num_tcp_relays == MAX_TCP_RELAYS_PEER) { + uint16_t index = rand() % MAX_TCP_RELAYS_PEER; + conn->tcp_relays[index].ip_port = ip_port; + memcpy(conn->tcp_relays[index].client_id, public_key, crypto_box_PUBLICKEYBYTES); + } else { + conn->tcp_relays[conn->num_tcp_relays].ip_port = ip_port; + memcpy(conn->tcp_relays[conn->num_tcp_relays].client_id, public_key, crypto_box_PUBLICKEYBYTES); + ++conn->num_tcp_relays; + } + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + if (c->tcp_connections[i] == NULL) + continue; + + if (memcmp(c->tcp_connections[i]->public_key, public_key, crypto_box_PUBLICKEYBYTES) == 0) { + if (conn->status_tcp[i] == STATUS_TCP_OFFLINE) + conn->status_tcp[i] = STATUS_TCP_INVISIBLE; + } + } + + return add_tcp_relay(c, ip_port, 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) +{ + 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; + + if (tcp_connection_check(c, public_key) != 0) + return -1; + + uint32_t i; + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + if (c->tcp_connections_new[i] == NULL) { + c->tcp_connections_new[i] = new_TCP_connection(ip_port, public_key, c->dht->self_public_key, c->dht->self_secret_key); + return 0; + } + } + + return -1; +} + +/* 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(const Net_Crypto *c, Node_format *tcp_relays, uint16_t num) +{ + if (num == 0) + return 0; + + uint32_t i; + uint16_t copied = 0; + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + if (c->tcp_connections[i] != NULL) { + memcpy(tcp_relays[copied].client_id, c->tcp_connections[i]->public_key, crypto_box_PUBLICKEYBYTES); + tcp_relays[copied].ip_port = c->tcp_connections[i]->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; + + if (copied == num) + return copied; + } + } + + return copied; +} + +/* Add a connected tcp connection to the tcp_connections array. + * + * return 0 if it was added. + * return -1 if it wasn't. + */ +static int add_tcp_connected(Net_Crypto *c, TCP_Client_Connection *tcp_con) +{ + uint32_t i; + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + if (c->tcp_connections[i] == NULL) + break; + } + + if (i == MAX_TCP_CONNECTIONS) + return -1; + + uint32_t tcp_num = i; + + for (i = 0; i < c->crypto_connections_length; ++i) { + Crypto_Connection *conn = get_crypto_connection(c, i); + + if (conn == 0) + return -1; + + if (conn->status == CRYPTO_CONN_NO_CONNECTION) + continue; + + if (conn->status == CRYPTO_CONN_TIMED_OUT) + continue; + + if (conn->dht_public_key_set) + if (send_routing_request(tcp_con, conn->dht_public_key) != 1) + return -1; + + } + + tcp_con->net_crypto_pointer = c; + tcp_con->net_crypto_location = tcp_num; + routing_response_handler(tcp_con, tcp_response_callback, tcp_con); + routing_status_handler(tcp_con, tcp_status_callback, tcp_con); + routing_data_handler(tcp_con, tcp_data_callback, tcp_con); + oob_data_handler(tcp_con, tcp_oob_callback, tcp_con); + c->tcp_connections[tcp_num] = tcp_con; + return 0; +} + +static void do_tcp(Net_Crypto *c) +{ + uint32_t i; + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + if (c->tcp_connections_new[i] == NULL) + continue; + + do_TCP_connection(c->tcp_connections_new[i]); + + if (c->tcp_connections_new[i]->status == TCP_CLIENT_CONFIRMED) { + if (add_tcp_connected(c, c->tcp_connections_new[i]) == 0) { + c->tcp_connections_new[i] = NULL; + } else { + kill_TCP_connection(c->tcp_connections_new[i]); + c->tcp_connections_new[i] = NULL; + } + } + } + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + if (c->tcp_connections[i] == NULL) + continue; + + do_TCP_connection(c->tcp_connections[i]); + } +} + +static void clear_disconnected_tcp_peer(Crypto_Connection *conn, uint32_t number) +{ + if (conn->status == CRYPTO_CONN_NO_CONNECTION) + return; + + if (number >= MAX_TCP_CONNECTIONS) + return; + + conn->status_tcp[number] = STATUS_TCP_NULL; + conn->con_number_tcp[number] = 0; +} + +static void clear_disconnected_tcp(Net_Crypto *c) +{ + uint32_t i, j; + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + if (c->tcp_connections_new[i] == NULL) + continue; + + if (c->tcp_connections_new[i]->status != TCP_CLIENT_DISCONNECTED) + continue; + + kill_TCP_connection(c->tcp_connections_new[i]); + c->tcp_connections_new[i] = NULL; + } + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + if (c->tcp_connections[i] == NULL) + continue; + + TCP_Client_Connection *tcp_con = c->tcp_connections[i]; + + if (tcp_con->status != TCP_CLIENT_DISCONNECTED) + continue; + + c->tcp_connections[i] = NULL; + kill_TCP_connection(tcp_con); + + for (j = 0; j < c->crypto_connections_length; ++j) { + Crypto_Connection *conn = get_crypto_connection(c, j); + + if (conn == 0) + return; + + clear_disconnected_tcp_peer(conn, i); + } + } +} + +/* 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; +} + +/* 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, &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, uint32_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) != 0) + return 1; + + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) + return -1; + + conn->direct_lastrecv_time = current_time_monotonic(); + return 0; +} + +/* Value to set sending variable */ +#define CONN_SENDING_VALUE 2 + +/* The dT for the average packet recieving rate calculations. + Also used as the */ +#define PACKET_COUNTER_AVERAGE_INTERVAL 100 + +/* 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.5 * 100.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))); + + 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; + + /* conjestion control + calculate a new value of conn->packet_send_rate based on some data + notes: needs improvement but seems to work fine for packet loss <1% + */ + + /* additional step: adjust the send rate based on the size change of the send queue */ + uint32_t queue_size = num_packets_array(&conn->send_array); + + if (queue_size > conn->packet_send_rate && queue_size > conn->last_queue_size) { + conn->rate_increase = 0; + conn->packets_resent = conn->packets_sent; + } + + + //hack to prevent 1 packet lost from affecting calculations at low send rates + if (conn->packets_resent == 1) { + conn->packets_resent = 0; + } + + //new "dropped" value: weighted average of previous value and packet drop rate measured by the number of packets which were resends of previous packets + double dropped = (conn->dropped) * 0.30 + ((double)conn->packets_resent / dt) * 0.70; + + //since the "dropped" packets measure is delayed in time from the actual # of dropped packets, + // ignore dropped packet measure for 2 seconds after it becomes high and the send rate is lowered as a result + double drop_ignore_new; + + if (conn->drop_ignore_start + 2000 < temp_time) { + drop_ignore_new = 0.0; + + if ((dropped * 1000.0) / conn->packet_send_rate >= 0.10) { + conn->drop_ignore_start = temp_time; + } + } else { + drop_ignore_new = (dropped > conn->drop_ignore) ? dropped : conn->drop_ignore; + } + + //calculate the "real" send rate (send rate - drop rate) + double r = conn->packet_send_rate; + double realrate = (r - (dropped - drop_ignore_new) * 1000.0); + + if (dropped < drop_ignore_new) { + realrate = r; + } + + //calculate exponential increase in rate, triggered when drop rate is below 5% for 5 seconds + if ((dropped * 1000.0) / conn->packet_send_rate >= 0.05) { + conn->rate_increase_stop_start = temp_time; + } + + if (conn->rate_increase_stop_start + 5000 < temp_time) { + if (conn->rate_increase < 1.0) { + conn->rate_increase = 1.0; + } + + conn->rate_increase *= pow(1.1, pow(realrate / r, 10.0));; + } else { + conn->rate_increase = 0.0; + } + + + //"constant" linear increase in rate + double linear_increase = realrate * 0.0025 + 1.0; + + //final send rate: average of "real" and previous send rates + increases + double newrate = (realrate + conn->packet_send_rate) / 2.0 + conn->rate_increase + linear_increase; + conn->last_send_rate = conn->packet_send_rate; + conn->packet_send_rate = newrate; + + + conn->dropped = dropped; + conn->drop_ignore = drop_ignore_new; + conn->packets_resent = 0; + conn->last_queue_size = queue_size; + + if (!conn->sending || !conn->packets_sent) { + conn->rate_increase = 0; + conn->packet_send_rate /= 2; + } + + if (conn->packet_send_rate < CRYPTO_PACKET_MIN_RATE) { + conn->packet_send_rate = CRYPTO_PACKET_MIN_RATE; + } + + conn->packets_sent = 0; + + if (conn->sending != 0 && num_packets_array(&conn->send_array) < CRYPTO_MIN_QUEUE_LENGTH / 2) { + --conn->sending; + } + + } + + if (conn->last_packets_left_set == 0) { + conn->last_packets_left_set = temp_time; + conn->packets_left = CRYPTO_MIN_QUEUE_LENGTH; + } else if (((1000.0 / conn->packet_send_rate) + conn->last_packets_left_set) < temp_time) { + uint32_t num_packets = conn->packet_send_rate * ((double)(temp_time - conn->last_packets_left_set) / 1000.0) + 0.5; + + if (conn->packets_left > num_packets * 2 + CRYPTO_MIN_QUEUE_LENGTH) { + conn->packets_left = num_packets * 2 + CRYPTO_MIN_QUEUE_LENGTH; + } else { + conn->packets_left += num_packets; + } + + conn->last_packets_left_set = temp_time; + } + + int ret = send_requested_packets(c, i, conn->packets_left); + + if (ret != -1) { + if (ret != 0) { + conn->sending = CONN_SENDING_VALUE; + } + + conn->packets_resent += ret; + conn->packets_left -= ret; + conn->packets_sent += ret; + } + + 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; + } +} + + +/* 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; + + return conn->packets_left; +} + +/* 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. + */ +int64_t write_cryptpacket(const Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint32_t length) +{ + 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 (conn->packets_left == 0) + return -1; + + int64_t ret = send_lossless_packet(c, crypt_connection_id, data, length); + + if (ret == -1) + return -1; + + --conn->packets_left; + conn->packets_sent++; + conn->sending = CONN_SENDING_VALUE; + return ret; +} + +/* 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, uint32_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) { + ret = send_data_packet_helper(c, crypt_connection_id, conn->recv_array.buffer_start, conn->send_array.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); + + disconnect_peer_tcp(c, crypt_connection_id); + bs_list_remove(&c->ip_port_list, &conn->ip_port, crypt_connection_id); + 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. + */ +unsigned int crypto_connection_status(const Net_Crypto *c, int crypt_connection_id, uint8_t *direct_connected) +{ + Crypto_Connection *conn = get_crypto_connection(c, crypt_connection_id); + + if (conn == 0) + return CRYPTO_CONN_NO_CONNECTION; + + *direct_connected = 0; + + if ((UDP_DIRECT_TIMEOUT + conn->direct_lastrecv_time) > current_time_monotonic()) + *direct_connected = 1; + + 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. + */ +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 public and private keys from the keys array. + * Length must be crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES. + */ +void load_keys(Net_Crypto *c, const uint8_t *keys) +{ + memcpy(c->self_public_key, keys, crypto_box_PUBLICKEYBYTES); + memcpy(c->self_secret_key, keys + crypto_box_PUBLICKEYBYTES, crypto_box_SECRETKEYBYTES); +} + +/* Run this to (re)initialize net_crypto. + * Sets all the global connection variables to their default values. + */ +Net_Crypto *new_net_crypto(DHT *dht) +{ + unix_time_update(); + + if (dht == NULL) + return NULL; + + Net_Crypto *temp = calloc(1, sizeof(Net_Crypto)); + + if (temp == NULL) + 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); + + pthread_mutex_init(&temp->connections_mutex, NULL); + 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 || conn->status == CRYPTO_CONN_TIMED_OUT) + 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; + + conn->killed = 1; + + } + + if (conn->killed) { + if (conn->connection_status_callback) { + conn->connection_status_callback(conn->connection_status_callback_object, conn->connection_status_callback_id, 0); + crypto_kill(c, i); + continue; + } + + conn->status = CRYPTO_CONN_TIMED_OUT; + continue; + } + + 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); + clear_disconnected_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); + } + + for (i = 0; i < MAX_TCP_CONNECTIONS; ++i) { + kill_TCP_connection(c->tcp_connections_new[i]); + kill_TCP_connection(c->tcp_connections[i]); + } + + pthread_mutex_destroy(&c->connections_mutex); + + 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); + memset(c, 0, sizeof(Net_Crypto)); + free(c); +} diff --git a/protocols/Tox/toxcore/toxcore/net_crypto.h b/protocols/Tox/toxcore/toxcore/net_crypto.h new file mode 100644 index 0000000000..07e9ef6a22 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/net_crypto.h @@ -0,0 +1,378 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifndef NET_CRYPTO_H +#define NET_CRYPTO_H + +#include "DHT.h" +#include "TCP_client.h" +#include <pthread.h> + +#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 +#define CRYPTO_CONN_ESTABLISHED 4 +#define CRYPTO_CONN_TIMED_OUT 5 + +#define CRYPTO_PACKET_BUFFER_SIZE 16384 /* Must be a power of 2 */ + +/* Minimum packet rate per second. */ +#define CRYPTO_PACKET_MIN_RATE 16.0 + +/* Minimum packet queue max length. */ +#define CRYPTO_MIN_QUEUE_LENGTH 16 + +#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 TODO*/ +#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 500 +/* 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 recieved UDP packets before the direct UDP connection is considered dead. */ +#define UDP_DIRECT_TIMEOUT (MAX_NUM_SENDPACKET_TRIES * CRYPTO_SEND_PACKET_INTERVAL * 2) + +#define PACKET_ID_PADDING 0 +#define PACKET_ID_REQUEST 1 +#define PACKET_ID_KILL 2 + +#define CRYPTO_RESERVED_PACKETS 16 + +#define MAX_TCP_CONNECTIONS 32 +#define MAX_TCP_RELAYS_PEER 4 + +#define STATUS_TCP_NULL 0 +#define STATUS_TCP_OFFLINE 1 +#define STATUS_TCP_INVISIBLE 2 /* we know the other peer is connected to this relay but he isn't appearing online */ +#define STATUS_TCP_ONLINE 3 + +/* 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. */ + +typedef struct { + uint64_t 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. + * 5 if the connection is timed out. + */ + 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 dht_public_key_set; /* True if the dht public key is set, false if it isn't. */ + uint64_t dht_public_key_timestamp; /* Timestamp of the last time we confirmed the key was correct. */ + + 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_port; /* The ip and port to contact this guy directly.*/ + uint64_t direct_lastrecv_time; /* The Time at which we last received a direct packet in ms. */ + + 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; + + 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 dropped, drop_ignore, rate_increase, last_send_rate; + uint64_t drop_ignore_start, rate_increase_stop_start; + uint32_t packets_resent, last_queue_size, packets_sent, last_packets_sent; + + uint8_t sending; /* indicates if data is being sent or not. */ + + uint8_t killed; /* set to 1 to kill the connection. */ + + uint8_t status_tcp[MAX_TCP_CONNECTIONS]; /* set to one of STATUS_TCP_* */ + uint8_t con_number_tcp[MAX_TCP_CONNECTIONS]; + + Node_format tcp_relays[MAX_TCP_RELAYS_PEER]; + uint16_t num_tcp_relays; + + uint8_t maximum_speed_reached; + + pthread_mutex_t mutex; +} 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; + + Crypto_Connection *crypto_connections; + TCP_Client_Connection *tcp_connections_new[MAX_TCP_CONNECTIONS]; + TCP_Client_Connection *tcp_connections[MAX_TCP_CONNECTIONS]; + + 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); + +/* Copy friends DHT public key into dht_key. + * + * return 0 on failure (no key copied). + * return timestamp on success (key copied). + */ +uint64_t get_connection_dht_key(const Net_Crypto *c, int crypt_connection_id, uint8_t *dht_public_key); + +/* Set the DHT public key of the crypto connection. + * 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 set_connection_dht_public_key(const Net_Crypto *c, int crypt_connection_id, const uint8_t *dht_public_key, + uint64_t timestamp); + +/* Set the direct ip of the crypto connection. + * + * return -1 on failure. + * return 0 on success. + */ +int set_direct_ip_port(Net_Crypto *c, int crypt_connection_id, IP_Port ip_port); + +/* 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); + +/* 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); + +/* 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. + */ +int64_t write_cryptpacket(const Net_Crypto *c, int crypt_connection_id, const uint8_t *data, uint32_t length); + +/* 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, uint32_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); + +/* 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(const 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. + */ +unsigned int crypto_connection_status(const Net_Crypto *c, int crypt_connection_id, uint8_t *direct_connected); + + +/* 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 public and private keys from the keys array. + * Length must be crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES. + */ +void load_keys(Net_Crypto *c, const uint8_t *keys); + +/* Create new instance of Net_Crypto. + * Sets all the global connection variables to their default values. + */ +Net_Crypto *new_net_crypto(DHT *dht); + +/* 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 diff --git a/protocols/Tox/toxcore/toxcore/network.c b/protocols/Tox/toxcore/toxcore/network.c new file mode 100644 index 0000000000..373fef9e40 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/network.c @@ -0,0 +1,971 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#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 <errno.h> +#endif + +#ifdef __APPLE__ +#include <mach/clock.h> +#include <mach/mach.h> +#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 +} + +/* 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 LOGGING +#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_INFO("[%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_INFO("[%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_INFO("[%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 /* LOGGING */ + +/* 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, uint32_t length) +{ + /* 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); + + + if ((res >= 0) && ((uint32_t)res == length)) + net->send_fail_eagain = 0; + else if ((res < 0) && (errno == EWOULDBLOCK)) + net->send_fail_eagain = current_time_monotonic(); + + 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) +{ + 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 <sodium.h> +#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. + * 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. + */ +Networking_Core *new_networking(IP ip, uint16_t port) +{ + /* 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); + 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)); + + /* Set socket nonblocking. */ + if (!set_socket_nonblock(temp->sock)) { + kill_networking(temp); + 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 LOGGING + int is_dualstack = +#endif /* LOGGING */ + 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 LOGGING + int res = +#endif /* LOGGING */ + 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; + *portptr = htons(port_to_try); + int tries; + + for (tries = TOX_PORTRANGE_FROM; tries <= TOX_PORTRANGE_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; + + return temp; + } + + port_to_try++; + + if (port_to_try > TOX_PORTRANGE_TO) + port_to_try = TOX_PORTRANGE_FROM; + + *portptr = htons(port_to_try); + } + +#ifdef DEBUG + fprintf(stderr, "Failed to bind socket: %u, %s (IP/Port: %s:%u\n", errno, + strerror(errno), ip_ntoa(&ip), port); +#endif + kill_networking(temp); + return NULL; +} + +/* Function to cleanup networking stuff. */ +void kill_networking(Networking_Core *net) +{ + 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)); +}; + +/* packing and unpacking functions */ +void ip_pack(uint8_t *data, const IP *source) +{ + data[0] = source->family; + memcpy(data + 1, &source->ip6, SIZE_IP6); +} + +void ip_unpack(IP *target, const uint8_t *data) +{ + target->family = data[0]; + memcpy(&target->ip6, data + 1, SIZE_IP6); +} + +void ipport_pack(uint8_t *data, const IP_Port *source) +{ + ip_pack(data, &source->ip); + memcpy(data + SIZE_IP, &source->port, SIZE_PORT); +} + +void ipport_unpack(IP_Port *target, const uint8_t *data) +{ + ip_unpack(&target->ip, data); + memcpy(&target->port, data + SIZE_IP, SIZE_PORT); +} + +/* ip_ntoa + * converts ip into a string + * uses a static buffer, so mustn't used multiple times in the same output + */ +/* there would be INET6_ADDRSTRLEN, but it might be too short for the error message */ +static char addresstext[96]; +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; +} + +/* + * 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 + */ +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; + + 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; + } + + IP4 ip4; + memset(&ip4, 0, sizeof(ip4)); + IP6 ip6; + memset(&ip6, 0, sizeof(ip6)); + + for (walker = server; (walker != NULL) && (rc != 3); 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; + rc = 3; + } else if (!(rc & 1)) { /* AF_UNSPEC requested, store away */ + struct sockaddr_in *addr = (struct sockaddr_in *)walker->ai_addr; + ip4.in_addr = addr->sin_addr; + rc |= 1; + } + + 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; + rc = 3; + } + } else if (!(rc & 2)) { /* AF_UNSPEC requested, store away */ + if (walker->ai_addrlen == sizeof(struct sockaddr_in6)) { + struct sockaddr_in6 *addr = (struct sockaddr_in6 *)walker->ai_addr; + ip6.in6_addr = addr->sin6_addr; + rc |= 2; + } + } + + break; /* switch */ + } + } + + if (to->family == AF_UNSPEC) { + if (rc & 2) { + to->family = AF_INET6; + to->ip6 = ip6; + + if ((rc & 1) && (extra != NULL)) { + extra->family = AF_INET; + extra->ip4 = ip4; + } + } else if (rc & 1) { + to->family = AF_INET; + to->ip4 = ip4; + } else + rc = 0; + } + + freeaddrinfo(server); + return rc; +} + +/* + * 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; +} diff --git a/protocols/Tox/toxcore/toxcore/network.h b/protocols/Tox/toxcore/toxcore/network.h new file mode 100644 index 0000000000..da175309e0 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/network.h @@ -0,0 +1,368 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifndef NETWORK_H +#define NETWORK_H + +#ifdef PLAN9 +#include <u.h> //Plan 9 requires this is imported first +#include <libc.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <time.h> + +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) /* Put win32 includes here */ +#ifndef WINVER +//Windows XP +#define WINVER 0x0501 +#endif +#include <winsock2.h> +#include <windows.h> +#include <ws2tcpip.h> + +#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 <fcntl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> +#include <sys/time.h> +#include <sys/types.h> +#include <netdb.h> +#include <unistd.h> + +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 65507 + +#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 3 /* Send nodes response packet ID for IPv4 addresses. */ +#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. */ +#define NET_PACKET_GROUP_CHATS 48 /* Group chats 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 __attribute__ ((__packed__)) +{ + uint8_t uint8[4]; + uint16_t uint16[2]; + uint32_t uint32; + struct in_addr in_addr; +} +IP4; + +typedef union __attribute__ ((__packed__)) +{ + uint8_t uint8[16]; + uint16_t uint16[8]; + uint32_t uint32[4]; + uint64_t uint64[2]; + struct in6_addr in6_addr; +} +IP6; + +typedef struct __attribute__ ((__packed__)) +{ + uint8_t family; + union { + IP4 ip4; + IP6 ip6; + }; +} +IP; + +typedef struct __attribute__ ((__packed__)) __attribute__((gcc_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 + +/* ip_ntoa + * converts ip into a string + * uses a static buffer, so mustn't used multiple times in the same output + */ +const char *ip_ntoa(const IP *ip); + +/* + * 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); + + +/* packs IP into data, writes SIZE_IP bytes to data */ +void ip_pack(uint8_t *data, const IP *source); +/* unpacks IP from data, reads SIZE_IP bytes from data */ +void ip_unpack(IP *target, const uint8_t *data); +/* packs IP_Port into data, writes SIZE_IPPORT bytes to data */ +void ipport_pack(uint8_t *data, const IP_Port *source); +/* unpacks IP_Port from data, reads SIZE_IPPORT bytes to data */ +void ipport_unpack(IP_Port *target, const uint8_t *data); + +/* + * 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, uint32_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; + uint64_t send_fail_eagain; +} 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); + +/* 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, uint32_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 0 if no problems. + * return -1 if there were problems. + */ +Networking_Core *new_networking(IP ip, uint16_t port); + +/* Function to cleanup networking stuff (doesn't do much right now). */ +void kill_networking(Networking_Core *net); + +#endif diff --git a/protocols/Tox/toxcore/toxcore/onion.c b/protocols/Tox/toxcore/toxcore/onion.c new file mode 100644 index 0000000000..1915324f84 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/onion.c @@ -0,0 +1,486 @@ +/* +* 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 <http://www.gnu.org/licenses/>. +* +*/ +#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 hour to make paths expire eventually. */ +#define KEY_REFRESH_INTERVAL (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(); + } +} + +/* Create a new onion path. + * + * Create a new onion path out of nodes (nodes is a list of 3 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].client_id, 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].client_id, 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].client_id, 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; + + /* to_net_family(&new_path->ip_port1.ip); */ + to_net_family(&new_path->ip_port2.ip); + to_net_family(&new_path->ip_port3.ip); + + 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, uint32_t length) +{ + if (1 + length + SEND_1 > max_packet_length || length == 0) + return -1; + + to_net_family(&dest.ip); + 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 ((uint32_t)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 ((uint32_t)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 ((uint32_t)len != SIZE_IPPORT + SEND_BASE * 2 + length + crypto_box_MACBYTES) + return -1; + + return 1 + crypto_box_NONCEBYTES + 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, uint32_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, uint32_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, uint32_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 ((uint32_t)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, uint32_t len, IP_Port source, const uint8_t *nonce) +{ + IP_Port send_to; + ipport_unpack(&send_to, plain); + to_host_family(&send_to.ip); + + 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); + uint32_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, uint32_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 ((uint32_t)len != length - (1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + RETURN_1 + crypto_box_MACBYTES)) + return 1; + + IP_Port send_to; + ipport_unpack(&send_to, plain); + to_host_family(&send_to.ip); + + 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); + uint32_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, uint32_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 ((uint32_t)len != length - (1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + RETURN_2 + crypto_box_MACBYTES)) + return 1; + + IP_Port send_to; + ipport_unpack(&send_to, plain); + to_host_family(&send_to.ip); + + uint8_t data[ONION_MAX_PACKET_SIZE]; + memcpy(data, plain + SIZE_IPPORT, len - SIZE_IPPORT); + uint32_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, uint32_t length) +{ + Onion *onion = object; + + if (length > ONION_MAX_PACKET_SIZE) + return 1; + + if (length <= 1 + RETURN_3) + 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; + ipport_unpack(&send_to, plain); + + 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)); + uint32_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, uint32_t length) +{ + Onion *onion = object; + + if (length > ONION_MAX_PACKET_SIZE) + return 1; + + if (length <= 1 + RETURN_2) + 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; + ipport_unpack(&send_to, plain); + + 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)); + uint32_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, uint32_t length) +{ + Onion *onion = object; + + if (length > ONION_MAX_PACKET_SIZE) + return 1; + + if (length <= 1 + RETURN_1) + 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; + ipport_unpack(&send_to, plain); + + uint32_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); +} diff --git a/protocols/Tox/toxcore/toxcore/onion.h b/protocols/Tox/toxcore/toxcore/onion.h new file mode 100644 index 0000000000..527f5c6067 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/onion.h @@ -0,0 +1,135 @@ +/* +* 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 <http://www.gnu.org/licenses/>. +* +*/ + +#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)) + +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; + IP_Port ip_port2; + IP_Port ip_port3; +} Onion_Path; + +/* Create a new onion path. + * + * Create a new onion path out of nodes (nodes is a list of 3 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); + +/* 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, uint32_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, uint32_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, uint32_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, uint32_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, uint32_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 diff --git a/protocols/Tox/toxcore/toxcore/onion_announce.c b/protocols/Tox/toxcore/toxcore/onion_announce.c new file mode 100644 index 0000000000..dff05135c8 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/onion_announce.c @@ -0,0 +1,421 @@ +/* +* 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 <http://www.gnu.org/licenses/>. +* +*/ +#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 (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 ANNOUNCE_REQUEST_SIZE_RECV (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_MAX_PACKET_SIZE). + * + * 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 packet length on success. + */ +int create_announce_request(uint8_t *packet, uint16_t max_packet_length, 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 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)); + uint8_t temp[ANNOUNCE_REQUEST_SIZE]; + temp[0] = NET_PACKET_ANNOUNCE_REQUEST; + random_nonce(temp + 1); + + int len = encrypt_data(dest.client_id, secret_key, temp + 1, plain, sizeof(plain), + temp + 1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES); + + if ((uint32_t)len + 1 + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES != ANNOUNCE_REQUEST_SIZE) + return -1; + + memcpy(temp + 1 + crypto_box_NONCEBYTES, public_key, crypto_box_PUBLICKEYBYTES); + + return create_onion_packet(packet, max_packet_length, path, dest.ip_port, temp, sizeof(temp)); +} + +/* Create an onion data request packet in packet of max_packet_length (recommended size ONION_MAX_PACKET_SIZE). + * + * 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 create_data_request(uint8_t *packet, uint16_t max_packet_length, 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) +{ + if ((unsigned int)DATA_REQUEST_MIN_SIZE + length > ONION_MAX_DATA_SIZE) + return -1; + + uint8_t temp[DATA_REQUEST_MIN_SIZE + length]; + temp[0] = NET_PACKET_ONION_DATA_REQUEST; + memcpy(temp + 1, public_key, crypto_box_PUBLICKEYBYTES); + memcpy(temp + 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(temp + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES, random_public_key, crypto_box_PUBLICKEYBYTES); + + int len = encrypt_data(encrypt_public_key, random_secret_key, temp + 1 + crypto_box_PUBLICKEYBYTES, + data, length, temp + 1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES); + + if (1 + crypto_box_PUBLICKEYBYTES + crypto_box_NONCEBYTES + crypto_box_PUBLICKEYBYTES + (uint32_t)len != sizeof(temp)) + return -1; + + return create_onion_packet(packet, max_packet_length, path, dest, temp, sizeof(temp)); +} + +/* 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 packet[ONION_MAX_PACKET_SIZE]; + int len = create_announce_request(packet, sizeof(packet), path, dest, public_key, secret_key, ping_id, client_id, + data_public_key, sendback_data); + + 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 packet[ONION_MAX_PACKET_SIZE]; + int len = create_data_request(packet, sizeof(packet), path, dest, public_key, encrypt_public_key, nonce, data, length); + + 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) +{ + uint32_t i; + + for (i = 0; i < ONION_ANNOUNCE_MAX_ENTRIES; ++i) { + if (!is_timeout(onion_a->entries[i].time, ONION_ANNOUNCE_TIMEOUT) + && memcmp(onion_a->entries[i].public_key, public_key, crypto_box_PUBLICKEYBYTES) == 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); + + uint32_t 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, uint32_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 (memcmp(ping_id1, plain, ONION_PING_ID_SIZE) == 0 || 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]; + uint32_t 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 (memcmp(onion_a->entries[index].public_key, packet_public_key, crypto_box_PUBLICKEYBYTES) == 0 + && memcmp(onion_a->entries[index].data_public_key, data_public_key, crypto_box_PUBLICKEYBYTES) != 0) { + pl[0] = 0; + 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, uint32_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); +} diff --git a/protocols/Tox/toxcore/toxcore/onion_announce.h b/protocols/Tox/toxcore/toxcore/onion_announce.h new file mode 100644 index 0000000000..d31355e5fa --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/onion_announce.h @@ -0,0 +1,143 @@ +/* +* 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 <http://www.gnu.org/licenses/>. +* +*/ + +#ifndef ONION_ANNOUNCE_H +#define ONION_ANNOUNCE_H + +#include "onion.h" + +#define ONION_ANNOUNCE_MAX_ENTRIES 48 +#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_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_MAX_PACKET_SIZE). + * + * 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 packet length on success. + */ +int create_announce_request(uint8_t *packet, uint16_t max_packet_length, 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 an onion data request packet in packet of max_packet_length (recommended size ONION_MAX_PACKET_SIZE). + * + * 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 create_data_request(uint8_t *packet, uint16_t max_packet_length, 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); + +/* 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 diff --git a/protocols/Tox/toxcore/toxcore/onion_client.c b/protocols/Tox/toxcore/toxcore/onion_client.c new file mode 100644 index 0000000000..96831beb95 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/onion_client.c @@ -0,0 +1,1155 @@ +/* +* 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 <http://www.gnu.org/licenses/>. +* +*/ +#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 + + +/* + * 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) +{ + uint32_t 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; + } + + if (ipport_equal(&onion_paths->paths[i].ip_port1, &nodes[0].ip_port)) { + return i; + } + } + + return -1; +} + +/* Create a new path or use an old suitable one (if pathnum is valid) + * or a rondom 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 DHT *dht, Onion_Client_Paths *onion_paths, uint32_t pathnum, Onion_Path *path) +{ + if (pathnum >= NUMBER_ONION_PATHS) + pathnum = rand() % NUMBER_ONION_PATHS; + + if (is_timeout(onion_paths->last_path_success[pathnum], ONION_PATH_TIMEOUT) + || is_timeout(onion_paths->path_creation_time[pathnum], ONION_PATH_MAX_LIFETIME)) { + Node_format nodes[3]; + + if (random_nodes_path(dht, nodes, 3) != 3) + return -1; + + int n = is_path_used(onion_paths, nodes); + + if (n == -1) { + if (create_onion_path(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(); + } else { + pathnum = n; + } + } + + memcpy(path, &onion_paths->paths[pathnum], sizeof(Onion_Path)); + return 0; +} + +/* Set path timeouts, return the path number. + * + */ +static uint32_t set_path_timeouts(Onion_Client *onion_c, uint32_t num, IP_Port source) +{ + if (num > onion_c->num_friends) + return -1; + + Onion_Client_Paths *onion_paths; + + if (num == 0) { + onion_paths = &onion_c->onion_paths; + } else { + onion_paths = &onion_c->friends_list[num - 1].onion_paths; + } + + uint32_t i; + + for (i = 0; i < NUMBER_ONION_PATHS; ++i) { + if (ipport_equal(&onion_paths->paths[i].ip_port1, &source)) { + onion_paths->last_path_success[i] = unix_time(); + return i; + } + } + + 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, IP_Port ip_port, const uint8_t *data, uint32_t length) +{ + if (ip_port.ip.family == AF_INET || ip_port.ip.family == AF_INET6) { + if ((uint32_t)sendpacket(onion_c->net, ip_port, data, length) != length) + return -1; + + return 0; + } else { + return -1; //TODO: TCP + } +} + +/* 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, + uint64_t *sendback) +{ + uint8_t data[sizeof(uint32_t) + crypto_box_PUBLICKEYBYTES + sizeof(IP_Port)]; + 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)); + *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) +{ + uint64_t sback; + memcpy(&sback, sendback, sizeof(uint64_t)); + uint8_t data[sizeof(uint32_t) + crypto_box_PUBLICKEYBYTES + sizeof(IP_Port)]; + + 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)); + 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; + + if (new_sendback(onion_c, num, dest_pubkey, dest, &sendback) == -1) + return -1; + + uint8_t zero_ping_id[ONION_PING_ID_SIZE] = {0}; + + if (ping_id == NULL) + ping_id = zero_ping_id; + + Onion_Path path; + + Node_format dest_node; + dest_node.ip_port = dest; + memcpy(dest_node.client_id, dest_pubkey, crypto_box_PUBLICKEYBYTES); + + if (num == 0) { + if (random_path(onion_c->dht, &onion_c->onion_paths, pathnum, &path) == -1) + return -1; + + uint8_t packet[ONION_MAX_PACKET_SIZE]; + int len = create_announce_request(packet, sizeof(packet), &path, dest_node, 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); + + if (len == -1) { + return -1; + } + + return send_onion_packet_tcp_udp(onion_c, path.ip_port1, packet, len); + } else { + if (random_path(onion_c->dht, &onion_c->friends_list[num - 1].onion_paths, pathnum, &path) == -1) + return -1; + + uint8_t packet[ONION_MAX_PACKET_SIZE]; + int len = create_announce_request(packet, sizeof(packet), &path, dest_node, + 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_client_id, zero_ping_id, sendback); + + if (len == -1) { + return -1; + } + + return send_onion_packet_tcp_udp(onion_c, path.ip_port1, packet, 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.client_id, entry2.client_id); + + 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, IP_Port source) +{ + if (num > onion_c->num_friends) + return -1; + + Onion_Node *list_nodes = NULL; + uint8_t *reference_id = NULL; + + if (num == 0) { + list_nodes = onion_c->clients_announce_list; + reference_id = onion_c->c->self_public_key; + + if (is_stored && memcmp(pingid_or_key, onion_c->temp_public_key, crypto_box_PUBLICKEYBYTES) != 0) { + is_stored = 0; + } + + } else { + list_nodes = onion_c->friends_list[num - 1].clients_list; + reference_id = onion_c->friends_list[num - 1].real_client_id; + } + + memcpy(cmp_public_key, reference_id, crypto_box_PUBLICKEYBYTES); + qsort(list_nodes, MAX_ONION_CLIENTS, sizeof(Onion_Node), cmp_entry); + + int index = -1; + uint32_t i; + + if (is_timeout(list_nodes[0].timestamp, ONION_NODE_TIMEOUT) + || id_closest(reference_id, list_nodes[0].client_id, public_key) == 2) { + index = 0; + } + + for (i = 0; i < MAX_ONION_CLIENTS; ++i) { + if (memcmp(list_nodes[i].client_id, public_key, crypto_box_PUBLICKEYBYTES) == 0) { + index = i; + break; + } + } + + if (index == -1) + return 0; + + memcpy(list_nodes[index].client_id, public_key, CLIENT_ID_SIZE); + list_nodes[index].ip_port = ip_port; + + if (is_stored) { + 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(); + list_nodes[index].last_pinged = 0; + list_nodes[index].path_used = set_path_timeouts(onion_c, num, source); + return 0; +} + +static int good_to_ping(Last_Pinged *last_pinged, uint8_t *last_pinged_index, const uint8_t *client_id) +{ + uint32_t i; + + for (i = 0; i < MAX_STORED_PINGED_NODES; ++i) { + if (!is_timeout(last_pinged[i].timestamp, MIN_NODE_PING_TIME)) + if (memcmp(last_pinged[i].client_id, client_id, crypto_box_PUBLICKEYBYTES) == 0) + return 0; + } + + memcpy(last_pinged[*last_pinged_index % MAX_STORED_PINGED_NODES].client_id, client_id, 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; + + 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; + 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_client_id; + last_pinged = onion_c->friends_list[num - 1].last_pinged; + last_pinged_index = &onion_c->friends_list[num - 1].last_pinged_index; + } + + uint32_t 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].client_id, nodes[i].client_id) == 2) { + /* check if node is already in list. */ + for (j = 0; j < MAX_ONION_CLIENTS; ++j) { + if (memcmp(list_nodes[j].client_id, nodes[i].client_id, crypto_box_PUBLICKEYBYTES) == 0) { + break; + } + } + + if (j == MAX_ONION_CLIENTS && good_to_ping(last_pinged, last_pinged_index, nodes[i].client_id)) { + client_send_announce_request(onion_c, num, nodes[i].ip_port, nodes[i].client_id, NULL, ~0); + } + } + } + + return 0; +} + +static int handle_announce_response(void *object, IP_Port source, const uint8_t *packet, uint32_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 num = check_sendback(onion_c, packet + 1, public_key, &ip_port); + + 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, source) == -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; + } + + 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, uint32_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 FAKEID_DATA_ID 156 +#define FAKEID_DATA_MIN_LENGTH (1 + sizeof(uint64_t) + crypto_box_PUBLICKEYBYTES) +#define FAKEID_DATA_MAX_LENGTH (FAKEID_DATA_MIN_LENGTH + sizeof(Node_format)*MAX_SENT_NODES) +static int handle_fakeid_announce(void *object, const uint8_t *source_pubkey, const uint8_t *data, uint32_t length) +{ + Onion_Client *onion_c = object; + + if (length < FAKEID_DATA_MIN_LENGTH) + return 1; + + if (length > FAKEID_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; + onion_set_friend_DHT_pubkey(onion_c, friend_num, data + 1 + sizeof(uint64_t), current_time_monotonic()); + onion_c->friends_list[friend_num].last_seen = unix_time(); + + uint16_t len_nodes = length - FAKEID_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].client_id, onion_c->friends_list[friend_num].fake_client_id); + } 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].client_id); + } + } + } + } + + return 0; +} +/* 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(const Onion_Client *onion_c, int friend_num, const uint8_t *data, uint32_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; + + 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_client_id, onion_c->c->self_secret_key, nonce, data, + length, packet + crypto_box_PUBLICKEYBYTES); + + if ((uint32_t)len + crypto_box_PUBLICKEYBYTES != sizeof(packet)) + return -1; + + uint32_t i, good_nodes[MAX_ONION_CLIENTS], num_good = 0, num_nodes = 0; + Onion_Path path[MAX_ONION_CLIENTS]; + 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) { + if (random_path(onion_c->dht, &onion_c->friends_list[friend_num].onion_paths, ~0, &path[num_good]) == -1) + continue; + + good_nodes[num_good] = i; + ++num_good; + } + } + + if (num_good < (num_nodes / 4) + 1) + return -1; + + uint32_t good = 0; + + for (i = 0; i < num_good; ++i) { + uint8_t o_packet[ONION_MAX_PACKET_SIZE]; + len = create_data_request(o_packet, sizeof(o_packet), &path[i], list_nodes[good_nodes[i]].ip_port, + onion_c->friends_list[friend_num].real_client_id, 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[i].ip_port1, o_packet, len) == 0) + ++good; + } + + return good; +} + +/* Try to send the fakeid 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_fakeid(const Onion_Client *onion_c, int friend_num, const uint8_t *data, uint32_t length) +{ + if ((uint32_t)friend_num >= onion_c->num_friends) + return -1; + + if (!onion_c->friends_list[friend_num].is_fake_clientid) + 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_client_id, 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].fake_client_id, temp, sizeof(temp), FAKEID_DATA_ID); + + if (len == -1) + return -1; + + return route_tofriend(onion_c->dht, onion_c->friends_list[friend_num].fake_client_id, packet, len); +} + +static int handle_dht_fakeid(void *object, IP_Port source, const uint8_t *source_pubkey, const uint8_t *packet, + uint32_t length) +{ + Onion_Client *onion_c = object; + + if (length < FAKEID_DATA_MIN_LENGTH + DATA_IN_RESPONSE_MIN_SIZE + crypto_box_NONCEBYTES) + return 1; + + if (length > FAKEID_DATA_MAX_LENGTH + DATA_IN_RESPONSE_MIN_SIZE + crypto_box_NONCEBYTES) + return 1; + + uint8_t plain[FAKEID_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 ((uint32_t)len != length - (DATA_IN_RESPONSE_MIN_SIZE + crypto_box_NONCEBYTES)) + return 1; + + if (memcmp(source_pubkey, plain + 1 + sizeof(uint64_t), crypto_box_PUBLICKEYBYTES) != 0) + return 1; + + return handle_fakeid_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_fakeid_announce(const 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[FAKEID_DATA_MAX_LENGTH]; + data[0] = FAKEID_DATA_ID; + 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 + FAKEID_DATA_MIN_LENGTH, FAKEID_DATA_MAX_LENGTH - FAKEID_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, FAKEID_DATA_MIN_LENGTH + nodes_len); + + if (onion_dht_both != 0) + num2 = send_dht_fakeid(onion_c, friend_num, data, FAKEID_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 *client_id) +{ + uint32_t i; + + for (i = 0; i < onion_c->num_friends; ++i) { + if (onion_c->friends_list[i].status == 0) + continue; + + if (memcmp(client_id, onion_c->friends_list[i].real_client_id, crypto_box_PUBLICKEYBYTES) == 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 *client_id) +{ + int num = onion_friend_num(onion_c, client_id); + + if (num != -1) + return num; + + uint32_t 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_client_id, client_id, 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].is_fake_clientid) + DHT_delfriend(onion_c->dht, onion_c->friends_list[friend_num].fake_client_id); + + memset(&(onion_c->friends_list[friend_num]), 0, sizeof(Onion_Friend)); + uint32_t 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 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, uint64_t timestamp) +{ + 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].fake_client_id_timestamp >= timestamp) + return -1; + + if (onion_c->friends_list[friend_num].is_fake_clientid) { + if (memcmp(dht_key, onion_c->friends_list[friend_num].fake_client_id, crypto_box_PUBLICKEYBYTES) == 0) { + return -1; + } + + DHT_delfriend(onion_c->dht, onion_c->friends_list[friend_num].fake_client_id); + } + + if (DHT_addfriend(onion_c->dht, dht_key) == 1) { + return -1; + } + + onion_c->friends_list[friend_num].last_seen = unix_time(); + onion_c->friends_list[friend_num].is_fake_clientid = 1; + onion_c->friends_list[friend_num].fake_client_id_timestamp = timestamp; + memcpy(onion_c->friends_list[friend_num].fake_client_id, dht_key, crypto_box_PUBLICKEYBYTES); + + return 0; +} + +/* Copy friends DHT public key into dht_key. + * + * return 0 on failure (no key copied). + * return timestamp on success (key copied). + */ +uint64_t 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].is_fake_clientid) + return 0; + + memcpy(dht_key, onion_c->friends_list[friend_num].fake_client_id, crypto_box_PUBLICKEYBYTES); + return onion_c->friends_list[friend_num].fake_client_id_timestamp; +} + +/* Get the ip of friend friendnum and put it in ip_port + * + * return -1, -- if client_id does NOT refer to a friend + * return 0, -- if client_id refers to a friend and we failed to find the friend (yet) + * return 1, ip if client_id 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; + + return 0; +} + + +#define ANNOUNCE_FRIEND (ONION_NODE_PING_INTERVAL * 3) +#define FRIEND_ONION_NODE_TIMEOUT (ONION_NODE_TIMEOUT * 3) + +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; + + uint32_t 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, ANNOUNCE_FRIEND)) { + if (client_send_announce_request(onion_c, friendnum + 1, list_nodes[i].ip_port, list_nodes[i].client_id, 0, ~0) == 0) { + list_nodes[i].last_pinged = unix_time(); + } + } + } + + if (count != MAX_ONION_CLIENTS) { + if (count < (uint32_t)rand() % MAX_ONION_CLIENTS) { + Node_format nodes_list[MAX_SENT_NODES]; + uint32_t num_nodes = get_close_nodes(onion_c->dht, onion_c->friends_list[friendnum].real_client_id, nodes_list, + (rand() % 2) ? AF_INET : AF_INET6, 1, 0); + + for (i = 0; i < num_nodes; ++i) + client_send_announce_request(onion_c, friendnum + 1, nodes_list[i].ip_port, nodes_list[i].client_id, 0, ~0); + } + } + + /* send packets to friend telling them our fake DHT id. */ + if (is_timeout(onion_c->friends_list[friendnum].last_fakeid_onion_sent, ONION_FAKEID_INTERVAL)) + if (send_fakeid_announce(onion_c, friendnum, 0) >= 1) + onion_c->friends_list[friendnum].last_fakeid_onion_sent = unix_time(); + + if (is_timeout(onion_c->friends_list[friendnum].last_fakeid_dht_sent, DHT_FAKEID_INTERVAL)) + if (send_fakeid_announce(onion_c, friendnum, 1) >= 1) + onion_c->friends_list[friendnum].last_fakeid_dht_sent = unix_time(); + + } +} + +/* Timeout before which a peer is considered dead and removed from the DHT search. */ +#define DEAD_ONION_TIMEOUT (10 * 60) + +static void cleanup_friend(Onion_Client *onion_c, uint16_t friendnum) +{ + if (friendnum >= onion_c->num_friends) + return; + + if (onion_c->friends_list[friendnum].status == 0) + return; + + if (onion_c->friends_list[friendnum].is_fake_clientid && !onion_c->friends_list[friendnum].is_online + && is_timeout(onion_c->friends_list[friendnum].last_seen, DEAD_ONION_TIMEOUT)) { + onion_c->friends_list[friendnum].is_fake_clientid = 0; + DHT_delfriend(onion_c->dht, onion_c->friends_list[friendnum].fake_client_id); + } +} + +/* 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 10 +#define ANNOUNCE_INTERVAL_ANNOUNCED ONION_NODE_PING_INTERVAL + +static void do_announce(Onion_Client *onion_c) +{ + uint32_t i, count = 0; + Onion_Node *list_nodes = onion_c->clients_announce_list; + + for (i = 0; i < MAX_ONION_CLIENTS; ++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; + } + + uint32_t interval = ANNOUNCE_INTERVAL_NOT_ANNOUNCED; + + if (list_nodes[i].is_stored) { + 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].client_id, + list_nodes[i].ping_id, list_nodes[i].path_used) == 0) { + list_nodes[i].last_pinged = unix_time(); + } + } + } + + if (count != MAX_ONION_CLIENTS) { + if (count < (uint32_t)rand() % MAX_ONION_CLIENTS) { + Node_format nodes_list[MAX_SENT_NODES]; + uint32_t num_nodes = get_close_nodes(onion_c->dht, onion_c->c->self_public_key, nodes_list, + (rand() % 2) ? AF_INET : AF_INET6, 1, 0); + + for (i = 0; i < num_nodes; ++i) { + client_send_announce_request(onion_c, 0, nodes_list[i].ip_port, nodes_list[i].client_id, 0, ~0); + } + } + } +} + +void do_onion_client(Onion_Client *onion_c) +{ + uint32_t i; + + if (onion_c->last_run == unix_time()) + return; + + do_announce(onion_c); + + for (i = 0; i < onion_c->num_friends; ++i) { + do_friend(onion_c, i); + cleanup_friend(onion_c, i); + } + + 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, FAKEID_DATA_ID, &handle_fakeid_announce, onion_c); + cryptopacket_registerhandler(onion_c->dht, FAKEID_DATA_ID, &handle_dht_fakeid, 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, FAKEID_DATA_ID, NULL, NULL); + cryptopacket_registerhandler(onion_c->dht, FAKEID_DATA_ID, NULL, NULL); + memset(onion_c, 0, sizeof(Onion_Client)); + free(onion_c); +} diff --git a/protocols/Tox/toxcore/toxcore/onion_client.h b/protocols/Tox/toxcore/toxcore/onion_client.h new file mode 100644 index 0000000000..bf891e7a5e --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/onion_client.h @@ -0,0 +1,226 @@ +/* +* 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 <http://www.gnu.org/licenses/>. +* +*/ + +#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 ONION_NODE_PING_INTERVAL 30 +#define ONION_NODE_TIMEOUT (ONION_NODE_PING_INTERVAL * 4) + +/* The interval in seconds at which to tell our friends where we are */ +#define ONION_FAKEID_INTERVAL 30 +#define DHT_FAKEID_INTERVAL 20 + +#define NUMBER_ONION_PATHS 3 + +/* The timeout the first time the path is added and + then for all the next consecutive times */ +#define ONION_PATH_FIRST_TIMEOUT 5 +#define ONION_PATH_TIMEOUT 30 +#define ONION_PATH_MAX_LIFETIME 600 + +#define MAX_STORED_PINGED_NODES 9 +#define MIN_NODE_PING_TIME 10 + +typedef struct { + uint8_t client_id[CLIENT_ID_SIZE]; + 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 path_creation_time[NUMBER_ONION_PATHS]; +} Onion_Client_Paths; + +typedef struct { + uint8_t client_id[CLIENT_ID_SIZE]; + 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 is_fake_clientid; /* 0 if we don't know the fake client id of the other 1 if we do. */ + uint64_t fake_client_id_timestamp; + uint8_t fake_client_id[crypto_box_PUBLICKEYBYTES]; + uint8_t real_client_id[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_fakeid_onion_sent; + uint64_t last_fakeid_dht_sent; + + uint64_t last_noreplay; + + uint64_t last_seen; + + Onion_Client_Paths onion_paths; + + 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; +} Onion_Friend; + +typedef int (*oniondata_handler_callback)(void *object, const uint8_t *source_pubkey, const uint8_t *data, + uint32_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]; + + Onion_Client_Paths onion_paths; + + uint8_t secret_symmetric_key[crypto_box_KEYBYTES]; + uint64_t last_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]; + + Ping_Array announce_ping_array; + uint8_t last_pinged_index; + struct { + oniondata_handler_callback function; + void *object; + } Onion_Data_Handlers[256]; +} Onion_Client; + +/* 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 *client_id); + +/* 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 *client_id); + +/* 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 client_id does NOT refer to a friend + * return 0, -- if client_id refers to a friend and we failed to find the friend (yet) + * return 1, ip if client_id 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 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, uint64_t timestamp); + +/* Copy friends DHT public key into dht_key. + * + * return 0 on failure (no key copied). + * return timestamp on success (key copied). + */ +uint64_t 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(const Onion_Client *onion_c, int friend_num, const uint8_t *data, uint32_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); + +#endif diff --git a/protocols/Tox/toxcore/toxcore/ping.c b/protocols/Tox/toxcore/toxcore/ping.c new file mode 100644 index 0000000000..e00036af51 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/ping.c @@ -0,0 +1,343 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdint.h> + +#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 8 + +/* Ping newly announced nodes to ping per TIME_TO_PING seconds*/ +#define TIME_TO_PING 8 + + +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 + CLIENT_ID_SIZE + crypto_box_NONCEBYTES + PING_PLAIN_SIZE + crypto_box_MACBYTES) +#define PING_DATA_SIZE (CLIENT_ID_SIZE + sizeof(IP_Port)) + +int send_ping_request(PING *ping, IP_Port ipp, const uint8_t *client_id) +{ + uint8_t pk[DHT_PING_SIZE]; + int rc; + uint64_t ping_id; + + if (id_equal(client_id, 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, client_id); + // Generate random ping_id. + uint8_t data[PING_DATA_SIZE]; + id_copy(data, client_id); + memcpy(data + CLIENT_ID_SIZE, &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 + CLIENT_ID_SIZE); // Generate new nonce + + + rc = encrypt_data_symmetric(shared_key, + pk + 1 + CLIENT_ID_SIZE, + ping_plain, sizeof(ping_plain), + pk + 1 + CLIENT_ID_SIZE + 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 *client_id, uint64_t ping_id, + uint8_t *shared_encryption_key) +{ + uint8_t pk[DHT_PING_SIZE]; + int rc; + + if (id_equal(client_id, 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 + CLIENT_ID_SIZE); // Generate new nonce + + // Encrypt ping_id using recipient privkey + rc = encrypt_data_symmetric(shared_encryption_key, + pk + 1 + CLIENT_ID_SIZE, + ping_plain, sizeof(ping_plain), + pk + 1 + CLIENT_ID_SIZE + 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, uint32_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 + CLIENT_ID_SIZE, + packet + 1 + CLIENT_ID_SIZE + 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, uint32_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 + CLIENT_ID_SIZE, + packet + 1 + CLIENT_ID_SIZE + 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 + CLIENT_ID_SIZE, sizeof(IP_Port)); + + if (!ipport_equal(&ipp, &source)) + return 1; + + addto_lists(dht, source, packet + 1); + return 0; +} + +/* Check if client_id 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, uint32_t length, const uint8_t *client_id, IP_Port ip_port) +{ + uint32_t i; + + for (i = 0; i < length; ++i) { + if (id_equal(list[i].client_id, client_id)) { + 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 client_id 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 *client_id, IP_Port ip_port) +{ + if (!ip_isset(&ip_port.ip)) + return -1; + + if (in_list(ping->dht->close_clientlist, LCLIENT_LIST, client_id, ip_port)) + return -1; + + uint32_t i; + + for (i = 0; i < MAX_TO_PING; ++i) { + if (!ip_isset(&ping->to_ping[i].ip_port.ip)) { + memcpy(ping->to_ping[i].client_id, client_id, CLIENT_ID_SIZE); + ipport_copy(&ping->to_ping[i].ip_port, &ip_port); + return 0; + } + + if (memcmp(ping->to_ping[i].client_id, client_id, CLIENT_ID_SIZE) == 0) { + return -1; + } + } + + uint32_t r = rand(); + + for (i = 0; i < MAX_TO_PING; ++i) { + if (id_closest(ping->dht->self_public_key, ping->to_ping[(i + r) % MAX_TO_PING].client_id, client_id) == 2) { + memcpy(ping->to_ping[(i + r) % MAX_TO_PING].client_id, client_id, CLIENT_ID_SIZE); + ipport_copy(&ping->to_ping[(i + r) % MAX_TO_PING].ip_port, &ip_port); + 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; + + ping->last_to_ping = unix_time(); + uint32_t i; + + for (i = 0; i < MAX_TO_PING; ++i) { + if (!ip_isset(&ping->to_ping[i].ip_port.ip)) + return; + + send_ping_request(ping, ping->to_ping[i].ip_port, ping->to_ping[i].client_id); + ip_reset(&ping->to_ping[i].ip_port.ip); + } +} + + +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); +} diff --git a/protocols/Tox/toxcore/toxcore/ping.h b/protocols/Tox/toxcore/toxcore/ping.h new file mode 100644 index 0000000000..c19c912aec --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/ping.h @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#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 client_id 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 *client_id, 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 *client_id); + +#endif /* __PING_H__ */ diff --git a/protocols/Tox/toxcore/toxcore/ping_array.c b/protocols/Tox/toxcore/toxcore/ping_array.c new file mode 100644 index 0000000000..93dade0595 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/ping_array.c @@ -0,0 +1,162 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#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; +} + diff --git a/protocols/Tox/toxcore/toxcore/ping_array.h b/protocols/Tox/toxcore/toxcore/ping_array.h new file mode 100644 index 0000000000..364ad83320 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/ping_array.h @@ -0,0 +1,75 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ +#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 diff --git a/protocols/Tox/toxcore/toxcore/tox.c b/protocols/Tox/toxcore/toxcore/tox.c new file mode 100644 index 0000000000..3c5db1e314 --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/tox.c @@ -0,0 +1,860 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "Messenger.h" +#include "logger.h" + +#define __TOX_DEFINED__ +typedef struct Messenger Tox; + +#include "tox.h" + +/* + * returns a FRIEND_ADDRESS_SIZE byte address to give to others. + * Format: [client_id (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)] + * + */ +void tox_get_address(const Tox *tox, uint8_t *address) +{ + const Messenger *m = tox; + getaddress(m, 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 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_UNKNOWN for unknown error. + * 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 tox_add_friend(Tox *tox, const uint8_t *address, const uint8_t *data, uint16_t length) +{ + Messenger *m = tox; + return m_addfriend(m, address, data, length); +} + +/* Add a friend without sending a friendrequest. + * + * return the friend number if success. + * return -1 if failure. + */ +int32_t tox_add_friend_norequest(Tox *tox, const uint8_t *client_id) +{ + Messenger *m = tox; + return m_addfriend_norequest(m, client_id); +} + +/* return the friend number associated to that client id. + * return -1 if no such friend. + */ +int32_t tox_get_friend_number(const Tox *tox, const uint8_t *client_id) +{ + const Messenger *m = tox; + return getfriend_id(m, client_id); +} + +/* Copies the public key associated to that friend id into client_id buffer. + * Make sure that client_id is of size CLIENT_ID_SIZE. + * + * return 0 if success. + * return -1 if failure. + */ +int tox_get_client_id(const Tox *tox, int32_t friendnumber, uint8_t *client_id) +{ + const Messenger *m = tox; + return getclient_id(m, friendnumber, client_id); +} + +/* Remove a friend. */ +int tox_del_friend(Tox *tox, int32_t friendnumber) +{ + Messenger *m = tox; + return m_delfriend(m, friendnumber); +} + +/* Checks friend's connecting status. + * + * return 1 if friend is connected to us (Online). + * return 0 if friend is not connected to us (Offline). + * return -1 on failure. + */ +int tox_get_friend_connection_status(const Tox *tox, int32_t friendnumber) +{ + const Messenger *m = tox; + return m_get_friend_connectionstatus(m, friendnumber); +} + +/* Checks if there exists a friend with given friendnumber. + * + * return 1 if friend exists. + * return 0 if friend doesn't exist. + */ +int tox_friend_exists(const Tox *tox, int32_t friendnumber) +{ + const Messenger *m = tox; + return m_friend_exists(m, friendnumber); +} + +/* Send a text chat message to an online friend. + * return the message id if packet was successfully put into the send queue. + * return 0 if it was not. + * + * You will want to retain the return value, it will be passed to your read_receipt callback + * if one is received. + * m_sendmessage_withid will send a message with the id of your choosing, + * however we can generate an id for you by calling plain m_sendmessage. + */ +uint32_t tox_send_message(Tox *tox, int32_t friendnumber, const uint8_t *message, uint32_t length) +{ + Messenger *m = tox; + return m_sendmessage(m, friendnumber, message, length); +} + +uint32_t tox_send_message_withid(Tox *tox, int32_t friendnumber, uint32_t theid, const uint8_t *message, + uint32_t length) +{ + Messenger *m = tox; + return m_sendmessage_withid(m, friendnumber, theid, message, length); +} + +/* Send an action to an online friend. + * + * return the message id if packet was successfully put into the send queue. + * return 0 if it was not. + * + * You will want to retain the return value, it will be passed to your read_receipt callback + * if one is received. + * m_sendaction_withid will send an action message with the id of your choosing, + * however we can generate an id for you by calling plain m_sendaction. + */ +uint32_t tox_send_action(Tox *tox, int32_t friendnumber, const uint8_t *action, uint32_t length) +{ + Messenger *m = tox; + return m_sendaction(m, friendnumber, action, length); +} + +uint32_t tox_send_action_withid(Tox *tox, int32_t friendnumber, uint32_t theid, const uint8_t *action, uint32_t length) +{ + Messenger *m = tox; + return m_sendaction_withid(m, friendnumber, theid, action, 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 tox_set_name(Tox *tox, const uint8_t *name, uint16_t length) +{ + Messenger *m = tox; + return setname(m, name, length); +} + +/* Get your nickname. + * m - The messenger context to use. + * name - Pointer to a string for the name. (must be at least MAX_NAME_LENGTH) + * + * return length of the name. + * return 0 on error. + */ +uint16_t tox_get_self_name(const Tox *tox, uint8_t *name) +{ + const Messenger *m = tox; + return getself_name(m, 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 (with the NULL terminator) if success. + * return -1 if failure. + */ +int tox_get_name(const Tox *tox, int32_t friendnumber, uint8_t *name) +{ + const Messenger *m = tox; + return getname(m, friendnumber, name); +} + +/* returns the length of name on success. + * returns -1 on failure. + */ +int tox_get_name_size(const Tox *tox, int32_t friendnumber) +{ + const Messenger *m = tox; + return m_get_name_size(m, friendnumber); +} + +int tox_get_self_name_size(const Tox *tox) +{ + const Messenger *m = tox; + return m_get_self_name_size(m); +} + +/* Set our user status; + * you are responsible for freeing status after. + * + * return 0 on success, -1 on failure. + */ +int tox_set_status_message(Tox *tox, const uint8_t *status, uint16_t length) +{ + Messenger *m = tox; + return m_set_statusmessage(m, status, length); +} + +int tox_set_user_status(Tox *tox, uint8_t status) +{ + Messenger *m = tox; + return m_set_userstatus(m, status); +} + +/* returns the length of status message on success. + * returns -1 on failure. + */ +int tox_get_status_message_size(const Tox *tox, int32_t friendnumber) +{ + const Messenger *m = tox; + return m_get_statusmessage_size(m, friendnumber); +} + +int tox_get_self_status_message_size(const Tox *tox) +{ + const Messenger *m = tox; + return m_get_self_statusmessage_size(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. + */ +int tox_get_status_message(const Tox *tox, int32_t friendnumber, uint8_t *buf, uint32_t maxlen) +{ + const Messenger *m = tox; + return m_copy_statusmessage(m, friendnumber, buf, maxlen); +} + +int tox_get_self_status_message(const Tox *tox, uint8_t *buf, uint32_t maxlen) +{ + const Messenger *m = tox; + return m_copy_self_statusmessage(m, buf, maxlen); +} + +/* 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 tox_get_user_status(const Tox *tox, int32_t friendnumber) +{ + const Messenger *m = tox; + return m_get_userstatus(m, friendnumber); +} + +uint8_t tox_get_self_user_status(const Tox *tox) +{ + const Messenger *m = tox; + return m_get_self_userstatus(m); +} + +/* returns timestamp of last time friendnumber was seen online, or 0 if never seen. + * returns -1 on error. + */ +uint64_t tox_get_last_online(const Tox *tox, int32_t friendnumber) +{ + const Messenger *m = tox; + return m_get_last_online(m, 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 tox_set_user_is_typing(Tox *tox, int32_t friendnumber, uint8_t is_typing) +{ + Messenger *m = tox; + return m_set_usertyping(m, friendnumber, is_typing); +} + +/* Get the typing status of a friend. + * + * returns 0 if friend is not typing. + * returns 1 if friend is typing. + */ +uint8_t tox_get_is_typing(const Tox *tox, int32_t friendnumber) +{ + const Messenger *m = tox; + return m_get_istyping(m, friendnumber); +} + +/* Sets whether we send read receipts for friendnumber. + * This function is not lazy, and it will fail if yesno is not (0 or 1). + */ +void tox_set_sends_receipts(Tox *tox, int32_t friendnumber, int yesno) +{ + Messenger *m = tox; + m_set_sends_receipts(m, friendnumber, yesno); +} + +/* 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 tox_count_friendlist(const Tox *tox) +{ + const Messenger *m = tox; + return count_friendlist(m); +} + +/* Return the number of online friends in the instance m. */ +uint32_t tox_get_num_online_friends(const Tox *tox) +{ + const Messenger *m = tox; + return get_num_online_friends(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 tox_get_friendlist(const Tox *tox, int32_t *out_list, uint32_t list_size) +{ + const Messenger *m = tox; + return copy_friendlist(m, out_list, list_size); +} + +/* Set the function that will be executed when a friend request is received. + * Function format is function(uint8_t * public_key, uint8_t * data, uint16_t length) + */ +void tox_callback_friend_request(Tox *tox, void (*function)(Tox *tox, const uint8_t *, const uint8_t *, uint16_t, + void *), void *userdata) +{ + Messenger *m = tox; + m_callback_friendrequest(m, function, userdata); +} + + +/* Set the function that will be executed when a message from a friend is received. + * Function format is: function(int32_t friendnumber, uint8_t * message, uint32_t length) + */ +void tox_callback_friend_message(Tox *tox, void (*function)(Messenger *tox, int32_t, const uint8_t *, uint16_t, void *), + void *userdata) +{ + Messenger *m = tox; + m_callback_friendmessage(m, function, userdata); +} + +/* Set the function that will be executed when an action from a friend is received. + * function format is: function(int32_t friendnumber, uint8_t * action, uint32_t length) + */ +void tox_callback_friend_action(Tox *tox, void (*function)(Messenger *tox, int32_t, const uint8_t *, uint16_t, void *), + void *userdata) +{ + Messenger *m = tox; + m_callback_action(m, function, userdata); +} + +/* Set the callback for name changes. + * function(int32_t friendnumber, uint8_t *newname, uint16_t length) + * You are not responsible for freeing newname. + */ +void tox_callback_name_change(Tox *tox, void (*function)(Messenger *tox, int32_t, const uint8_t *, uint16_t, void *), + void *userdata) +{ + Messenger *m = tox; + m_callback_namechange(m, function, userdata); +} + +/* Set the callback for status message changes. + * function(int32_t friendnumber, uint8_t *newstatus, uint16_t length) + * You are not responsible for freeing newstatus. + */ +void tox_callback_status_message(Tox *tox, void (*function)(Messenger *tox, int32_t, const uint8_t *, uint16_t, void *), + void *userdata) +{ + Messenger *m = tox; + m_callback_statusmessage(m, function, userdata); +} + +/* Set the callback for status type changes. + * function(int32_t friendnumber, USERSTATUS kind) + */ +void tox_callback_user_status(Tox *tox, void (*function)(Messenger *tox, int32_t, uint8_t, void *), + void *userdata) +{ + Messenger *m = tox; + m_callback_userstatus(m, function, userdata); +} + +/* Set the callback for typing changes. + * function (int32_t friendnumber, uint8_t is_typing) + */ +void tox_callback_typing_change(Tox *tox, void (*function)(Messenger *tox, int32_t, uint8_t, void *), void *userdata) +{ + Messenger *m = tox; + m_callback_typingchange(m, function, userdata); +} + +/* Set the callback for read receipts. + * function(int32_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 tox_callback_read_receipt(Tox *tox, void (*function)(Messenger *tox, int32_t, uint32_t, void *), void *userdata) +{ + Messenger *m = tox; + m_callback_read_receipt(m, function, userdata); +} + +/* Set the callback for connection status changes. + * function(int32_t friendnumber, uint8_t status) + * + * Status: + * 0 -- friend went offline after being previously online + * 1 -- friend went online + * + * NOTE: 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 tox_callback_connection_status(Tox *tox, void (*function)(Messenger *tox, int32_t, uint8_t, void *), + void *userdata) +{ + Messenger *m = tox; + m_callback_connectionstatus(m, function, userdata); +} + +/**********ADVANCED FUNCTIONS (If you don't know what they do you can safely ignore them.) ************/ + +/* Functions to get/set the nospam part of the id. + */ +uint32_t tox_get_nospam(const Tox *tox) +{ + const Messenger *m = tox; + return get_nospam(&(m->fr)); +} + +void tox_set_nospam(Tox *tox, uint32_t nospam) +{ + Messenger *m = tox; + set_nospam(&(m->fr), nospam); +} + +/* Copy the public and secret key from the Tox object. + public_key and secret_key must be 32 bytes big. + if the pointer is NULL, no data will be copied to it.*/ +void tox_get_keys(Tox *tox, uint8_t *public_key, uint8_t *secret_key) +{ + Messenger *m = tox; + + if (public_key) + memcpy(public_key, m->net_crypto->self_public_key, crypto_box_PUBLICKEYBYTES); + + if (secret_key) + memcpy(secret_key, m->net_crypto->self_secret_key, crypto_box_SECRETKEYBYTES); +} + +/**********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 *group_public_key, void *userdata) + */ +void tox_callback_group_invite(Tox *tox, void (*function)(Messenger *tox, int32_t, const uint8_t *, void *), + void *userdata) +{ + Messenger *m = tox; + m_callback_group_invite(m, function, userdata); +} + +/* Set the callback for group messages. + * + * Function(Tox *tox, int groupnumber, int friendgroupnumber, 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; + m_callback_group_message(m, function, userdata); +} + +/* Set the callback for group actions. + * + * Function(Tox *tox, int groupnumber, int friendgroupnumber, 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; + m_callback_group_action(m, 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; + m_callback_group_namelistchange(m, 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); +} +/* 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, 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 m_group_peername(m, groupnumber, peernumber, name); +} +/* 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, friendnumber, groupnumber); +} +/* Join a group (you need to have been invited first.) + * + * returns group number on success + * returns -1 on failure. + */ +int tox_join_groupchat(Tox *tox, int32_t friendnumber, const uint8_t *friend_group_public_key) +{ + Messenger *m = tox; + return join_groupchat(m, friendnumber, friend_group_public_key); +} + +/* 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, uint32_t length) +{ + Messenger *m = tox; + return group_message_send(m, 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, uint32_t length) +{ + Messenger *m = tox; + return group_action_send(m, groupnumber, action, length); +} + +/* 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, 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, 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); +} + +/* 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, int *out_list, uint32_t list_size) +{ + const Messenger *m = tox; + return copy_chatlist(m, out_list, list_size); +} + + +/****************FILE SENDING FUNCTIONS*****************/ + + +/* Set the callback for file send requests. + * + * Function(Tox *tox, int32_t friendnumber, uint8_t filenumber, uint64_t filesize, uint8_t *filename, uint16_t filename_length, void *userdata) + */ +void tox_callback_file_send_request(Tox *tox, void (*function)(Messenger *tox, int32_t, uint8_t, uint64_t, + const uint8_t *, uint16_t, void *), void *userdata) +{ + Messenger *m = tox; + callback_file_sendrequest(m, function, userdata); +} +/* Set the callback for file control requests. + * + * Function(Tox *tox, int32_t friendnumber, uint8_t send_receive, uint8_t filenumber, uint8_t control_type, uint8_t *data, uint16_t length, void *userdata) + * + */ +void tox_callback_file_control(Tox *tox, void (*function)(Messenger *tox, int32_t, uint8_t, uint8_t, uint8_t, + const uint8_t *, uint16_t, void *), void *userdata) +{ + Messenger *m = tox; + callback_file_control(m, function, userdata); +} +/* Set the callback for file data. + * + * Function(Tox *tox, int32_t friendnumber, uint8_t filenumber, uint8_t *data, uint16_t length, void *userdata) + * + */ +void tox_callback_file_data(Tox *tox, void (*function)(Messenger *tox, int32_t, uint8_t, const uint8_t *, + uint16_t length, void *), void *userdata) + +{ + Messenger *m = tox; + callback_file_data(m, function, userdata); +} +/* Send a file send request. + * Maximum filename length is 255 bytes. + * return file number on success + * return -1 on failure + */ +int tox_new_file_sender(Tox *tox, int32_t friendnumber, uint64_t filesize, const uint8_t *filename, + uint16_t filename_length) +{ + Messenger *m = tox; + return new_filesender(m, friendnumber, filesize, filename, filename_length); +} +/* Send a file control request. + * send_receive is 0 if we want the control packet to target a sending file, 1 if it targets a receiving file. + * + * return 0 on success + * return -1 on failure + */ +int tox_file_send_control(Tox *tox, int32_t friendnumber, uint8_t send_receive, uint8_t filenumber, uint8_t message_id, + const uint8_t *data, uint16_t length) +{ + Messenger *m = tox; + return file_control(m, friendnumber, send_receive, filenumber, message_id, data, length); +} +/* Send file data. + * + * return 0 on success + * return -1 on failure + */ +int tox_file_send_data(Tox *tox, int32_t friendnumber, uint8_t filenumber, const uint8_t *data, uint16_t length) +{ + Messenger *m = tox; + return file_data(m, friendnumber, filenumber, data, length); +} + +/* Returns the recommended/maximum size of the filedata you send with tox_file_send_data() + * + * return size on success + * return -1 on failure (currently will never return -1) + */ +int tox_file_data_size(const Tox *tox, int32_t friendnumber) +{ + return MAX_CRYPTO_DATA_SIZE - 2; +} + +/* 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 tox_file_data_remaining(const Tox *tox, int32_t friendnumber, uint8_t filenumber, uint8_t send_receive) +{ + const Messenger *m = tox; + return file_dataremaining(m, friendnumber, filenumber, send_receive); +} + +/***************END OF FILE SENDING FUNCTIONS******************/ + +/* TODO: expose this properly. */ +static int tox_add_tcp_relay(Tox *tox, const char *address, uint8_t ipv6enabled, uint16_t port, + const uint8_t *public_key) +{ + Messenger *m = tox; + 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; + add_tcp_relay(m->net_crypto, ip_port_v64, public_key); + return 1; + } else { + return 0; + } +} + +int tox_bootstrap_from_address(Tox *tox, const char *address, + uint8_t ipv6enabled, uint16_t port, const uint8_t *public_key) +{ + Messenger *m = tox; + tox_add_tcp_relay(tox, address, ipv6enabled, port, public_key); + return DHT_bootstrap_from_address(m->dht, address, ipv6enabled, port, public_key); +} + +/* return 0 if we are not connected to the DHT. + * return 1 if we are. + */ +int tox_isconnected(const Tox *tox) +{ + const Messenger *m = tox; + return DHT_isconnected(m->dht); +} + +/* Return the time in milliseconds before tox_do() should be called again + * for optimal performance. + * + * returns time (in ms) before the next tox_do() needs to be run on success. + */ +uint32_t tox_do_interval(Tox *tox) +{ + Messenger *m = tox; + return messenger_run_interval(m); +} + +/* Run this at startup. + * + * return allocated instance of tox on success. + * return 0 if there are problems. + */ +Tox *tox_new(uint8_t ipv6enabled) +{ + LOGGER_INIT(LOGGER_OUTPUT_FILE, LOGGER_LEVEL); + return new_messenger(ipv6enabled); +} + +/* Run this before closing shop. + * Free all datastructures. + */ +void tox_kill(Tox *tox) +{ + Messenger *m = tox; + kill_messenger(m); +} + +/* The main loop that needs to be run at least 20 times per second. */ +void tox_do(Tox *tox) +{ + Messenger *m = tox; + do_messenger(m); +} + +/* SAVING AND LOADING FUNCTIONS: */ + +/* return size of the messenger data (for saving). */ +uint32_t tox_size(const Tox *tox) +{ + const Messenger *m = tox; + return messenger_size(m); +} + +/* Save the messenger in data (must be allocated memory of size Messenger_size()). */ +void tox_save(const Tox *tox, uint8_t *data) +{ + const Messenger *m = tox; + messenger_save(m, data); +} + +/* Load the messenger from data of size length. */ +int tox_load(Tox *tox, const uint8_t *data, uint32_t length) +{ + Messenger *m = tox; + return messenger_load(m, data, length); +} diff --git a/protocols/Tox/toxcore/toxcore/tox.h b/protocols/Tox/toxcore/toxcore/tox.h new file mode 100644 index 0000000000..8caa01e05f --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/tox.h @@ -0,0 +1,673 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifndef TOX_H +#define TOX_H + +#include <stdint.h> + + +#ifdef __cplusplus +extern "C" { +#endif + +#define TOX_MAX_NAME_LENGTH 128 + +/* Maximum length of single messages after which they should be split. */ +#define TOX_MAX_MESSAGE_LENGTH 1368 +#define TOX_MAX_STATUSMESSAGE_LENGTH 1007 +#define TOX_CLIENT_ID_SIZE 32 + +#define TOX_FRIEND_ADDRESS_SIZE (TOX_CLIENT_ID_SIZE + sizeof(uint32_t) + sizeof(uint16_t)) + +#define TOX_ENABLE_IPV6_DEFAULT 1 + +/* Errors for m_addfriend + * FAERR - Friend Add Error + */ +enum { + TOX_FAERR_TOOLONG = -1, + TOX_FAERR_NOMESSAGE = -2, + TOX_FAERR_OWNKEY = -3, + TOX_FAERR_ALREADYSENT = -4, + TOX_FAERR_UNKNOWN = -5, + TOX_FAERR_BADCHECKSUM = -6, + TOX_FAERR_SETNEWNOSPAM = -7, + TOX_FAERR_NOMEM = -8 +}; + +/* USERSTATUS - + * Represents userstatuses someone can have. + */ +typedef enum { + TOX_USERSTATUS_NONE, + TOX_USERSTATUS_AWAY, + TOX_USERSTATUS_BUSY, + TOX_USERSTATUS_INVALID +} +TOX_USERSTATUS; + +#ifndef __TOX_DEFINED__ +#define __TOX_DEFINED__ +typedef struct Tox Tox; +#endif + +/* NOTE: Strings in Tox are all UTF-8, (This means that there is no terminating NULL character.) + * + * The exact buffer you send will be received at the other end without modification. + * + * Do not treat Tox strings as C strings. + */ + +/* return TOX_FRIEND_ADDRESS_SIZE byte address to give to others. + * format: [client_id (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)] + */ +void tox_get_address(const Tox *tox, 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 TOX_FRIEND_ADDRESS_SIZE bytes. TODO: add checksum. + * data is the data and length is the length. + * + * return the friend number if success. + * return TOX_FAERR_TOOLONG if message length is too long. + * return TOX_FAERR_NOMESSAGE if no message (message length must be >= 1 byte). + * return TOX_FAERR_OWNKEY if user's own key. + * return TOX_FAERR_ALREADYSENT if friend request already sent or already a friend. + * return TOX_FAERR_UNKNOWN for unknown error. + * return TOX_FAERR_BADCHECKSUM if bad checksum in address. + * return TOX_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 TOX_FAERR_NOMEM if increasing the friend list size fails. + */ +int32_t tox_add_friend(Tox *tox, 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 -1 if failure. + */ +int32_t tox_add_friend_norequest(Tox *tox, const uint8_t *client_id); + +/* return the friend number associated to that client id. + return -1 if no such friend */ +int32_t tox_get_friend_number(const Tox *tox, const uint8_t *client_id); + +/* Copies the public key associated to that friend id into client_id buffer. + * Make sure that client_id is of size CLIENT_ID_SIZE. + * return 0 if success. + * return -1 if failure. + */ +int tox_get_client_id(const Tox *tox, int32_t friendnumber, uint8_t *client_id); + +/* Remove a friend. + * + * return 0 if success. + * return -1 if failure. + */ +int tox_del_friend(Tox *tox, int32_t friendnumber); + +/* Checks friend's connecting status. + * + * return 1 if friend is connected to us (Online). + * return 0 if friend is not connected to us (Offline). + * return -1 on failure. + */ +int tox_get_friend_connection_status(const Tox *tox, 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 tox_friend_exists(const Tox *tox, int32_t friendnumber); + +/* Send a text chat message to an online friend. + * + * return the message id if packet was successfully put into the send queue. + * return 0 if it was not. + * + * maximum length of messages is TOX_MAX_MESSAGE_LENGTH, your client must split larger messages + * or else sending them will not work. No the core will not split messages for you because that + * requires me to parse UTF-8. + * + * You will want to retain the return value, it will be passed to your read_receipt callback + * if one is received. + * m_sendmessage_withid will send a message with the id of your choosing, + * however we can generate an id for you by calling plain m_sendmessage. + */ +uint32_t tox_send_message(Tox *tox, int32_t friendnumber, const uint8_t *message, uint32_t length); +uint32_t tox_send_message_withid(Tox *tox, int32_t friendnumber, uint32_t theid, const uint8_t *message, + uint32_t length); + +/* Send an action to an online friend. + * + * return the message id if packet was successfully put into the send queue. + * return 0 if it was not. + * + * maximum length of actions is TOX_MAX_MESSAGE_LENGTH, your client must split larger actions + * or else sending them will not work. No the core will not split actions for you because that + * requires me to parse UTF-8. + * + * You will want to retain the return value, it will be passed to your read_receipt callback + * if one is received. + * m_sendaction_withid will send an action message with the id of your choosing, + * however we can generate an id for you by calling plain m_sendaction. + */ +uint32_t tox_send_action(Tox *tox, int32_t friendnumber, const uint8_t *action, uint32_t length); +uint32_t tox_send_action_withid(Tox *tox, int32_t friendnumber, uint32_t theid, const uint8_t *action, uint32_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 tox_set_name(Tox *tox, 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 (128) bytes. + * + * return length of name. + * return 0 on error. + */ +uint16_t tox_get_self_name(const Tox *tox, 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 tox_get_name(const Tox *tox, int32_t friendnumber, uint8_t *name); + +/* returns the length of name on success. + * returns -1 on failure. + */ +int tox_get_name_size(const Tox *tox, int32_t friendnumber); +int tox_get_self_name_size(const Tox *tox); + +/* Set our user status. + * + * userstatus must be one of TOX_USERSTATUS values. + * max length of the status is TOX_MAX_STATUSMESSAGE_LENGTH. + * + * returns 0 on success. + * returns -1 on failure. + */ +int tox_set_status_message(Tox *tox, const uint8_t *status, uint16_t length); +int tox_set_user_status(Tox *tox, uint8_t userstatus); + +/* returns the length of status message on success. + * returns -1 on failure. + */ +int tox_get_status_message_size(const Tox *tox, int32_t friendnumber); +int tox_get_self_status_message_size(const Tox *tox); + +/* 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 tox_get_status_message(const Tox *tox, int32_t friendnumber, uint8_t *buf, uint32_t maxlen); +int tox_get_self_status_message(const Tox *tox, uint8_t *buf, uint32_t maxlen); + +/* return one of TOX_USERSTATUS values. + * Values unknown to your application should be represented as TOX_USERSTATUS_NONE. + * As above, the self variant will return our own TOX_USERSTATUS. + * If friendnumber is invalid, this shall return TOX_USERSTATUS_INVALID. + */ +uint8_t tox_get_user_status(const Tox *tox, int32_t friendnumber); +uint8_t tox_get_self_user_status(const Tox *tox); + + +/* returns timestamp of last time friendnumber was seen online, or 0 if never seen. + * returns -1 on error. + */ +uint64_t tox_get_last_online(const Tox *tox, 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 tox_set_user_is_typing(Tox *tox, 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. + */ +uint8_t tox_get_is_typing(const Tox *tox, int32_t friendnumber); + +/* Sets whether we send read receipts for friendnumber. + * This function is not lazy, and it will fail if yesno is not (0 or 1). + */ +void tox_set_sends_receipts(Tox *tox, int32_t friendnumber, int yesno); + +/* 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 tox_count_friendlist(const Tox *tox); + +/* Return the number of online friends in the instance m. */ +uint32_t tox_get_num_online_friends(const Tox *tox); + +/* 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 tox_get_friendlist(const Tox *tox, int32_t *out_list, uint32_t list_size); + +/* Set the function that will be executed when a friend request is received. + * Function format is function(Tox *tox, uint8_t * public_key, uint8_t * data, uint16_t length, void *userdata) + */ +void tox_callback_friend_request(Tox *tox, void (*function)(Tox *tox, const uint8_t *, const uint8_t *, uint16_t, + void *), void *userdata); + +/* Set the function that will be executed when a message from a friend is received. + * Function format is: function(Tox *tox, int32_t friendnumber, uint8_t * message, uint16_t length, void *userdata) + */ +void tox_callback_friend_message(Tox *tox, void (*function)(Tox *tox, int32_t, const uint8_t *, uint16_t, void *), + void *userdata); + +/* Set the function that will be executed when an action from a friend is received. + * Function format is: function(Tox *tox, int32_t friendnumber, uint8_t * action, uint16_t length, void *userdata) + */ +void tox_callback_friend_action(Tox *tox, void (*function)(Tox *tox, int32_t, const uint8_t *, uint16_t, void *), + void *userdata); + +/* Set the callback for name changes. + * function(Tox *tox, int32_t friendnumber, uint8_t *newname, uint16_t length, void *userdata) + * You are not responsible for freeing newname + */ +void tox_callback_name_change(Tox *tox, void (*function)(Tox *tox, int32_t, const uint8_t *, uint16_t, void *), + void *userdata); + +/* Set the callback for status message changes. + * function(Tox *tox, int32_t friendnumber, uint8_t *newstatus, uint16_t length, void *userdata) + * You are not responsible for freeing newstatus. + */ +void tox_callback_status_message(Tox *tox, void (*function)(Tox *tox, int32_t, const uint8_t *, uint16_t, void *), + void *userdata); + +/* Set the callback for status type changes. + * function(Tox *tox, int32_t friendnumber, uint8_t TOX_USERSTATUS, void *userdata) + */ +void tox_callback_user_status(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, void *), void *userdata); + +/* Set the callback for typing changes. + * function (Tox *tox, int32_t friendnumber, uint8_t is_typing, void *userdata) + */ +void tox_callback_typing_change(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, void *), void *userdata); + +/* Set the callback for read receipts. + * function(Tox *tox, int32_t friendnumber, uint32_t receipt, void *userdata) + * + * 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 tox_callback_read_receipt(Tox *tox, void (*function)(Tox *tox, int32_t, uint32_t, void *), void *userdata); + +/* Set the callback for connection status changes. + * function(Tox *tox, int32_t friendnumber, uint8_t status, void *userdata) + * + * Status: + * 0 -- friend went offline after being previously online + * 1 -- friend went online + * + * NOTE: 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 tox_callback_connection_status(Tox *tox, void (*function)(Tox *tox, int32_t, uint8_t, void *), void *userdata); + + +/**********ADVANCED FUNCTIONS (If you don't know what they do you can safely ignore them.) ************/ + +/* Functions to get/set the nospam part of the id. + */ +uint32_t tox_get_nospam(const Tox *tox); +void tox_set_nospam(Tox *tox, uint32_t nospam); + +/* Copy the public and secret key from the Tox object. + public_key and secret_key must be 32 bytes big. + if the pointer is NULL, no data will be copied to it.*/ +void tox_get_keys(Tox *tox, uint8_t *public_key, uint8_t *secret_key); + +/**********GROUP CHAT FUNCTIONS: WARNING Group chats will be rewritten so this might change ************/ + +/* Set the callback for group invites. + * + * Function(Tox *tox, int friendnumber, uint8_t *group_public_key, void *userdata) + */ +void tox_callback_group_invite(Tox *tox, void (*function)(Tox *tox, int32_t, const uint8_t *, void *), void *userdata); + +/* Set the callback for group messages. + * + * Function(Tox *tox, int groupnumber, int friendgroupnumber, 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 friendgroupnumber, 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 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); + +/* 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.) + * + * returns group number on success + * returns -1 on failure. + */ +int tox_join_groupchat(Tox *tox, int32_t friendnumber, const uint8_t *friend_group_public_key); + +/* 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, uint32_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, uint32_t length); + +/* 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, int *out_list, uint32_t list_size); + + +/****************FILE SENDING FUNCTIONS*****************/ +/* NOTE: This how to will be updated. + * + * HOW TO SEND FILES CORRECTLY: + * 1. Use tox_new_file_sender(...) to create a new file sender. + * 2. Wait for the callback set with tox_callback_file_control(...) to be called with receive_send == 1 and control_type == TOX_FILECONTROL_ACCEPT + * 3. Send the data with tox_file_send_data(...) with chunk size tox_file_data_size(...) + * 4. When sending is done, send a tox_file_send_control(...) with send_receive = 0 and message_id = TOX_FILECONTROL_FINISHED + * 5. when the callback set with tox_callback_file_control(...) is called with receive_send == 1 and control_type == TOX_FILECONTROL_FINISHED + * the other person has received the file correctly. + * + * HOW TO RECEIVE FILES CORRECTLY: + * 1. wait for the callback set with tox_callback_file_send_request(...) + * 2. accept or refuse the connection with tox_file_send_control(...) with send_receive = 1 and message_id = TOX_FILECONTROL_ACCEPT or TOX_FILECONTROL_KILL + * 3. save all the data received with the callback set with tox_callback_file_data(...) to a file. + * 4. when the callback set with tox_callback_file_control(...) is called with receive_send == 0 and control_type == TOX_FILECONTROL_FINISHED + * the file is done transferring. + * 5. send a tox_file_send_control(...) with send_receive = 1 and message_id = TOX_FILECONTROL_FINISHED to confirm that we did receive the file. + * + * tox_file_data_remaining(...) can be used to know how many bytes are left to send/receive. + * + * If the connection breaks during file sending (The other person goes offline without pausing the sending and then comes back) + * the receiver must send a control packet with send_receive == 1 message_id = TOX_FILECONTROL_RESUME_BROKEN and the data being + * a uint64_t (in host byte order) containing the number of bytes received. + * + * If the sender receives this packet, he must send a control packet with send_receive == 0 and control_type == TOX_FILECONTROL_ACCEPT + * then he must start sending file data from the position (data , uint64_t in host byte order) received in the TOX_FILECONTROL_RESUME_BROKEN packet. + * + * To pause a file transfer send a control packet with control_type == TOX_FILECONTROL_PAUSE. + * To unpause a file transfer send a control packet with control_type == TOX_FILECONTROL_ACCEPT. + * + * If you receive a control packet with receive_send == 1 and control_type == TOX_FILECONTROL_PAUSE, you must stop sending filenumber until the other + * person sends a control packet with send_receive == 0 and control_type == TOX_FILECONTROL_ACCEPT with the filenumber being a paused filenumber. + * + * If you receive a control packet with receive_send == 0 and control_type == TOX_FILECONTROL_PAUSE, it means the sender of filenumber has paused the + * transfer and will resume it later with a control packet with send_receive == 1 and control_type == TOX_FILECONTROL_ACCEPT for that file number. + * + * More to come... + */ + +enum { + TOX_FILECONTROL_ACCEPT, + TOX_FILECONTROL_PAUSE, + TOX_FILECONTROL_KILL, + TOX_FILECONTROL_FINISHED, + TOX_FILECONTROL_RESUME_BROKEN +}; +/* Set the callback for file send requests. + * + * Function(Tox *tox, int32_t friendnumber, uint8_t filenumber, uint64_t filesize, uint8_t *filename, uint16_t filename_length, void *userdata) + */ +void tox_callback_file_send_request(Tox *tox, void (*function)(Tox *m, int32_t, uint8_t, uint64_t, const uint8_t *, + uint16_t, void *), void *userdata); + +/* Set the callback for file control requests. + * + * receive_send is 1 if the message is for a slot on which we are currently sending a file and 0 if the message + * is for a slot on which we are receiving the file + * + * Function(Tox *tox, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber, uint8_t control_type, uint8_t *data, uint16_t length, void *userdata) + * + */ +void tox_callback_file_control(Tox *tox, void (*function)(Tox *m, int32_t, uint8_t, uint8_t, uint8_t, const uint8_t *, + uint16_t, void *), void *userdata); + +/* Set the callback for file data. + * + * Function(Tox *tox, int32_t friendnumber, uint8_t filenumber, uint8_t *data, uint16_t length, void *userdata) + * + */ +void tox_callback_file_data(Tox *tox, void (*function)(Tox *m, int32_t, uint8_t, const uint8_t *, uint16_t length, + void *), void *userdata); + + +/* Send a file send request. + * Maximum filename length is 255 bytes. + * return file number on success + * return -1 on failure + */ +int tox_new_file_sender(Tox *tox, int32_t friendnumber, uint64_t filesize, const uint8_t *filename, + uint16_t filename_length); + +/* Send a file control request. + * + * send_receive is 0 if we want the control packet to target a file we are currently sending, + * 1 if it targets a file we are currently receiving. + * + * return 0 on success + * return -1 on failure + */ +int tox_file_send_control(Tox *tox, int32_t friendnumber, uint8_t send_receive, uint8_t filenumber, uint8_t message_id, + const uint8_t *data, uint16_t length); + +/* Send file data. + * + * return 0 on success + * return -1 on failure + */ +int tox_file_send_data(Tox *tox, int32_t friendnumber, uint8_t filenumber, const uint8_t *data, uint16_t length); + +/* Returns the recommended/maximum size of the filedata you send with tox_file_send_data() + * + * return size on success + * return -1 on failure (currently will never return -1) + */ +int tox_file_data_size(const Tox *tox, int32_t friendnumber); + +/* 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 tox_file_data_remaining(const Tox *tox, int32_t friendnumber, uint8_t filenumber, uint8_t send_receive); + +/***************END OF FILE SENDING FUNCTIONS******************/ + +/* + * Use this function to bootstrap the client. + */ + +/* Resolves address into an IP address. If successful, sends a "get nodes" + * request to the given node with ip, port (in network byte order, HINT: use htons()) + * 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 tox_bootstrap_from_address(Tox *tox, const char *address, uint8_t ipv6enabled, + uint16_t port, const uint8_t *public_key); + +/* return 0 if we are not connected to the DHT. + * return 1 if we are. + */ +int tox_isconnected(const Tox *tox); + +/* + * Run this function at startup. + * + * Initializes a tox structure + * The type of communication socket depends on ipv6enabled: + * If set to 0 (zero), creates an IPv4 socket which subsequently only allows + * IPv4 communication + * If set to anything else, creates an IPv6 socket which allows both IPv4 AND + * IPv6 communication + * + * return allocated instance of tox on success. + * return 0 if there are problems. + */ +Tox *tox_new(uint8_t ipv6enabled); + +/* Run this before closing shop. + * Free all datastructures. */ +void tox_kill(Tox *tox); + +/* Return the time in milliseconds before tox_do() should be called again + * for optimal performance. + * + * returns time (in ms) before the next tox_do() needs to be run on success. + */ +uint32_t tox_do_interval(Tox *tox); + +/* The main loop that needs to be run in intervals of tox_do_interval() ms. */ +void tox_do(Tox *tox); + +/* SAVING AND LOADING FUNCTIONS: */ + +/* return size of messenger data (for saving). */ +uint32_t tox_size(const Tox *tox); + +/* Save the messenger in data (must be allocated memory of size Messenger_size()). */ +void tox_save(const Tox *tox, uint8_t *data); + +/* Load the messenger from data of size length. + * + * returns 0 on success + * returns -1 on failure + */ +int tox_load(Tox *tox, const uint8_t *data, uint32_t length); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/protocols/Tox/toxcore/toxcore/util.c b/protocols/Tox/toxcore/toxcore/util.c new file mode 100644 index 0000000000..969ee7045a --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/util.c @@ -0,0 +1,193 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <time.h> + +/* for CLIENT_ID_SIZE */ +#include "DHT.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 memcmp(dest, src, CLIENT_ID_SIZE) == 0; +} + +uint32_t id_copy(uint8_t *dest, const uint8_t *src) +{ + memcpy(dest, src, CLIENT_ID_SIZE); + return CLIENT_ID_SIZE; +} + +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; +} + +/* 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) { + memcpy(&length_sub, data, sizeof(length_sub)); + memcpy(&cookie_type, data + sizeof(length_sub), sizeof(cookie_type)); + 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 ((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 = cookie_type & 0xFFFF; + + if (-1 == load_state_callback(outer, data, length_sub, type)) + return -1; + + data += length_sub; + length -= length_sub; + } + + return length == 0 ? 0 : -1; +}; + +/* Converts 4 bytes to uint32_t */ +inline__ void bytes_to_U32(uint32_t *dest, const uint8_t *bytes) +{ + *dest = +#ifdef WORDS_BIGENDIAN + ( ( uint32_t ) * bytes ) | + ( ( uint32_t ) * ( bytes + 1 ) << 8 ) | + ( ( uint32_t ) * ( bytes + 2 ) << 16 ) | + ( ( uint32_t ) * ( bytes + 3 ) << 24 ) ; +#else + ( ( uint32_t ) * bytes << 24 ) | + ( ( uint32_t ) * ( bytes + 1 ) << 16 ) | + ( ( uint32_t ) * ( bytes + 2 ) << 8 ) | + ( ( uint32_t ) * ( bytes + 3 ) ) ; +#endif +} + +/* Converts 2 bytes to uint16_t */ +inline__ void bytes_to_U16(uint16_t *dest, const uint8_t *bytes) +{ + *dest = +#ifdef WORDS_BIGENDIAN + ( ( uint16_t ) * bytes ) | + ( ( uint16_t ) * ( bytes + 1 ) << 8 ); +#else + ( ( uint16_t ) * bytes << 8 ) | + ( ( uint16_t ) * ( bytes + 1 ) ); +#endif +} + +/* Convert uint32_t to byte string of size 4 */ +inline__ void U32_to_bytes(uint8_t *dest, uint32_t value) +{ +#ifdef WORDS_BIGENDIAN + *(dest) = ( value ); + *(dest + 1) = ( value >> 8 ); + *(dest + 2) = ( value >> 16 ); + *(dest + 3) = ( value >> 24 ); +#else + *(dest) = ( value >> 24 ); + *(dest + 1) = ( value >> 16 ); + *(dest + 2) = ( value >> 8 ); + *(dest + 3) = ( value ); +#endif +} + +/* Convert uint16_t to byte string of size 2 */ +inline__ void U16_to_bytes(uint8_t *dest, uint16_t value) +{ +#ifdef WORDS_BIGENDIAN + *(dest) = ( value ); + *(dest + 1) = ( value >> 8 ); +#else + *(dest) = ( value >> 8 ); + *(dest + 1) = ( value ); +#endif +}
\ No newline at end of file diff --git a/protocols/Tox/toxcore/toxcore/util.h b/protocols/Tox/toxcore/toxcore/util.h new file mode 100644 index 0000000000..955ce8c4ad --- /dev/null +++ b/protocols/Tox/toxcore/toxcore/util.h @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef __UTIL_H__ +#define __UTIL_H__ + +#include <stdbool.h> +#include <stdint.h> + +#define inline__ inline __attribute__((always_inline)) + +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) + +/* 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); + +/* Converts 4 bytes to uint32_t */ +void bytes_to_U32(uint32_t *dest, const uint8_t *bytes); + +/* Converts 2 bytes to uint16_t */ +void bytes_to_U16(uint16_t *dest, const uint8_t *bytes); + +/* Convert uint32_t to byte string of size 4 */ +void U32_to_bytes(uint8_t *dest, uint32_t value); + +/* Convert uint16_t to byte string of size 2 */ +void U16_to_bytes(uint8_t *dest, uint16_t value); + + +#endif /* __UTIL_H__ */ diff --git a/protocols/Tox/toxcore/toxdns/Makefile.inc b/protocols/Tox/toxcore/toxdns/Makefile.inc new file mode 100644 index 0000000000..689a58a5c8 --- /dev/null +++ b/protocols/Tox/toxcore/toxdns/Makefile.inc @@ -0,0 +1,29 @@ +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 diff --git a/protocols/Tox/toxcore/toxdns/toxdns.c b/protocols/Tox/toxcore/toxdns/toxdns.c new file mode 100644 index 0000000000..7a7a052dd1 --- /dev/null +++ b/protocols/Tox/toxcore/toxdns/toxdns.c @@ -0,0 +1,238 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#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 sucess. + * 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++; + + switch (ch) { + case 'A' ... 'Z': { + ch = ch - 'A'; + break; + } + + case 'a' ... 'z': { + ch = ch - 'a'; + break; + } + + case '0' ... '5': { + ch = ch - '0' + 26; + break; + } + + default: { + 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; +} diff --git a/protocols/Tox/toxcore/toxdns/toxdns.h b/protocols/Tox/toxcore/toxdns/toxdns.h new file mode 100644 index 0000000000..a0cc323b43 --- /dev/null +++ b/protocols/Tox/toxcore/toxdns/toxdns.h @@ -0,0 +1,85 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + */ + +#ifndef TOXDNS_H +#define TOXDNS_H + +#include <stdint.h> + +/* 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 sucess. + * 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); + +#endif |