toxcore 0.2.0 → 0.2.11
raw patch · 95 files changed
+11203/−3940 lines, 95 filesdep +MonadRandomdep +asyncdep +binarydep −bytestring-arbitrarydep −data-default-classdep −directorydep ~QuickChecknew-component:exe:toxsave-convert
Dependencies added: MonadRandom, async, binary, binary-bits, clock, containers, entropy, groom, integer-gmp, iproute, lens-family, msgpack-binary, msgpack-rpc-conduit, msgpack-types, mtl, network, random, text, transformers
Dependencies removed: bytestring-arbitrary, data-default-class, directory
Dependency ranges changed: QuickCheck
Files
- LICENSE +675/−0
- LICENSE.md +0/−595
- src/Network/Tox.lhs +3476/−0
- src/Network/Tox/Application/GroupChats.lhs +388/−0
- src/Network/Tox/Binary.hs +209/−0
- src/Network/Tox/C.hs +0/−10
- src/Network/Tox/C/CEnum.hs +0/−37
- src/Network/Tox/C/Callbacks.hs +0/−89
- src/Network/Tox/C/Constants.hs +0/−50
- src/Network/Tox/C/Options.hs +0/−317
- src/Network/Tox/C/Tox.hs +0/−2405
- src/Network/Tox/C/Type.hs +0/−17
- src/Network/Tox/C/Version.hs +0/−27
- src/Network/Tox/Crypto.lhs +16/−0
- src/Network/Tox/Crypto/Box.lhs +184/−0
- src/Network/Tox/Crypto/CombinedKey.lhs +58/−0
- src/Network/Tox/Crypto/Key.lhs +152/−0
- src/Network/Tox/Crypto/KeyPair.lhs +94/−0
- src/Network/Tox/Crypto/Keyed.hs +48/−0
- src/Network/Tox/Crypto/KeyedT.hs +53/−0
- src/Network/Tox/Crypto/Nonce.lhs +62/−0
- src/Network/Tox/DHT.lhs +179/−0
- src/Network/Tox/DHT/ClientList.lhs +156/−0
- src/Network/Tox/DHT/ClientNode.lhs +37/−0
- src/Network/Tox/DHT/DhtPacket.lhs +124/−0
- src/Network/Tox/DHT/DhtRequestPacket.lhs +69/−0
- src/Network/Tox/DHT/DhtState.lhs +366/−0
- src/Network/Tox/DHT/Distance.lhs +103/−0
- src/Network/Tox/DHT/KBuckets.lhs +235/−0
- src/Network/Tox/DHT/NodeList.lhs +75/−0
- src/Network/Tox/DHT/NodesRequest.lhs +51/−0
- src/Network/Tox/DHT/NodesResponse.lhs +67/−0
- src/Network/Tox/DHT/Operation.lhs +589/−0
- src/Network/Tox/DHT/PendingReplies.lhs +45/−0
- src/Network/Tox/DHT/PingPacket.lhs +78/−0
- src/Network/Tox/DHT/RpcPacket.lhs +85/−0
- src/Network/Tox/DHT/Stamped.hs +54/−0
- src/Network/Tox/Encoding.hs +37/−0
- src/Network/Tox/Network/MonadRandomBytes.hs +105/−0
- src/Network/Tox/Network/Networked.hs +61/−0
- src/Network/Tox/NodeInfo.lhs +12/−0
- src/Network/Tox/NodeInfo/HostAddress.lhs +102/−0
- src/Network/Tox/NodeInfo/NodeInfo.lhs +98/−0
- src/Network/Tox/NodeInfo/PortNumber.lhs +50/−0
- src/Network/Tox/NodeInfo/SocketAddress.lhs +76/−0
- src/Network/Tox/NodeInfo/TransportProtocol.lhs +65/−0
- src/Network/Tox/Protocol.lhs +9/−0
- src/Network/Tox/Protocol/Packet.lhs +72/−0
- src/Network/Tox/Protocol/PacketKind.lhs +163/−0
- src/Network/Tox/SaveData.lhs +336/−0
- src/Network/Tox/SaveData/Conferences.lhs +169/−0
- src/Network/Tox/SaveData/DHT.lhs +120/−0
- src/Network/Tox/SaveData/Friend.lhs +136/−0
- src/Network/Tox/SaveData/Nodes.hs +18/−0
- src/Network/Tox/SaveData/Util.hs +38/−0
- src/Network/Tox/Testing.lhs +55/−0
- src/Network/Tox/Time.hs +54/−0
- src/Network/Tox/Timed.hs +31/−0
- src/Network/Tox/TimedT.hs +29/−0
- test/Data/Result.hs +40/−0
- test/Network/Tox/C/ToxSpec.hs +0/−119
- test/Network/Tox/CSpec.hs +0/−96
- test/Network/Tox/Crypto/BoxSpec.hs +76/−0
- test/Network/Tox/Crypto/CombinedKeySpec.hs +26/−0
- test/Network/Tox/Crypto/KeyPairSpec.hs +61/−0
- test/Network/Tox/Crypto/KeySpec.hs +92/−0
- test/Network/Tox/Crypto/NonceSpec.hs +53/−0
- test/Network/Tox/CryptoSpec.hs +10/−0
- test/Network/Tox/DHT/ClientListSpec.hs +74/−0
- test/Network/Tox/DHT/DhtPacketSpec.hs +73/−0
- test/Network/Tox/DHT/DhtRequestPacketSpec.hs +16/−0
- test/Network/Tox/DHT/DhtStateSpec.hs +119/−0
- test/Network/Tox/DHT/DistanceSpec.lhs +169/−0
- test/Network/Tox/DHT/KBucketsSpec.hs +138/−0
- test/Network/Tox/DHT/NodesRequestSpec.hs +21/−0
- test/Network/Tox/DHT/NodesResponseSpec.hs +15/−0
- test/Network/Tox/DHT/OperationSpec.hs +89/−0
- test/Network/Tox/DHT/PendingRepliesSpec.hs +37/−0
- test/Network/Tox/DHT/PingPacketSpec.hs +15/−0
- test/Network/Tox/DHT/RpcPacketSpec.hs +21/−0
- test/Network/Tox/DHTSpec.hs +10/−0
- test/Network/Tox/EncodingSpec.hs +146/−0
- test/Network/Tox/NodeInfo/HostAddressSpec.hs +15/−0
- test/Network/Tox/NodeInfo/NodeInfoSpec.hs +27/−0
- test/Network/Tox/NodeInfo/PortNumberSpec.hs +15/−0
- test/Network/Tox/NodeInfo/SocketAddressSpec.hs +20/−0
- test/Network/Tox/NodeInfo/TransportProtocolSpec.hs +16/−0
- test/Network/Tox/NodeInfoSpec.hs +10/−0
- test/Network/Tox/Protocol/PacketKindSpec.hs +25/−0
- test/Network/Tox/Protocol/PacketSpec.hs +23/−0
- test/Network/Tox/ProtocolSpec.hs +10/−0
- test/Network/Tox/SaveDataSpec.hs +26/−0
- tools/groupbot/Main.hs +0/−143
- tools/toxsave-convert.hs +21/−0
- toxcore.cabal +130/−35
+ LICENSE view
@@ -0,0 +1,675 @@+ 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>.+
− LICENSE.md
@@ -1,595 +0,0 @@-GNU General Public License-==========================--_Version 3, 29 June 2007_-_Copyright © 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>>.
+ src/Network/Tox.lhs view
@@ -0,0 +1,3476 @@+\chapter{Introduction}++\begin{code}+{-# LANGUAGE Safe #-}+module Network.Tox where+\end{code}++This document is a textual specification of the Tox protocol and all the+supporting modules required to implement it. The goal of this document is to+give enough guidance to permit a complete and correct implementation of the+protocol.++\section{Objectives}++This section provides an overview of goals and non-goals of Tox. It provides+the reader with:++\begin{itemize}+ \item a basic understanding of what problems Tox intends to solve;+ \item a means to validate whether those problems are indeed solved by the+ protocol as specified;+ \item the ability to make better tradeoffs and decisions in their own+ reimplementation of the protocol.+\end{itemize}++\subsection{Goals}++\begin{itemize}++ \item \textbf{Authentication:} Tox aims to provide authenticated+ communication. This means that during a communication session, both parties+ can be sure of the other party's identity. Users are identified by their+ public key. The initial key exchange is currently not in scope for the Tox+ protocol. In the future, Tox may provide a means for initial authentication+ using a challenge/response or shared secret based exchange.++ If the secret key is compromised, the user's identity is compromised, and an+ attacker can impersonate that user. When this happens, the user must create+ a new identity with a new public key.++ \item \textbf{End-to-end encryption:} The Tox protocol establishes end-to-end+ encrypted communication links. Shared keys are deterministically derived+ using a Diffie-Hellman-like method, so keys are never transferred over the+ network.++ \item \textbf{Forward secrecy}: Session keys are re-negotiated when the peer+ connection is established.++ \item \textbf{Privacy}: When Tox establishes a communication link, it aims to+ avoid leaking to any third party the identities of the parties involved+ (i.e. their public keys).++ Furthermore, it aims to avoid allowing third parties to determine the IP+ address of a given user.++ \item \textbf{Resilience:}+ \begin{itemize}+ \item Independence of infrastructure: Tox avoids relying on servers as+ much as possible. Communications are not transmitted via or stored on+ central servers. Joining a Tox network requires connecting to a+ well-known node called a bootstrap node. Anyone can run a bootstrap+ node, and users need not put any trust in them.+ \item Tox tries to establish communication paths in difficult network+ situations. This includes connecting to peers behind a NAT or firewall.+ Various techniques help achieve this, such as UDP hole-punching, UPnP,+ NAT-PMP, other untrusted nodes acting as relays, and DNS tunnels.+ \item Resistance to basic denial of service attacks: short timeouts make+ the network dynamic and resilient against poisoning attempts.+ \end{itemize}++ \item \textbf{Minimum configuration:} Tox aims to be nearly zero-conf.+ User-friendliness is an important aspect to security. Tox aims to make+ security easy to achieve for average users.+\end{itemize}++\subsection{Non-goals}++\begin{itemize}+ \item \textbf{Anonymity} is not in scope for the Tox protocol itself, but it+ provides an easy way to integrate with software providing anonymity, such as+ Tor.++ By default, Tox tries to establish direct connections between peers; as a+ consequence, each is aware of the other's IP address, and third parties+ may be able to determine that a connection has been established between+ those IP addresses. One of the reasons for making direct connections is that+ relaying real-time multimedia conversations over anonymity networks is not+ feasible with the current network infrastructure.+\end{itemize}++\section{Threat model}++TODO(iphydf): Define one.++\section{Data types}++All data types are defined before their first use, and their binary protocol+representation is given. The protocol representations are normative and must+be implemented exactly as specified. For some types, human-readable+representations are suggested. An implementation may choose to provide no such+representation or a different one. The implementation is free to choose any+in-memory representation of the specified types.++Binary formats are specified in tables with length, type, and content+descriptions. If applicable, specific enumeration types are used, so types may+be self-explanatory in some cases. The length can be either a fixed number in+bytes (e.g. \texttt{32}), a number in bits (e.g. \texttt{7} bit), a choice of+lengths (e.g. \texttt{4 $|$ 16}), or an inclusive range (e.g. \texttt{[0,+100]}). Open ranges are denoted \texttt{[n,]} to mean a minimum length of+\texttt{n} with no specified maximum length.++\section{Integers}++The protocol uses four bounded unsigned integer types. Bounded means they have+an upper bound beyond which incrementing is not defined. The integer types+support modular arithmetic, so overflow wraps around to zero. Unsigned means+their lower bound is 0. Signed integer types are not used. The binary+encoding of all integer types is a fixed-width byte sequence with the integer+encoded in \href{https://en.wikipedia.org/wiki/Endianness}{Big Endian} unless+stated otherwise.++\begin{tabular}{l|l|l|l}+ Type name & C type & Length & Upper bound \\+ \hline+ Word8 & \texttt{uint8\_t} & 1 & 255 (0xff) \\+ Word16 & \texttt{uint16\_t} & 2 & 65535 (0xffff) \\+ Word32 & \texttt{uint32\_t} & 4 & 4294967295 (0xffffffff) \\+ Word64 & \texttt{uint64\_t} & 8 & 18446744073709551615 (0xffffffffffffffff) \\+\end{tabular}++\section{Strings}++A String is a data structure used for human readable text. Strings are+sequences of glyphs. A glyph consists of one non-zero-width unicode code point+and zero or more zero-width unicode code points. The human-readable+representation of a String starts and ends with a quotation mark (\texttt{"})+and contains all human-readable glyphs verbatim. Control characters are+represented in an isomorphic human-readable way. I.e. every control character+has exactly one human-readable representation, and a mapping exists from the+human-readable representation to the control character. Therefore, the use of+Unicode Control Characters (U+240x) is not permitted without additional marker.++\input{src/Network/Tox/Crypto.lhs}+\input{src/Network/Tox/NodeInfo.lhs}+\input{src/Network/Tox/Protocol.lhs}+\input{src/Network/Tox/DHT.lhs}++\chapter{LAN discovery}++LAN discovery is a way to discover Tox peers that are on a local network. If+two Tox friends are on a local network, the most efficient way for them to+communicate together is to use the local network. If a Tox client is opened on+a local network in which another Tox client exists then good behavior would be+to bootstrap to the network using the Tox client on the local network. This is+what LAN discovery aims to accomplish.++LAN discovery works by sending a UDP packet through the toxcore UDP socket to+the interface broadcast address on IPv4, the global broadcast address+(255.255.255.255) and the multicast address on IPv6 (FF02::1) on the default+Tox UDP port (33445).++The LAN Discovery packet:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (33) \\+ \texttt{32} & DHT public key \\+\end{tabular}++LAN Discovery packets contain the DHT public key of the sender. When a LAN+Discovery packet is received, a DHT get nodes packet will be sent to the sender+of the packet. This means that the DHT instance will bootstrap itself to every+peer from which it receives one of these packets. Through this mechanism, Tox+clients will bootstrap themselves automatically from other Tox clients running+on the local network.++When enabled, toxcore sends these packets every 10 seconds to keep delays low.+The packets could be sent up to every 60 seconds but this would make peer+finding over the network 6 times slower.++LAN discovery enables two friends on a local network to find each other as the+DHT prioritizes LAN addresses over non LAN addresses for DHT peers. Sending a+get node request/bootstrapping from a peer successfully should also add them to+the list of DHT peers if we are searching for them. The peer must not be+immediately added if a LAN discovery packet with a DHT public key that we are+searching for is received as there is no cryptographic proof that this packet+is legitimate and not maliciously crafted. This means that a DHT get node or+ping packet must be sent, and a valid response must be received, before we can+say that this peer has been found.++LAN discovery is how Tox handles and makes everything work well on LAN.++\chapter{Messenger}++Messenger is the module at the top of all the other modules. It sits on top of+\texttt{friend\_connection} in the hierarchy of toxcore.++Messenger takes care of sending and receiving messages using the connection+provided by \texttt{friend\_connection}. The module provides a way for friends+to connect and makes it usable as an instant messenger. For example, Messenger+lets users set a nickname and status message which it then transmits to friends+when they are online. It also allows users to send messages to friends and+builds an instant messenging system on top of the lower level+\texttt{friend\_connection} module.++Messenger offers two methods to add a friend. The first way is to add a friend+with only their long term public key, this is used when a friend needs to be+added but for some reason a friend request should not be sent. The friend+should only be added. This method is most commonly used to accept friend+requests but could also be used in other ways. If two friends add each other+using this function they will connect to each other. Adding a friend using+this method just adds the friend to \texttt{friend\_connection} and creates a+new friend entry in Messenger for the friend.++The Tox ID is used to identify peers so that they can be added as friends in+Tox. In order to add a friend, a Tox user must have the friend's Tox ID. The+Tox ID contains the long term public key of the peer (32 bytes) followed by the+4 byte nospam (see: \texttt{friend\_requests}) value and a 2 byte XOR checksum.+The method of sending the Tox ID to others is up to the user and the client but+the recommended way is to encode it in hexadecimal format and have the user+manually send it to the friend using another program.++Tox ID:++\begin{figure}+\includegraphics{res/images/tox-id.png}+\caption{Tox ID}+\end{figure}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{32} & long term public key \\+ \texttt{4} & nospam \\+ \texttt{2} & checksum \\+\end{tabular}++The checksum is calculated by XORing the first two bytes of the ID with the+next two bytes, then the next two bytes until all the 36 bytes have been XORed+together. The result is then appended to the end to form the Tox ID.++The user must make sure the Tox ID is not intercepted and replaced in transit+by a different Tox ID, which would mean the friend would connect to a malicious+person instead of the user, though taking reasonable precautions as this is+outside the scope of Tox. Tox assumes that the user has ensured that they are+using the correct Tox ID, belonging to the intended person, to add a friend.++The second method to add a friend is by using their Tox ID and a message to be+sent in a friend request. This way of adding friends will try to send a friend+request, with the set message, to the peer whose Tox ID was added. The method+is similar to the first one, except that a friend request is crafted and sent+to the other peer.++When a friend connection associated to a Messenger friend goes online, a ONLINE+packet will be sent to them. Friends are only set as online if an ONLINE+packet is received.++As soon as a friend goes online, Messenger will stop sending friend requests to+that friend, if it was sending them, as they are redundant for this friend.++Friends will be set as offline if either the friend connection associated to+them goes offline or if an OFFLINE packet is received from the friend.++Messenger packets are sent to the friend using the online friend connection to+the friend.++Should Messenger need to check whether any of the non lossy packets in the+following list were received by the friend, for example to implement receipts+for text messages, \texttt{net\_crypto} can be used. The \texttt{net\_crypto}+packet number, used to send the packets, should be noted and then+\texttt{net\_crypto} checked later to see if the bottom of the send array is+after this packet number. If it is, then the friend has received them. Note+that \texttt{net\_crypto} packet numbers could overflow after a long time, so+checks should happen within 2**32 \texttt{net\_crypto} packets sent with the+same friend connection.++Message receipts for action messages and normal text messages are implemented+by adding the \texttt{net\_crypto} packet number of each message, along with the+receipt number, to the top of a linked list that each friend has as they are+sent. Every Messenger loop, the entries are read from the bottom and entries+are removed and passed to the client until an entry that refers to a packet not+yet received by the other is reached, when this happens it stops.++List of Messenger packets:++\section{\texttt{ONLINE}}++length: 1 byte++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x18) \\+\end{tabular}++Sent to a friend when a connection is established to tell them to mark us as+online in their friends list. This packet and the OFFLINE packet are necessary+as \texttt{friend\_connections} can be established with non-friends who are part+of a groupchat. The two packets are used to differentiate between these peers,+connected to the user through groupchats, and actual friends who ought to be+marked as online in the friendlist.++On receiving this packet, Messenger will show the peer as being online.++\section{\texttt{OFFLINE}}++length: 1 byte++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x19) \\+\end{tabular}++Sent to a friend when deleting the friend. Prevents a deleted friend from+seeing us as online if we are connected to them because of a group chat.++On receiving this packet, Messenger will show this peer as offline.++\section{\texttt{NICKNAME}}++length: 1 byte to 129 bytes.++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x30) \\+ \texttt{[0, 128]} & Nickname as a UTF8 byte string \\+\end{tabular}++Used to send the nickname of the peer to others. This packet should be sent+every time to each friend every time they come online and each time the+nickname is changed.++\section{\texttt{STATUSMESSAGE}}++length: 1 byte to 1008 bytes.++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x31) \\+ \texttt{[0, 1007]} & Status message as a UTF8 byte string \\+\end{tabular}++Used to send the status message of the peer to others. This packet should be+sent every time to each friend every time they come online and each time the+status message is changed.++\section{\texttt{USERSTATUS}}++length: 2 bytes++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x32) \\+ \texttt{1} & \texttt{uint8\_t} status (0 = online, 1 = away, 2 = busy) \\+\end{tabular}++Used to send the user status of the peer to others. This packet should be sent+every time to each friend every time they come online and each time the user+status is changed.++\section{\texttt{TYPING}}++length: 2 bytes++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x33) \\+ \texttt{1} & \texttt{uint8\_t} typing status (0 = not typing, 1 = typing) \\+\end{tabular}++Used to tell a friend whether the user is currently typing or not.++\section{\texttt{MESSAGE}}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x40) \\+ \texttt{[0, 1372]} & Message as a UTF8 byte string \\+\end{tabular}++Used to send a normal text message to the friend.++\section{\texttt{ACTION}}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x41) \\+ \texttt{[0, 1372]} & Action message as a UTF8 byte string \\+\end{tabular}++Used to send an action message (like an IRC action) to the friend.++\section{\texttt{MSI}}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x45) \\+ \texttt{?} & data \\+\end{tabular}++Reserved for Tox AV usage.++\section{File Transfer Related Packets}++\subsection{\texttt{FILE\_SENDREQUEST}}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x50) \\+ \texttt{1} & \texttt{uint8\_t} file number \\+ \texttt{4} & \texttt{uint32\_t} file type \\+ \texttt{8} & \texttt{uint64\_t} file size \\+ \texttt{32} & file id (32 bytes) \\+ \texttt{[0, 255]} & filename as a UTF8 byte string \\+\end{tabular}++Note that file type and file size are sent in big endian/network byte format.++\subsection{\texttt{FILE\_CONTROL}}++length: 4 bytes if \texttt{control\_type} isn't seek. 8 bytes if+\texttt{control\_type} is seek.++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x51) \\+ \texttt{1} & \texttt{uint8\_t} \texttt{send\_receive} \\+ \texttt{1} & \texttt{uint8\_t} file number \\+ \texttt{1} & \texttt{uint8\_t} \texttt{control\_type} \\+ \texttt{8} & \texttt{uint64\_t} seek parameter \\+\end{tabular}++\texttt{send\_receive} is 0 if the control targets a file being sent (by the+peer sending the file control), and 1 if it targets a file being received.++\texttt{control\_type} can be one of: 0 = accept, 1 = pause, 2 = kill, 3 = seek.++The seek parameter is only included when \texttt{control\_type} is seek (3).++Note that if it is included the seek parameter will be sent in big+endian/network byte format.++\subsection{\texttt{FILE\_DATA}}++length: 2 to 1373 bytes.++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x52) \\+ \texttt{1} & \texttt{uint8\_t} file number \\+ \texttt{[0, 1371]} & file data piece \\+\end{tabular}++Files are transferred in Tox using File transfers.++To initiate a file transfer, the friend creates and sends a+\texttt{FILE\_SENDREQUEST} packet to the friend it wants to initiate a file+transfer to.++The first part of the \texttt{FILE\_SENDREQUEST} packet is the file number. The+file number is the number used to identify this file transfer. As the file+number is represented by a 1 byte number, the maximum amount of concurrent+files Tox can send to a friend is 256. 256 file transfers per friend is enough+that clients can use tricks like queueing files if there are more files needing+to be sent.++256 outgoing files per friend means that there is a maximum of 512 concurrent+file transfers, between two users, if both incoming and outgoing file transfers+are counted together.++As file numbers are used to identify the file transfer, the Tox instance must+make sure to use a file number that isn't used for another outgoing file+transfer to that same friend when creating a new outgoing file transfer. File+numbers are chosen by the file sender and stay unchanged for the entire+duration of the file transfer. The file number is used by both+\texttt{FILE\_CONTROL} and \texttt{FILE\_DATA} packets to identify which file+transfer these packets are for.++The second part of the file transfer request is the file type. This is simply+a number that identifies the type of file. for example, tox.h defines the file+type 0 as being a normal file and type 1 as being an avatar meaning the Tox+client should use that file as an avatar. The file type does not effect in any+way how the file is transfered or the behavior of the file transfer. It is set+by the Tox client that creates the file transfers and send to the friend+untouched.++The file size indicates the total size of the file that will be transfered. A+file size of \texttt{UINT64\_MAX} (maximum value in a \texttt{uint64\_t}) means+that the size of the file is undetermined or unknown. For example if someone+wanted to use Tox file transfers to stream data they would set the file size to+\texttt{UINT64\_MAX}. A file size of 0 is valid and behaves exactly like a+normal file transfer.++The file id is 32 bytes that can be used to uniquely identify the file+transfer. For example, avatar transfers use it as the hash of the avatar so+that the receiver can check if they already have the avatar for a friend which+saves bandwidth. It is also used to identify broken file transfers across+toxcore restarts (for more info see the file transfer section of tox.h). The+file transfer implementation does not care about what the file id is, as it is+only used by things above it.++The last part of the file transfer is the optional file name which is used to+tell the receiver the name of the file.++When a \texttt{FILE\_SENDREQUEST} packet is received, the implementation+validates and sends the info to the Tox client which decides whether they+should accept the file transfer or not.++To refuse or cancel a file transfer, they will send a \texttt{FILE\_CONTROL}+packet with \texttt{control\_type} 2 (kill).++\texttt{FILE\_CONTROL} packets are used to control the file transfer.+\texttt{FILE\_CONTROL} packets are used to accept/unpause, pause, kill/cancel+and seek file transfers. The \texttt{control\_type} parameter denotes what the+file control packet does.++The \texttt{send\_receive} and file number are used to identify a specific file+transfer. Since file numbers for outgoing and incoming files are not related+to each other, the \texttt{send\_receive} parameter is used to identify if the+file number belongs to files being sent or files being received. If+\texttt{send\_receive} is 0, the file number corresponds to a file being sent by+the user sending the file control packet. If \texttt{send\_receive} is 1, it+corresponds to a file being received by the user sending the file control+packet.++\texttt{control\_type} indicates the purpose of the \texttt{FILE\_CONTROL}+packet. \texttt{control\_type} of 0 means that the \texttt{FILE\_CONTROL} packet+is used to tell the friend that the file transfer is accepted or that we are+unpausing a previously paused (by us) file transfer. \texttt{control\_type} of+1 is used to tell the other to pause the file transfer.++If one party pauses a file transfer, that party must be the one to unpause it.+Should both sides pause a file transfer, both sides must unpause it before the+file can be resumed. For example, if the sender pauses the file transfer, the+receiver must not be able to unpause it. To unpause a file transfer,+\texttt{control\_type} 0 is used. Files can only be paused when they are in+progress and have been accepted.++\texttt{control\_type} 2 is used to kill, cancel or refuse a file transfer.+When a \texttt{FILE\_CONTROL} is received, the targeted file transfer is+considered dead, will immediately be wiped and its file number can be reused.+The peer sending the \texttt{FILE\_CONTROL} must also wipe the targeted file+transfer from their side. This control type can be used by both sides of the+transfer at any time.++\texttt{control\_type} 3, the seek control type is used to tell the sender of+the file to start sending from a different index in the file than 0. It can+only be used right after receiving a \texttt{FILE\_SENDREQUEST} packet and+before accepting the file by sending a \texttt{FILE\_CONTROL} with+\texttt{control\_type} 0. When this \texttt{control\_type} is used, an extra 8+byte number in big endian format is appended to the \texttt{FILE\_CONTROL} that+is not present with other control types. This number indicates the index in+bytes from the beginning of the file at which the file sender should start+sending the file. The goal of this control type is to ensure that files can be+resumed across core restarts. Tox clients can know if they have received a+part of a file by using the file id and then using this packet to tell the+other side to start sending from the last received byte. If the seek position+is bigger or equal to the size of the file, the seek packet is invalid and the+one receiving it will discard it.++To accept a file Tox will therefore send a seek packet, if it is needed, and+then send a \texttt{FILE\_CONTROL} packet with \texttt{control\_type} 0 (accept)+to tell the file sender that the file was accepted.++Once the file transfer is accepted, the file sender will start sending file+data in sequential chunks from the beginning of the file (or the position from+the \texttt{FILE\_CONTROL} seek packet if one was received).++File data is sent using \texttt{FILE\_DATA} packets. The file number+corresponds to the file transfer that the file chunks belong to. The receiver+assumes that the file transfer is over as soon as a chunk with the file data+size not equal to the maximum size (1371 bytes) is received. This is how the+sender tells the receiver that the file transfer is complete in file transfers+where the size of the file is unknown (set to \texttt{UINT64\_MAX}). The+receiver also assumes that if the amount of received data equals to the file+size received in the \texttt{FILE\_SENDREQUEST}, the file sending is finished+and has been successfully received. Immediately after this occurs, the+receiver frees up the file number so that a new incoming file transfer can use+that file number. The implementation should discard any extra data received+which is larger than the file size received at the beginning.++In 0 filesize file transfers, the sender will send one \texttt{FILE\_DATA}+packet with a file data size of 0.++The sender will know if the receiver has received the file successfully by+checking if the friend has received the last \texttt{FILE\_DATA} packet sent+(containing the last chunk of the file). \texttt{net\_crypto} can be used to+check whether packets sent through it have been received by storing the packet+number of the sent packet and verifying later in \texttt{net\_crypto} to see+whether it was received or not. As soon as \texttt{net\_crypto} says the other+received the packet, the file transfer is considered successful, wiped and the+file number can be reused to send new files.++\texttt{FILE\_DATA} packets should be sent as fast as the \texttt{net\_crypto}+connection can handle it respecting its congestion control.++If the friend goes offline, all file transfers are cleared in toxcore. This+makes it simpler for toxcore as it does not have to deal with resuming file+transfers. It also makes it simpler for clients as the method for resuming+file transfers remains the same, even if the client is restarted or toxcore+loses the connection to the friend because of a bad internet connection.++\section{Group Chat Related Packets}++\begin{tabular}{l|l}+ Packet ID & Packet Name \\+ \hline+ 0x60 & \texttt{INVITE\_GROUPCHAT} \\+ 0x61 & \texttt{ONLINE\_PACKET} \\+ 0x62 & \texttt{DIRECT\_GROUPCHAT} \\+ 0x63 & \texttt{MESSAGE\_GROUPCHAT} \\+ 0xC7 & \texttt{LOSSY\_GROUPCHAT} \\+\end{tabular}++Messenger also takes care of saving the friends list and other friend+information so that it's possible to close and start toxcore while keeping all+your friends, your long term key and the information necessary to reconnect to+the network.++Important information messenger stores includes: the long term private key, our+current nospam value, our friends' public keys and any friend requests the user+is currently sending. The network DHT nodes, TCP relays and some onion nodes+are stored to aid reconnection.++In addition to this, a lot of optional data can be stored such as the usernames+of friends, our current username, status messages of friends, our status+message, etc... can be stored. The exact format of the toxcore save is+explained later.++The TCP server is run from the toxcore messenger module if the client has+enabled it. TCP server is usually run independently as part of the bootstrap+node package but it can be enabled in clients. If it is enabled in toxcore,+Messenger will add the running TCP server to the TCP relay.++Messenger is the module that transforms code that can connect to friends based+on public key into a real instant messenger.++\chapter{TCP client}++\texttt{TCP client} is the client for the TCP server. It establishes and keeps+a connection to the TCP server open.++All the packet formats are explained in detail in \texttt{TCP server} so this+section will only cover \texttt{TCP client} specific details which are not+covered in the \texttt{TCP server} documentation.++TCP clients can choose to connect to TCP servers through a proxy. Most common+types of proxies (SOCKS, HTTP) work by establishing a connection through a+proxy using the protocol of that specific type of proxy. After the connection+through that proxy to a TCP server is established, the socket behaves from the+point of view of the application exactly like a TCP socket that connects+directly to a TCP server instance. This means supporting proxies is easy.++\texttt{TCP client} first establishes a TCP connection, either through a proxy+or directly to a TCP server. It uses the DHT public key as its long term key+when connecting to the TCP server.++It establishes a secure connection to the TCP server. After establishing a+connection to the TCP server, and when the handshake response has been received+from the TCP server, the toxcore implementation immediately sends a ping+packet. Ideally the first packets sent would be routing request packets but+this solution aids code simplicity and allows the server to confirm the+connection.++Ping packets, like all other data packets, are sent as encrypted packets.++Ping packets are sent by the toxcore TCP client every 30 seconds with a timeout+of 10 seconds, the same interval and timeout as toxcore TCP server ping+packets. They are the same because they accomplish the same thing.++\texttt{TCP client} must have a mechanism to make sure important packets+(routing requests, disconnection notifications, ping packets, ping response+packets) don't get dropped because the TCP socket is full. Should this happen,+the TCP client must save these packets and prioritize sending them, in order,+when the TCP socket on the server becomes available for writing again.+\texttt{TCP client} must also take into account that packets might be bigger+than the number of bytes it can currently write to the socket. In this case,+it must save the bytes of the packet that it didn't write to the socket and+write them to the socket as soon as the socket allows so that the connection+does not get broken. It must also assume that it may receive only part of an+encrypted packet. If this occurs it must save the part of the packet it has+received and wait for the rest of the packet to arrive before handling it.++\texttt{TCP client} can be used to open up a route to friends who are connected+to the TCP server. This is done by sending a routing request to the TCP server+with the DHT public key of the friend. This tells the server to register a+\texttt{connection\_id} to the DHT public key sent in the packet. The server+will then respond with a routing response packet. If the connection was+accepted, the \texttt{TCP client} will store the \texttt{connection id} for+this connection. The \texttt{TCP client} will make sure that routing response+packets are responses to a routing packet that it sent by storing that it sent+a routing packet to that public key and checking the response against it. This+prevents the possibility of a bad TCP server exploiting the client.++The \texttt{TCP client} will handle connection notifications and disconnection+notifications by alerting the module using it that the connection to the peer+is up or down.++\texttt{TCP client} will send a disconnection notification to kill a connection+to a friend. It must send a disconnection notification packet regardless of+whether the peer was online or offline so that the TCP server will unregister+the connection.++Data to friends can be sent through the TCP relay using OOB (out of band)+packets and connected connections. To send an OOB packet, the DHT public key+of the friend must be known. OOB packets are sent in blind and there is no way+to query the TCP relay to see if the friend is connected before sending one.+OOB packets should be sent when the connection to the friend via the TCP relay+isn't in an connected state but it is known that the friend is connected to+that relay. If the friend is connected via the TCP relay, then normal data+packets must be sent as they are smaller than OOB packets.++OOB recv and data packets must be handled and passed to the module using it.++\chapter{TCP connections}++\texttt{TCP\_connections} takes care of handling multiple TCP client instances+to establish a reliable connection via TCP relays to a friend. Connecting to a+friend with only one relay would not be very reliable, so+\texttt{TCP\_connections} provides the level of abstraction needed to manage+multiple relays. For example, it ensures that if a relay goes down, the+connection to the peer will not be impacted. This is done by connecting to the+other peer with more than one relay.++\texttt{TCP\_connections} is above \href{#tcp-client}{\texttt{TCP client}} and+below \texttt{net\_crypto}.++A TCP connection in \texttt{TCP\_connections} is defined as a connection to a+peer though one or more TCP relays. To connect to another peer with+\texttt{TCP\_connections}, a connection in \texttt{TCP\_connections} to the peer+with DHT public key X will be created. Some TCP relays which we know the peer+is connected to will then be associated with that peer. If the peer isn't+connected directly yet, these relays will be the ones that the peer has sent to+us via the onion module. The peer will also send some relays it is directly+connected to once a connection is established, however, this is done by another+module.++\texttt{TCP\_connections} has a list of all relays it is connected to. It tries+to keep the number of relays it is connected to as small as possible in order+to minimize load on relays and lower bandwidth usage for the client. The+desired number of TCP relay connections per peer is set to 3 in toxcore with+the maximum number set to 6. The reason for these numbers is that 1 would mean+no backup relays and 2 would mean only 1 backup. To be sure that the+connection is reliable 3 seems to be a reasonable lower bound. The maximum+number of 6 is the maximum number of relays that can be tied to each peer. If+2 peers are connected each to the same 6+ relays and they both need to be+connected to that amount of relays because of other friends this is where this+maximum comes into play. There is no reason why this number is 6 but in+toxcore it has to be at least double than the desired number (3) because the+code assumes this.++If necessary, \texttt{TCP\_connections} will connect to TCP relays to use them+to send onion packets. This is only done if there is no UDP connection to the+network. When there is a UDP connection, packets are sent with UDP only+because sending them with TCP relays can be less reliable. It is also+important that we are connected at all times to some relays as these relays+will be used by TCP only peers to initiate a connection to us.++In toxcore, each client is connected to 3 relays even if there are no TCP peers+and the onion is not needed. It might be optimal to only connect to these+relays when toxcore is initializing as this is the only time when peers will+connect to us via TCP relays we are connected to. Due to how the onion works,+after the initialization phase, where each peer is searched in the onion and+then if they are found the info required to connect back (DHT pk, TCP relays)+is sent to them, there should be no more peers connecting to us via TCP relays.+This may be a way to further reduce load on TCP relays, however, more research+is needed before it is implemented.++\texttt{TCP\_connections} picks one relay and uses only it for sending data to+the other peer. The reason for not picking a random connected relay for each+packet is that it severely deteriorates the quality of the link between two+peers and makes performance of lossy video and audio transmissions really poor.+For this reason, one relay is picked and used to send all data. If for any+reason no more data can be sent through that relay, the next relay is used.+This may happen if the TCP socket is full and so the relay should not+necessarily be dropped if this occurs. Relays are only dropped if they time+out or if they become useless (if the relay is one too many or is no longer+being used to relay data to any peers).++\texttt{TCP\_connections} in toxcore also contains a mechanism to make+connections go to sleep. TCP connections to other peers may be put to sleep if+the connection to the peer establishes itself with UDP after the connection is+established with TCP. UDP is the method preferred by \texttt{net\_crypto} to+communicate with other peers. In order to keep track of the relays which were+used to connect with the other peer in case the UDP connection fails, they are+saved by \texttt{TCP\_connections} when the connection is put to sleep. Any+relays which were only used by this redundant connection are saved then+disconnected from. If the connection is awakened, the relays are reconnected+to and the connection is reestablished. Putting a connection to sleep is the+same as saving all the relays used by the connection and removing the+connection. Awakening the connection is the same as creating a new connection+with the same parameters and restoring all the relays.++A method to detect potentially dysfunctional relays that try to disrupt the+network by lying that they are connecting to a peer when they are not or that+maliciously drop all packets should be considered. Toxcore doesn't currently+implement such a system and adding one requires more research and likely also+requires extending the protocol.++When TCP connections connects to a relay it will create a new+\href{#tcp-client}{\texttt{TCP\_client}} instance for that relay. At any time+if the \texttt{TCP\_client} instance reports that it has disconnected, the TCP+relay will be dropped. Once the TCP relay reports that it is connected,+\texttt{TCP\_connections} will find all the connections that are associated to+the relay and announce to the relay that it wants to connect to each of them+with routing requests. If the relay reports that the peer for a connection is+online, the connection number and relay will be used to send data in that+connection with data packets. If the peer isn't reported as online but the+relay is associated to a connection, TCP OOB (out of band) packets will be used+to send data instead of data packets. TCP OOB packets are used in this case+since the relay most likely has the peer connected but it has not sent a+routing request to connect to us.++\texttt{TCP\_connections} is used as the bridge between individual+\texttt{TCP\_client} instances and \texttt{net\_crypto}, or the bridge between+individual connections and something that requires an interface that looks like+one connection.++\chapter{TCP server}++The TCP server in tox has the goal of acting like a TCP relay between clients+who cannot connect directly to each other or who for some reason are limited to+using the TCP protocol to connect to each other. \texttt{TCP\_server} is+typically run only on actual server machines but any Tox client could host one+as the api to run one is exposed through the tox.h api.++To connect to a hosted TCP server toxcore uses the TCP client module.++The TCP server implementation in toxcore can currently either work on epoll on+linux or using unoptimized but portable socket polling.++TCP connections between the TCP client and the server are encrypted to prevent+an outsider from knowing information like who is connecting to whom just be+looking at someones connection to a TCP server. This is useful when someone+connects though something like Tor for example. It also prevents someone from+injecting data in the stream and makes it so we can assume that any data+received was not tampered with and is exactly what was sent by the client.++When a client first connects to a TCP server he opens up a TCP connection to+the ip and port the TCP server is listening on. Once the connection is+established he then sends a handshake packet, the server then responds with his+own and a secure connection is established. The connection is then said to be+unconfirmed and the client must then send some encrypted data to the server+before the server can mark the connection as confirmed. The reason it works+like this is to prevent a type of attack where a peer would send a handshake+packet and then time out right away. To prevent this the server must wait a+few seconds for a sign that the client received his handshake packet before+confirming the connection. The both can then communicate with each other using+the encrypted connection.++The TCP server essentially acts as just a relay between 2 peers. When a TCP+client connects to the server he tells the server which clients he wants the+server to connect him to. The server will only let two clients connect to each+other if both have indicated to the server that they want to connect to each+other. This is to prevent non friends from checking if someone is connected to+a TCP server. The TCP server supports sending packets blindly through it to+clients with a client with public key X (OOB packets) however the TCP server+does not give any feedback or anything to say if the packet arrived or not and+as such it is only useful to send data to friends who may not know that we are+connected to the current TCP server while we know they are. This occurs when+one peer discovers the TCP relay and DHT public key of the other peer before+the other peer discovers its DHT public key. In that case OOB packets would be+used until the other peer knows that the peer is connected to the relay and+establishes a connection through it.++In order to make toxcore work on TCP only the TCP server supports relaying+onion packets from TCP clients and sending any responses from them to TCP+clients.++To establish a secure connection with a TCP server send the following 128 bytes+of data or handshake packet to the server:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{32} & DHT public key of client \\+ \texttt{24} & Nonce for the encrypted data \\+ \texttt{72} & Payload (plus MAC) \\+\end{tabular}++Payload is encrypted with the DHT private key of the client and public key of+the server and the nonce:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{32} & Public key \\+ \texttt{24} & Base nonce \\+\end{tabular}++The base nonce is the one TCP client wants the TCP server to use to decrypt the+packets received from the TCP client.++The first 32 bytes are the public key (DHT public key) that the TCP client is+announcing itself to the server with. The next 24 bytes are a nonce which the+TCP client uses along with the secret key associated with the public key in the+first 32 bytes of the packet to encrypt the rest of this 'packet'. The+encrypted part of this packet contains a temporary public key that will be used+for encryption during the connection and will be discarded after. It also+contains a base nonce which will be used later for decrypting packets received+from the TCP client.++If the server decrypts successfully the encrypted data in the handshake packet+and responds with the following handshake response of length 96 bytes:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{24} & Nonce for the encrypted data \\+ \texttt{72} & Payload (plus MAC) \\+\end{tabular}++Payload is encrypted with the private key of the server and the DHT public key+of the client and the nonce:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{32} & Public key \\+ \texttt{24} & Base nonce \\+\end{tabular}++The base nonce is the one the TCP server wants the TCP client to use to decrypt+the packets received from the TCP server.++The client already knows the long term public key of the server so it is+omitted in the response, instead only a nonce is present in the unencrypted+part. The encrypted part of the response has the same elements as the+encrypted part of the request: a temporary public key tied to this connection+and a base nonce which will be used later when decrypting packets received from+the TCP client both unique for the connection.++In toxcore the base nonce is generated randomly like all the other nonces, it+must be randomly generated to prevent nonce reuse. For example if the nonce+used was 0 for both sides since both sides use the same keys to encrypt packets+they send to each other, two packets would be encrypted with the same nonce.+These packets could then be possibly replayed back to the sender which would+cause issues. A similar mechanism is used in \texttt{net\_crypto}.++After this the client will know the connection temporary public key and base+nonce of the server and the server will know the connection base nonce and+temporary public key of the client.++The client will then send an encrypted packet to the server, the contents of+the packet do not matter and it must be handled normally by the server (ex: if+it was a ping send a pong response. The first packet must be any valid+encrypted data packet), the only thing that does matter is that the packet was+encrypted correctly by the client because it means that the client has+correctly received the handshake response the server sent to it and that the+handshake the client sent to the server really came from the client and not+from an attacker replaying packets. The server must prevent resource consuming+attacks by timing out clients if they do not send any encrypted packets so the+server to prove to the server that the connection was established correctly.++Toxcore does not have a timeout for clients, instead it stores connecting+clients in large circular lists and times them out if their entry in the list+gets replaced by a newer connection. The reasoning behind this is that it+prevents TCP flood attacks from having a negative impact on the currently+connected nodes. There are however much better ways to do this and the only+reason toxcore does it this way is because writing it was very simple. When+connections are confirmed they are moved somewhere else.++When the server confirms the connection he must look in the list of connected+peers to see if he is already connected to a client with the same announced+public key. If this is the case the server must kill the previous connection+because this means that the client previously timed out and is reconnecting.+Because of Toxcore design it is very unlikely to happen that two legitimate+different peers will have the same public key so this is the correct behavior.++Encrypted data packets look like this to outsiders:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{2} & \texttt{uint16\_t} length of data \\+ variable & encrypted data \\+\end{tabular}++In a TCP stream they would look like:+\texttt{[[length][data]][[length][data]][[length][data]]...}.++Both the client and server use the following (temp public and private (client+and server) connection keys) which are each generated for the connection and+then sent to the other in the handshake and sent to the other. They are then+used like the next diagram shows to generate a shared key which is equal on+both sides.++\begin{verbatim}+Client: Server:+generate_shared_key( generate_shared_key(+[temp connection public key of server], [temp connection public key of client],+[temp connection private key of client]) [temp connection private key of server])+= =+[shared key] [shared key]+\end{verbatim}++The generated shared key is equal on both sides and is used to encrypt and+decrypt the encrypted data packets.++each encrypted data packet sent to the client will be encrypted with the shared+key and with a nonce equal to: (client base nonce + number of packets sent so+for the first packet it is (starting at 0) nonce + 0, the second is nonce + 1+and so on. Note that nonces like all other numbers sent over the network in+toxcore are numbers in big endian format so when increasing them by 1 the least+significant byte is the last one)++each packet received from the client will be decrypted with the shared key and+with a nonce equal to: (server base nonce + number of packets sent so for the+first packet it is (starting at 0) nonce + 0, the second is nonce + 1 and so+on. Note that nonces like all other numbers sent over the network in toxcore+are numbers in big endian format so when increasing them by 1 the least+significant byte is the last one)++Encrypted data packets have a hard maximum size of 2 + 2048 bytes in the+toxcore TCP server implementation, 2048 bytes is big enough to make sure that+all toxcore packets can go through and leaves some extra space just in case the+protocol needs to be changed in the future. The 2 bytes represents the size of+the data length and the 2048 bytes the max size of the encrypted part. This+means the maximum size is 2050 bytes. In current toxcore, the largest+encrypted data packets sent will be of size 2 + 1417 which is 1419 total.++The logic behind the format of the handshake is that we:++\begin{enumerate}+\item need to prove to the server that we own the private key related to the public+ key we are announcing ourselves with.+\item need to establish a secure connection that has perfect forward secrecy+\item prevent any replay, impersonation or other attacks+\end{enumerate}++How it accomplishes each of those points:++\begin{enumerate}+ \item If the client does not own the private key related to the public key they+ will not be able to create the handshake packet.+ \item Temporary session keys generated by the client and server in the encrypted+ part of the handshake packets are used to encrypt/decrypt packets during the+ session.+ \item The following attacks are prevented:+ \begin{itemize}+ \item Attacker modifies any byte of the handshake packets: Decryption fail, no+ attacks possible.+ \item Attacker captures the handshake packet from the client and replays it+ later to the server: Attacker will never get the server to confirm the+ connection (no effect).+ \item Attacker captures a server response and sends it to the client next time+ they try to connect to the server: Client will never confirm the+ connection. (See: \texttt{TCP\_client})+ \item Attacker tries to impersonate a server: They won't be able to decrypt the+ handshake and won't be able to respond.+ \item Attacker tries to impersonate a client: Server won't be able to decrypt+ the handshake.+ \end{itemize}+\end{enumerate}++The logic behind the format of the encrypted packets is that:++\begin{enumerate}+ \item TCP is a stream protocol, we need packets.+ \item Any attacks must be prevented+\end{enumerate}++How it accomplishes each of those points:++\begin{enumerate}+ \item 2 bytes before each packet of encrypted data denote the length. We assume a+ functioning TCP will deliver bytes in order which makes it work. If the TCP+ doesn't it most likely means it is under attack and for that see the next+ point.+ \item The following attacks are prevented:+ \begin{itemize}+ \item Modifying the length bytes will either make the connection time out+ and/or decryption fail.+ \item Modifying any encrypted bytes will make decryption fail.+ \item Injecting any bytes will make decryption fail.+ \item Trying to re order the packets will make decryption fail because of the+ ordered nonce.+ \item Removing any packets from the stream will make decryption fail because of+ the ordered nonce.+ \end{itemize}+\end{enumerate}++\section{Encrypted payload types}++The folowing represents the various types of data that can be sent inside+encrypted data packets.++\subsection{Routing request (0x00)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x00) \\+ \texttt{32} & Public key \\+\end{tabular}++\subsection{Routing request response (0x01)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x01) \\+ \texttt{1} & \texttt{uint8\_t} rpid \\+ \texttt{32} & Public key \\+\end{tabular}++rpid is invalid \texttt{connection\_id} (0) if refused, \texttt{connection\_id} if accepted.++\subsection{Connect notification (0x02)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x02) \\+ \texttt{1} & \texttt{uint8\_t} \texttt{connection\_id} of connection that got connected \\+\end{tabular}++\subsection{Disconnect notification (0x03)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x03) \\+ \texttt{1} & \texttt{uint8\_t} \texttt{connection\_id} of connection that got disconnected \\+\end{tabular}++\subsection{Ping packet (0x04)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x04) \\+ \texttt{8} & \texttt{uint64\_t} \texttt{ping\_id} (0 is invalid) \\+\end{tabular}++\subsection{Ping response (pong) (0x05)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x05) \\+ \texttt{8} & \texttt{uint64\_t} \texttt{ping\_id} (0 is invalid) \\+\end{tabular}++\subsection{OOB send (0x06)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x06) \\+ \texttt{32} & Destination public key \\+ variable & Data \\+\end{tabular}++\subsection{OOB recv (0x07)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x07) \\+ \texttt{32} & Sender public key \\+ variable & Data \\+\end{tabular}++\subsection{Onion packet (0x08)}++Same format as initial onion packet but packet id is 0x08 instead of 0x80.++\subsection{Onion packet response (0x09)}++Same format as onion packet but packet id is 0x09 instead of 0x8e.++\subsection{Data (0x10 and up)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} packet id \\+ \texttt{1} & \texttt{uint8\_t} connection id \\+ variable & data \\+\end{tabular}++The TCP server is set up in a way to minimize waste while relaying the many+packets that might go between two tox peers hence clients must create+connections to other clients on the relay. The connection number is a+\texttt{uint8\_t} and must be equal or greater to 16 in order to be valid.+Because a \texttt{uint8\_t} has a maximum value of 256 it means that the maximum+number of different connections to other clients that each connection can have+is 240. The reason valid \texttt{connection\_ids} are bigger than 16 is because+they are the first byte of data packets. Currently only number 0 to 9 are+taken however we keep a few extras in case we need to extend the protocol+without breaking it completely.++Routing request (Sent by client to server): Send a routing request to the+server that we want to connect to peer with public key where the public key is+the public the peer announced themselves as. The server must respond to this+with a Routing response.++Routing response (Sent by server to client): The response to the routing+request, tell the client if the routing request succeeded (valid+\texttt{connection\_id}) and if it did, tell them the id of the connection+(\texttt{connection\_id}). The public key sent in the routing request is also+sent in the response so that the client can send many requests at the same time+to the server without having code to track which response belongs to which+public key.++The only reason a routing request should fail is if the connection has reached+the maximum number of simultaneous connections. In case the routing request+fails the public key in the response will be the public key in the failed+request.++Connect notification (Sent by server to client): Tell the client that+\texttt{connection\_id} is now connected meaning the other is online and data+can be sent using this \texttt{connection\_id}.++Disconnect notification (Sent by client to server): Sent when client wants the+server to forget about the connection related to the \texttt{connection\_id} in+the notification. Server must remove this connection and must be able to reuse+the \texttt{connection\_id} for another connection. If the connection was+connected the server must send a disconnect notification to the other client.+The other client must think that this client has simply disconnected from the+TCP server.++Disconnect notification (Sent by server to client): Sent by the server to the+client to tell them that the connection with \texttt{connection\_id} that was+connected is now disconnected. It is sent either when the other client of the+connection disconnect or when they tell the server to kill the connection (see+above).++Ping and Pong packets (can be sent by both client and server, both will+respond): ping packets are used to know if the other side of the connection is+still live. TCP when established doesn't have any sane timeouts (1 week isn't+sane) so we are obliged to have our own way to check if the other side is still+live. Ping ids can be anything except 0, this is because of how toxcore sets+the variable storing the \texttt{ping\_id} that was sent to 0 when it receives a+pong response which means 0 is invalid.++The server should send ping packets every X seconds (toxcore+\texttt{TCP\_server} sends them every 30 seconds and times out the peer if it+doesn't get a response in 10). The server should respond immediately to ping+packets with pong packets.++The server should respond to ping packets with pong packets with the same+\texttt{ping\_id} as was in the ping packet. The server should check that each+pong packet contains the same \texttt{ping\_id} as was in the ping, if not the+pong packet must be ignored.++OOB send (Sent by client to server): If a peer with private key equal to the+key they announced themselves with is connected, the data in the OOB send+packet will be sent to that peer as an OOB recv packet. If no such peer is+connected, the packet is discarded. The toxcore \texttt{TCP\_server}+implementation has a hard maximum OOB data length of 1024. 1024 was picked+because it is big enough for the \texttt{net\_crypto} packets related to the+handshake and is large enough that any changes to the protocol would not+require breaking TCP server. It is however not large enough for the biggest+\texttt{net\_crypto} packets sent with an established \texttt{net\_crypto}+connection to prevent sending those via OOB packets.++OOB recv (Sent by server to client): OOB recv are sent with the announced+public key of the peer that sent the OOB send packet and the exact data.++OOB packets can be used just like normal data packets however the extra size+makes sending data only through them less efficient than data packets.++Data: Data packets can only be sent and received if the corresponding+\texttt{connection\_id} is connection (a Connect notification has been received+from it) if the server receives a Data packet for a non connected or existent+connection it will discard it.++Why did I use different packet ids for all packets when some are only sent by+the client and some only by the server? It's less confusing.++\chapter{Friend connection}++\texttt{friend\_connection} is the module that sits on top of the DHT, onion and+\texttt{net\_crypto} modules and takes care of linking the 3 together.++Friends in \texttt{friend\_connection} are represented by their real public key.+When a friend is added in \texttt{friend\_connection}, an onion search entry is+created for that friend. This means that the onion module will start looking+for this friend and send that friend their DHT public key, and the TCP relays+it is connected to, in case a connection is only possible with TCP.++Once the onion returns the DHT public key of the peer, the DHT public key is+saved, added to the DHT friends list and a new \texttt{net\_crypto} connection+is created. Any TCP relays returned by the onion for this friend are passed to+the \texttt{net\_crypto} connection.++If the DHT establishes a direct UDP connection with the friend,+\texttt{friend\_connection} will pass the IP/port of the friend to+\texttt{net\_crypto} and also save it to be used to reconnect to the friend if+they disconnect.++If \texttt{net\_crypto} finds that the friend has a different DHT public key,+which can happen if the friend restarted their client, \texttt{net\_crypto} will+pass the new DHT public key to the onion module and will remove the DHT entry+for the old DHT public key and replace it with the new one. The current+\texttt{net\_crypto} connection will also be killed and a new one with the+correct DHT public key will be created.++When the \texttt{net\_crypto} connection for a friend goes online,+\texttt{friend\_connection} will tell the onion module that the friend is online+so that it can stop spending resources looking for the friend. When the friend+connection goes offline, \texttt{friend\_connection} will tell the onion module+so that it can start looking for the friend again.++There are 2 types of data packets sent to friends with the \texttt{net\_crypto}+connection handled at the level of \texttt{friend\_connection}, Alive packets+and TCP relay packets. Alive packets are packets with the packet id or first+byte of data (only byte in this packet) being 16. They are used in order to+check if the other friend is still online. \texttt{net\_crypto} does not have+any timeout when the connection is established so timeouts are caught using+this packet. In toxcore, this packet is sent every 8 seconds. If none of+these packets are received for 32 seconds, the connection is timed out and+killed. These numbers seem to cause the least issues and 32 seconds is not too+long so that, if a friend times out, toxcore won't falsely see them online for+too long. Usually when a friend goes offline they have time to send a+disconnect packet in the \texttt{net\_crypto} connection which makes them appear+offline almost instantly.++The timeout for when to stop retrying to connect to a friend by creating new+\texttt{net\_crypto} connections when the old one times out in toxcore is the+same as the timeout for DHT peers (122 seconds). However, it is calculated+from the last time a DHT public key was received for the friend or time the+friend's \texttt{net\_crypto} connection went offline after being online. The+highest time is used to calculate when the timeout is. \texttt{net\_crypto}+connections will be recreated (if the connection fails) until this timeout.++\texttt{friend\_connection} sends a list of 3 relays (the same number as the+target number of TCP relay connections in \texttt{TCP\_connections}) to each+connected friend every 5 minutes in toxcore. Immediately before sending the+relays, they are associated to the current \texttt{net\_crypto->TCP\_connections}+connection. This facilitates connecting the two friends together using the+relays as the friend who receives the packet will associate the sent relays to+the \texttt{net\_crypto} connection they received it from. When both sides do+this they will be able to connect to each other using the relays. The packet+id or first byte of the packet of share relay packets is 0x11. This is then+followed by some TCP relays stored in packed node format.++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x11) \\+ variable & TCP relays in packed node format (see DHT) \\+\end{tabular}++If local IPs are received as part of the packet, the local IP will be replaced+with the IP of the peer that sent the relay. This is because we assume this is+the best way to attempt to connect to the TCP relay. If the peer that sent the+relay is using a local IP, then the sent local IP should be used to connect to+the relay.++For all other data packets, are passed by \texttt{friend\_connection} up to the+upper Messenger module. It also separates lossy and lossless packets from+\texttt{net\_crypto}.++Friend connection takes care of establishing the connection to the friend and+gives the upper messenger layer a simple interface to receive and send+messages, add and remove friends and know if a friend is connected (online) or+not connected (offline).++\chapter{Friend requests}++When a Tox user adds someone with Tox, toxcore will try sending a friend+request to that person. A friend request contains the long term public key of+the sender, a nospam number and a message.++Transmitting the long term public key is the primary goal of the friend request+as it is what the peer needs to find and establish a connection to the sender.+The long term public key is what the receiver adds to his friends list if he+accepts the friend request.++The nospam is a number used to prevent someone from spamming the network with+valid friend requests. It makes sure that the only people who have seen the+Tox ID of a peer are capable of sending them a friend request. The nospam is+one of the components of the Tox ID.++The nospam is a number or a list of numbers set by the peer, only received+friend requests that contain a nospam that was set by the peer are sent to the+client to be accepted or refused by the user. The nospam prevents random peers+in the network from sending friend requests to non friends. The nospam is not+long enough to be secure meaning an extremely resilient attacker could manage+to send a spam friend request to someone. 4 bytes is large enough to prevent+spam from random peers in the network. The nospam could also allow Tox users+to issue different Tox IDs and even change Tox IDs if someone finds a Tox ID+and decides to send it hundreds of spam friend requests. Changing the nospam+would stop the incoming wave of spam friend requests without any negative+effects to the users friends list. For example if users would have to change+their public key to prevent them from receiving friend requests it would mean+they would have to essentially abandon all their current friends as friends are+tied to the public key. The nospam is not used at all once the friends have+each other added which means changing it won't have any negative effects.++Friend request:++\begin{verbatim}+[uint32_t nospam][Message (UTF8) 1 to ONION_CLIENT_MAX_DATA_SIZE bytes]+\end{verbatim}++Friend request packet when sent as an onion data packet:++\begin{verbatim}+[uint8_t (32)][Friend request]+\end{verbatim}++Friend request packet when sent as a \texttt{net\_crypto} data packet (If we are+directly connected to the peer because of a group chat but are not friends with+them):++\begin{verbatim}+[uint8_t (18)][Friend request]+\end{verbatim}++When a friend is added to toxcore with their Tox ID and a message, the friend+is added in \texttt{friend\_connection} and then toxcore tries to send friend+requests.++When sending a friend request, toxcore will check if the peer which a friend+request is being sent to is already connected to using a \texttt{net\_crypto}+connection which can happen if both are in the same group chat. If this is the+case the friend request will be sent as a \texttt{net\_crypto} packet using that+connection. If not, it will be sent as an onion data packet.++Onion data packets contain the real public key of the sender and if a+\texttt{net\_crypto} connection is established it means the peer knows our real+public key. This is why the friend request does not need to contain the real+public key of the peer.++Friend requests are sent with exponentially increasing interval of 2 seconds, 4+seconds, 8 seconds, etc... in toxcore. This is so friend requests get resent+but eventually get resent in intervals that are so big that they essentially+expire. The sender has no way of knowing if a peer refuses a friend requests+which is why friend requests need to expire in some way. Note that the+interval is the minimum timeout, if toxcore cannot send that friend request it+will try again until it manages to send it. One reason for not being able to+send the friend request would be that the onion has not found the friend in the+onion and so cannot send an onion data packet to them.++Received friend requests are passed to the client, the client is expected to+show the message from the friend request to the user and ask the user if they+want to accept the friend request or not. Friend requests are accepted by+adding the peer sending the friend request as a friend and refused by simply+ignoring it.++Friend requests are sent multiple times meaning that in order to prevent the+same friend request from being sent to the client multiple times toxcore keeps+a list of the last real public keys it received friend requests from and+discards any received friend requests that are from a real public key that is+in that list. In toxcore this list is a simple circular list. There are many+ways this could be improved and made more efficient as a circular list isn't+very efficient however it has worked well in toxcore so far.++Friend requests from public keys that are already added to the friends list+should also be discarded.++\chapter{Group}++Group chats in Tox work by temporarily adding some peers present in the group+chat as temporary \texttt{friend\_connection} friends, that are deleted when the+group chat is exited.++Each peer in the group chat is identified by their real long term public key.+Peers also transmit their DHT public keys to each other via the group chat in+order to speed up the connection by making it unnecessary for the peers to find+each other's DHT public keys with the onion, as would happen had they added each+other as normal friends.++The upside of using \texttt{friend\_connection} is that group chats do not have+to deal with things like hole punching, peers only on TCP or other low level+networking things. The downside however is that every single peer knows each+other's real long term public key and DHT public key, meaning that these group+chats should only be used between friends.++Each peer adds a \texttt{friend\_connection} for each of up to 4 other peers in+the group. If the group chat has 5 participants or fewer, each of the peers will+therefore have each of the others added to their list of friend connections, and+a peer wishing to send a message to the group may communicate it directly to the+other peers. When there are more than 5 peers, messages are relayed along friend+connections.++Since the maximum number of peers per groupchat that will be connected to with+friend connections is 4, if the peers in the groupchat are arranged in a circle+and each peer connects to the 2 peers that are closest to the right of them and+the 2 peers that are closest to the left of them, then the peers should form a+well-connected circle of peers.++Group chats in toxcore do this by subtracting the real long term public key of+the peer with all the others in the group (our PK - other peer PK), using+modular arithmetic, and finding the two peers for which the result of this+operation is the smallest. The operation is then inversed (other peer PK - our+PK) and this operation is done again with all the public keys of the peers in+the group. The 2 peers for which the result is again the smallest are picked.++This gives 4 peers that are then added as a friend connection and associated to+the group. If every peer in the group does this, they will form a circle of+perfectly connected peers.++Once the peers are connected to each other in a circle they relay each other's+messages. Every time a peer leaves the group or a new peer joins, each member+of the chat will recalculate the peers they should connect to.++To join a group chat, a peer must first be invited to it by their friend. To+make a groupchat the peer will first create a groupchat and then invite people+to this group chat. Once their friends are in the group chat, those friends can+invite their other friends to the chat, and so on.++To create a group chat, a peer generates a random 32 byte id that is used to+uniquely identify the group chat. 32 bytes is enough so that when randomly+generated with a secure random number generator every groupchat ever created+will have a different id. The goal of this 32 byte id is so that peers have a+way of identifying each group chat, so that they can prevent themselves from+joining a groupchat twice for example.++The groupchat will also have an unsigned 1 byte type. This type indicates what+kind of groupchat the groupchat is. The current types are:++\begin{tabular}{l|l}+ Type number & Type \\+ \hline+ \texttt{0} & text \\+ \texttt{1} & audio \\+\end{tabular}++Text groupchats are text only, while audio indicates that the groupchat supports+sending audio to it as well as text.++The groupchat will also be identified by a unique unsigned 2 byte integer, which+in toxcore corresponds to the index of the groupchat in the array it is being+stored in. Every groupchat in the current instance must have a different+number. This number is used by groupchat peers that are directly connected to+us to tell us which packets are for which groupchat. Every groupchat packet+contains a 2 byte groupchat number. Putting a 32 byte groupchat id in each+packet would increase bandwidth waste by a lot, and this is the reason why+groupchat numbers are used instead.++Using the group number as the index of the array used to store the groupchat+instances is recommended, because this kind of access is usually most efficient+and it ensures that each groupchat has a unique group number.++When creating a new groupchat, the peer will add themselves as a groupchat peer+with a peer number of 0 and their own long term public key and DHT public key.++Invite packets:++Invite packet:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x60) \\+ \texttt{1} & \texttt{uint8\_t} (0x00) \\+ \texttt{2} & \texttt{uint16\_t} group number \\+ \texttt{33} & Group chat identifier \\+\end{tabular}++Accept Invite packet:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x60) \\+ \texttt{1} & \texttt{uint8\_t} (0x01) \\+ \texttt{2} & \texttt{uint16\_t} group number (local) \\+ \texttt{2} & \texttt{uint16\_t} group number to join \\+ \texttt{33} & Group chat identifier \\+\end{tabular}++Member Information packet:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x60) \\+ \texttt{1} & \texttt{uint8\_t} (0x02) \\+ \texttt{2} & \texttt{uint16\_t} group number (local) \\+ \texttt{2} & \texttt{uint16\_t} group number to join \\+ \texttt{33} & Group chat identifier \\+ \texttt{2} & \texttt{uint16\_t} peer number \\+\end{tabular}++A group chat identifier consists of a 1-byte type and a 32-byte id concatenated:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} type \\+ \texttt{32} & \texttt{uint8\_t} groupchat id \\+\end{tabular}++To invite a friend to a group chat, an invite packet is sent to the friend.+These packets are sent using Messenger (if you look at the Messenger packet id+section, all the groupchat packet ids are in there). Note that all numbers+here, like all numbers sent using Tox packets, are sent in big endian format.++The group chat number is as explained above, the number used to uniquely+identify the groupchat instance from all the other groupchat instances the peer+has. It is sent in the invite packet because it is needed by the friend in+order to send back groupchat related packets.++What follows is the 33 byte group chat identifier.++To refuse the invite, the friend receiving it will simply ignore and discard+it.++To accept the invite, the friend will create their own groupchat instance with+the 1 byte type and 32 byte groupchat id sent in the request, and send an invite+accept packet back. The friend will also add the peer who sent the invite as+a groupchat connection, and mark the connection as introducing the friend.++If the friend being invited is already in the group, they will respond with a+member information packet, add the peer who sent the invite as a groupchat+connection, and mark the connection as introducing both the friend and the+peer who sent the invite.++The first group number in the invite accept packet is the group number of the+groupchat the invited friend just created. The second group number is the+group number that was sent in the invite request. What follows is the 33 byte+group chat identifier which was sent in the invite request. The member+information packet is the same, but includes also the current peer number of+the invited friend.++When a peer receives an invite accept packet they will check if the group+identifier sent back corresponds to the group identifier of the groupchat with+the group number also sent back. If so, a new peer number will be generated for+the peer that sent the invite accept packet. Then the peer with their+generated peer number, their long term public key and their DHT public key will+be added to the peer list of the groupchat. A new peer message packet will also+be sent to tell everyone in the group chat about the new peer. The peer will+also be added as a groupchat connection, and the connection will be marked as+introducing the peer.++When a peer receives a member information packet they proceed as with an+invite accept packet, but use the peer number in the packet rather than+generating a new one, and mark the new connection as also introducing the peer+receiving the member information packet.++Peer numbers are used to uniquely identify each peer in the group chat. They+are used in groupchat message packets so that peers receiving them can know who+or which groupchat peer sent them. As groupchat message packets are relayed,+they must contain something that is used by others to identify the sender. Since+putting a 32 byte public key in each packet would be wasteful, a 2 byte peer+number is used instead. Each peer in the groupchat has a unique peer number.+Toxcore generates each peer number randomly but makes sure newly generated peer+numbers are not equal to current ones already used by other peers in the group+chat. If two peers join the groupchat from two different endpoints there is a+small possibility that both will be given the same peer number, but the+probability of this occurring is low enough in practice that it is not an issue.++Peer online packet:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x61) \\+ \texttt{2} & \texttt{uint16\_t} group number (local) \\+ \texttt{33} & Group chat identifier \\+\end{tabular}++Peer introduced packet:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x62) \\+ \texttt{2} & \texttt{uint16\_t} group number (local) \\+ \texttt{1} & \texttt{uint8\_t} (0x01) \\+\end{tabular}++For a groupchat connection to work, both peers in the groupchat must be+attempting to connect directly to each other.++Groupchat connections are established when both peers who want to connect to+each other either create a new friend connection to connect to each other or+reuse an existing friend connection that connects them together (if they are+friends or already are connected together because of another group chat).++As soon as the connection to the other peer is opened, a peer online packet is+sent to the peer. The goal of the online packet is to tell the peer that we+want to establish the groupchat connection with them and tell them the+groupchat number of our groupchat instance. The peer online packet contains+the group number and the 33 byte group chat identifier. The group number is the+group number the peer has for the group with the group id sent in the packet.++When both sides send an online packet to the other peer, a connection is+established.++When an online packet is received from a peer, if the connection to the peer+is already established (an online packet has been already received), or if+there is no group connection to that peer being established, the packet is+dropped. Otherwise, the group number to communicate with the group via the+peer is saved, the connection is considered established, and an online packet+is sent back to the peer. A ping message is sent to the group. If this is the+first group connection to that group we establish, or the connection is marked+as introducing us, we send a peer query packet back to the peer. This is so+we can get the list of peers from the group. If the connection is marked as+introducing the peer, we send a new peer message to the group announcing the+peer, and a name message reannouncing our name.++A groupchat connection can be marked as introducing one or both of the peers it+connects, to indicate that the connection should be maintained until that peer+is well connected to the group. A peer maintains a groupchat connection to a+second peer as long as the second peer is one of the four closest peers in the+groupchat to the first, or the connection is marked as introducing a peer who+still requires the connection. A peer requires a groupchat connection to a+second peer which introduces the first peer until the first peer has more than+4 groupchat connections and receives a message from the second peer via a+different groupchat connection. The first peer then sends a peer introduced+packet to the second peer to indicate that they no longer require the+connection.++Peer query packet:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x62) \\+ \texttt{2} & \texttt{uint16\_t} group number \\+ \texttt{1} & \texttt{uint8\_t} (0x08) \\+\end{tabular}++Peer response packet:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x62) \\+ \texttt{2} & \texttt{uint16\_t} group number \\+ \texttt{1} & \texttt{uint8\_t} (0x09) \\+ variable & Repeated times number of peers: Peer info \\+\end{tabular}++The Peer info structure is as follows:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{2} & \texttt{uint16\_t} peer number \\+ \texttt{32} & Long term public key \\+ \texttt{32} & DHT public key \\+ \texttt{1} & \texttt{uint8\_t} Name length \\+ \texttt{[0, 255]} & Name \\+\end{tabular}++Title response packet:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x62) \\+ \texttt{2} & \texttt{uint16\_t} group number \\+ \texttt{1} & \texttt{uint8\_t} (0x0a) \\+ variable & Title \\+\end{tabular}++Message packets:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x63) \\+ \texttt{2} & \texttt{uint16\_t} group number \\+ \texttt{2} & \texttt{uint16\_t} peer number \\+ \texttt{4} & \texttt{uint32\_t} message number \\+ \texttt{1} & \texttt{uint8\_t} with a value representing id of message \\+ variable & Data \\+\end{tabular}++Lossy Message packets:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0xc7) \\+ \texttt{2} & \texttt{uint16\_t} group number \\+ \texttt{2} & \texttt{uint16\_t} peer number \\+ \texttt{2} & \texttt{uint16\_t} message number \\+ \texttt{1} & \texttt{uint8\_t} with a value representing id of message \\+ variable & Data \\+\end{tabular}++If a peer query packet is received, the receiver takes their list of peers and+creates a peer response packet which is then sent to the other peer. If there+are too many peers in the group chat and the peer response packet would be+larger than the maximum size of friend connection packets (1373 bytes), more+than one peer response packet is sent back. A Title response packet is also+sent back. This is how the peer that joins a group chat finds out the list of+peers in the group chat and the title of the group chat right after joining.++Peer response packets are straightforward and contain the information for each+peer (peer number, real public key, DHT public key, name) appended to each+other. The title response is also straightforward.++Both the maximum length of groupchat peer names and the groupchat title is 128+bytes. This is the same maximum length as names in all of toxcore.++When a peer receives a peer response packet, they will add each of the+received peers to their groupchat peer list, find the 4 closest peers to them+and create groupchat connections to them as was explained previously. The DHT+public key of an already known peer is updated to one given in the response+packet if the peer is frozen, or if it has been frozen since its DHT public+key was last updated.++When a peer receives a title response packet, they update the title for the+groupchat accordingly if the title has not already been set, or if since it+was last set there has been a time at which all peers were frozen.++If the peer does not yet know their own peer number, as is the case if they+have just accepted an invitation, the peer will find themselves in the list of+received peers and use the peer number assigned to them as their own. They are+then able to send messages and invite other peers to the groupchat. They+immediately send a name message to announce their name to the group.++Message packets are used to send messages to all peers in the groupchat. To+send a message packet, a peer will first take their peer number and the message+they want to send. Each message packet sent will have a message number that is+equal to the last message number sent + 1. Like all other numbers (group chat+number, peer number) in the packet, the message number in the packet will be in+big endian format.++When a Message packet is received, the peer receiving it will first check that+the peer number of the sender is in their peer list. If not, the peer ignores+the message but sends a peer query packet to the peer the packet was directly+received from. That peer should have the message sender in their peer list,+and so will send the sender's peer info back in a peer response.++If the sender is in the receiver's peer list, the receiver now checks whether+they have already seen a message with the same sender and message number. This+is achieved by storing the 8 greatest message numbers received from a given+sender peer number. If the message has lesser message number than any of those+8, it is assumed to have been received. If the message has already been+received according to this check, or if it is a name or title message and+another message of the same type from the same sender with a greater message+number has been received, then the packet is discarded. Otherwise, the+message is processed as described below, and a Message packet with the message+is sent (relayed) to all current group connections except the one that it was+received from, and also to that one if that peer is the original sender of the+message. The only thing that should change in the Message packet as it is+relayed is the group number.++Lossy message packets are used to send audio packets to others in audio group+chats. Lossy packets work the same way as normal relayed groupchat messages in+that they are relayed to everyone in the group chat until everyone has them, but+there are a few differences. Firstly, the message number is only a 2 byte+integer. When receiving a lossy packet from a peer the receiving peer will first+check if a message with that message number was already received from that peer.+If it wasn't, the packet will be added to the list of received packets and then+the packet will be passed to its handler and then sent to the 2 closest+connected groupchat peers that are not the sender. The reason for it to be 2+instead of 4 (or 3 if we are not the original sender) as for lossless message+packets is that it reduces bandwidth usage without lowering the quality of the+received audio stream via lossy packets, at the cost of reduced robustness+against connections failing. To check if a packet was already received, the last+256 message numbers received from each peer are stored. If video was added+meaning a much higher number of packets would be sent, this number would be+increased. If the packet number is in this list then it was received.++\section{Message ids}++\subsection{ping (0x00)}++Sent approximately every 20 seconds by every peer. Contains no data.++\subsection{\texttt{new\_peer} (0x10)}++Tell everyone about a new peer in the chat.++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{2} & \texttt{uint16\_t} Peer number \\+ \texttt{32} & Long term public key \\+ \texttt{32} & DHT public key \\+\end{tabular}++\subsection{\texttt{kill\_peer} (0x11)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{2} & \texttt{uint16\_t} Peer number \\+\end{tabular}++\subsection{\texttt{freeze\_peer} (0x12)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{2} & \texttt{uint16\_t} Peer number \\+\end{tabular}++\subsection{Name change (0x30)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ variable & Name (namelen) \\+\end{tabular}++\subsection{Groupchat title change (0x31)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ variable & Title (titlelen) \\+\end{tabular}++\subsection{Chat message (0x40)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ variable & Message (messagelen) \\+\end{tabular}++\subsection{Action (/me) (0x41)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ variable & Message (messagelen) \\+\end{tabular}++Ping messages are sent every 20 seconds by every peer. This is how other+peers know that the peers are still alive.++When a new peer joins, the peer which invited the joining peer will send a new+peer message to warn everyone that there is a new peer in the chat. When a new+peer message is received, the peer in the message must be added to the peer+list if it is not there already, and its DHT public key must be set to that+in the message.++Kill peer messages are used to indicate that a peer has quit the group chat+permanently. Freeze peer messages are similar, but indicate that the quitting+peer may later return to the group. Each is sent by the one quitting the group+chat right before they quit it.++Name change messages are used to change or set the name of the peer sending it.+They are also sent by a joining peer right after receiving the list of peers in+order to tell others what their name is.++Title change packets are used to change the title of the group chat and can be+sent by anyone in the group chat.++Chat and action messages are used by the group chat peers to send messages to+others in the group chat.++\section{Timeouts and reconnection}++Groupchat connections may go down, and this may lead to a peer becoming+disconnected from the group or the group otherwise splitting into multiple+connected components. To ensure the group becomes fully connected again once+suitable connections are re-established, peers keep track of peers who are no+longer visible in the group ("frozen" peers), and try to re-integrate them+into the group via any suitable friend connections which may come to be+available. The rejoin packet is used for this.++Rejoin packet:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x64) \\+ \texttt{33} & Group chat identifier \\+\end{tabular}++A peer in a groupchat is considered to be active when a group message or+rejoin packet is received from it, or a new peer message is received for it.+A peer which remains inactive for 60 seconds is set as frozen; this means it+is removed from the peer list and added to a separate list of frozen peers.+Frozen peers are disregarded for all purposes except those discussed below.++If a frozen peer becomes active, we unfreeze it, meaning that we move it from+the frozen peers list to the peer list, and we send a name message to the+group.++Whenever we make a new friend connection to a peer, we check whether the+public key of the peer is that of any frozen peer. If so, we send a rejoin+packet to the peer along the friend connection, and create a groupchat+connection to the peer, marked as introducing us, and send a peer online+packet to the peer.++If we receive a rejoin packet from a peer along a friend connection, then,+after unfreezing the peer if it was frozen, we update the peer's DHT public+key in the groupchat peer list to the key in the friend connection, and create+a groupchat connection for the peer, marked as introducing the peer, and send+a peer online packet to the peer.++When a peer is added to the peer list, any existing peer in the peer list or+frozen peers list with the same public key is first removed.++\input{src/Network/Tox/Application/GroupChats.lhs}++\chapter{Net crypto}++The Tox transport protocol is what Tox uses to establish and send data securely+to friends and provides encryption, ordered delivery, and perfect forward+secrecy. It is a UDP protocol but it is also used when 2 friends connect over+TCP relays.++The reason the protocol for connections to friends over TCP relays and direct+UDP is the same is for simplicity and so the connection can switch between both+without the peers needing to disconnect and reconnect. For example two Tox+friends might first connect over TCP and a few seconds later switch to UDP when+a direct UDP connection becomes possible. The opening up of the UDP route or+'hole punching' is done by the DHT module and the opening up of a relayed TCP+connection is done by the \texttt{TCP\_connection} module. The Tox transport+protocol has the job of connecting two peers (tox friends) safely once a route+or communications link between both is found. Direct UDP is preferred over TCP+because it is direct and isn't limited by possibly congested TCP relays. Also,+a peer can only connect to another using the Tox transport protocol if they+know the real public key and DHT public key of the peer they want to connect+to. However, both the DHT and TCP connection modules require this information+in order to find and open the route to the peer which means we assume this+information is known by toxcore and has been passed to \texttt{net\_crypto} when+the \texttt{net\_crypto} connection was created.++Because this protocol has to work over UDP it must account for possible packet+loss, packets arriving in the wrong order and has to implement some kind of+congestion control. This is implemented above the level at which the packets+are encrypted. This prevents a malicious TCP relay from disrupting the+connection by modifying the packets that go through it. The packet loss+prevention makes it work very well on TCP relays that we assume may go down at+any time as the connection will stay strong even if there is need to switch to+another TCP relay which will cause some packet loss.++Before sending the actual handshake packet the peer must obtain a cookie. This+cookie step serves as a way for the receiving peer to confirm that the peer+initiating the connection can receive the responses in order to prevent certain+types of DoS attacks.++The peer receiving a cookie request packet must not allocate any resources to+the connection. They will simply respond to the packet with a cookie response+packet containing the cookie that the requesting peer must then use in the+handshake to initiate the actual connection.++The cookie response must be sent back using the exact same link the cookie+request packet was sent from. The reason for this is that if it is sent back+using another link, the other link might not work and the peer will not be+expecting responses from another link. For example, if a request is sent from+UDP with ip port X, it must be sent back by UDP to ip port X. If it was+received from a TCP OOB packet it must be sent back by a TCP OOB packet via the+same relay with the destination being the peer who sent the request. If it was+received from an established TCP relay connection it must be sent back via that+same exact connection.++When a cookie request is received, the peer must not use the information in the+request packet for anything, he must not store it, he must only create a cookie+and cookie response from it, then send the created cookie response packet and+forget them. The reason for this is to prevent possible attacks. For example+if a peer would allocate long term memory for each cookie request packet+received then a simple packet flood would be enough to achieve an effective+denial of service attack by making the program run out of memory.++cookie request packet (145 bytes):++\begin{verbatim}+[uint8_t 24]+[Sender's DHT Public key (32 bytes)]+[Random nonce (24 bytes)]+[Encrypted message containing:+ [Sender's real public key (32 bytes)]+ [padding (32 bytes)]+ [uint64_t echo id (must be sent back untouched in cookie response)]+]+\end{verbatim}++Encrypted message is encrypted with sender's DHT private key, receiver's DHT+public key and the nonce.++The packet id for cookie request packets is 24. The request contains the DHT+public key of the sender which is the key used (The DHT private key) (along+with the DHT public key of the receiver) to encrypt the encrypted part of the+cookie packet and a nonce also used to encrypt the encrypted part of the+packet. Padding is used to maintain backwards-compatibility with previous+versions of the protocol. The echo id in the cookie request must be sent back+untouched in the cookie response. This echo id is how the peer sending the+request can be sure that the response received was a response to the packet+that he sent.++The reason for sending the DHT public key and real public key in the cookie+request is that both are contained in the cookie sent back in the response.++Toxcore currently sends 1 cookie request packet every second 8 times before it+kills the connection if there are no responses.++cookie response packet (161 bytes):++\begin{verbatim}+[uint8_t 25]+[Random nonce (24 bytes)]+[Encrypted message containing:+ [Cookie]+ [uint64_t echo id (that was sent in the request)]+]+\end{verbatim}++Encrypted message is encrypted with the exact same symmetric key as the cookie+request packet it responds to but with a different nonce.++The packet id for cookie request packets is 25. The response contains a nonce+and an encrypted part encrypted with the nonce. The encrypted part is+encrypted with the same key used to decrypt the encrypted part of the request+meaning the expensive shared key generation needs to be called only once in+order to handle and respond to a cookie request packet with a cookie response.++The Cookie (see below) and the echo id that was sent in the request are the+contents of the encrypted part.++The Cookie should be (112 bytes):++\begin{verbatim}+[nonce]+[encrypted data:+ [uint64_t time]+ [Sender's real public key (32 bytes)]+ [Sender's DHT public key (32 bytes)]+]+\end{verbatim}++The cookie is a 112 byte piece of data that is created and sent to the+requester as part of the cookie response packet. A peer who wants to connect+to another must obtain a cookie packet from the peer they are trying to connect+to. The only way to send a valid handshake packet to another peer is to first+obtain a cookie from them.++The cookie contains information that will both prove to the receiver of the+handshake that the peer has received a cookie response and contains encrypted+info that tell the receiver of the handshake packet enough info to both decrypt+and validate the handshake packet and accept the connection.++When toxcore is started it generates a symmetric encryption key that it uses to+encrypt and decrypt all cookie packets (using NaCl authenticated encryption+exactly like encryption everywhere else in toxcore). Only the instance of+toxcore that create the packets knows the encryption key meaning any cookie it+successfully decrypts and validates were created by it.++The time variable in the cookie is used to prevent cookie packets that are too+old from being used. Toxcore has a time out of 15 seconds for cookie packets.+If a cookie packet is used more than 15 seconds after it is created toxcore+will see it as invalid.++When responding to a cookie request packet the sender's real public key is the+known key sent by the peer in the encrypted part of the cookie request packet+and the senders DHT public key is the key used to encrypt the encrypted part of+the cookie request packet.++When generating a cookie to put inside the encrypted part of the handshake: One+of the requirements to connect successfully to someone else is that we know+their DHT public key and their real long term public key meaning there is+enough information to construct the cookie.++Handshake packet:++\begin{verbatim}+[uint8_t 26]+[Cookie]+[nonce (24 bytes)]+[Encrypted message containing:+ [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)]+]+\end{verbatim}++The packet id for handshake packets is 26. The cookie is a cookie obtained by+sending a cookie request packet to the peer and getting a cookie response+packet with a cookie in it. It may also be obtained in the handshake packet by+a peer receiving a handshake packet (Other Cookie).++The nonce is a nonce used to encrypt the encrypted part of the handshake+packet. The encrypted part of the handshake packet is encrypted with the long+term keys of both peers. This is to prevent impersonation.++Inside the encrypted part of the handshake packet there is a 'base nonce' and a+session public key. The 'base nonce' is a nonce that the other should use to+encrypt each data packet, adding + 1 to it for each data packet sent. (first+packet is 'base nonce' + 0, next is 'base nonce' + 1, etc. Note that for+mathematical operations the nonce is considered to be a 24 byte number in big+endian format). The session key is the temporary connection public key that+the peer has generated for this connection and it sending to the other. This+session key is used so that the connection has perfect forward secrecy. It is+important to save the private key counterpart of the session public key sent in+the handshake, the public key received by the other and both the received and+sent base nonces as they are used to encrypt/decrypt the data packets.++The hash of the cookie in the encrypted part is used to make sure that an+attacker has not taken an older valid handshake packet and then replaced the+cookie packet inside with a newer one which would be bad as they could replay+it and might be able to make a mess.++The 'Other Cookie' is a valid cookie that we put in the handshake so that the+other can respond with a valid handshake without having to make a cookie+request to obtain one.++The handshake packet is sent by both sides of the connection. If a peer+receives a handshake it will check if the cookie is valid, if the encrypted+section decrypts and validates, if the cookie hash is valid, if long term+public key belongs to a known friend. If all these are true then the+connection is considered 'Accepted' but not 'Confirmed'.++If there is no existing connection to the peer identified by the long term+public key to set to 'Accepted', one will be created with that status. If a+connection to such peer with a not yet 'Accepted' status to exists, this+connection is set to accepted. If a connection with a 'Confirmed' status+exists for this peer, the handshake packet will be ignored and discarded (The+reason for discarding it is that we do not want slightly late handshake packets+to kill the connection) except if the DHT public key in the cookie contained in+the handshake packet is different from the known DHT public key of the peer.+If this happens the connection will be immediately killed because it means it+is no longer valid and a new connection will be created immediately with the+'Accepted' status.++Sometimes toxcore might receive the DHT public key of the peer first with a+handshake packet so it is important that this case is handled and that the+implementation passes the DHT public key to the other modules (DHT,+\texttt{TCP\_connection}) because this does happen.++Handshake packets must be created only once during the connection but must be+sent in intervals until we are sure the other received them. This happens when+a valid encrypted data packet is received and decrypted.++The states of a connection:++\begin{enumerate}+ \item Not accepted: Send handshake packets.++ \item Accepted: A handshake packet has been received from the other peer but+ no encrypted packets: continue (or start) sending handshake packets because+ the peer can't know if the other has received them.++ \item Confirmed: A valid encrypted packet has been received from the other+ peer: Connection is fully established: stop sending handshake packets.+\end{enumerate}++Toxcore sends handshake packets every second 8 times and times out the+connection if the connection does not get confirmed (no encrypted packet is+received) within this time.++Perfect handshake scenario:++\begin{verbatim}+Peer 1 Peer 2+Cookie request ->+ <- Cookie response+Handshake packet ->+ * accepts connection+ <- Handshake packet+*accepts connection+Encrypted packet -> <- Encrypted packet+*confirms connection *confirms connection+ Connection successful.+Encrypted packets -> <- Encrypted packets++More realistic handshake scenario:+Peer 1 Peer 2+Cookie request -> *packet lost*+Cookie request ->+ <- Cookie response+ *Peer 2 randomly starts new connection to peer 1+ <- Cookie request+Cookie response ->+Handshake packet -> <- Handshake packet+*accepts connection * accepts connection+Encrypted packet -> <- Encrypted packet+*confirms connection *confirms connection+ Connection successful.+Encrypted packets -> <- Encrypted packets+\end{verbatim}++The reason why the handshake is like this is because of certain design+requirements:++\begin{enumerate}+ \item The handshake must not leak the long term public keys of the peers to a+ possible attacker who would be looking at the packets but each peer must know+ for sure that they are connecting to the right peer and not an impostor.+ \item A connection must be able of being established if only one of the peers has+ the information necessary to initiate a connection (DHT public key of the+ peer and a link to the peer).+ \item If both peers initiate a connection to each other at the same time the+ connection must succeed without issues.+ \item There must be perfect forward secrecy.+ \item Must be resistant to any possible attacks.+\end{enumerate}++Due to how it is designed only one connection is possible at a time between 2+peers.++Encrypted packets:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x1b) \\+ \texttt{2} & \texttt{uint16\_t} The last 2 bytes of the nonce used to encrypt this \\+ variable & Payload \\+\end{tabular}++The payload is encrypted with the session key and 'base nonce' set by the+receiver in their handshake + packet number (starting at 0, big endian math).++The packet id for encrypted packets is 27. Encrypted packets are the packets+used to send data to the other peer in the connection. Since these packets can+be sent over UDP the implementation must assume that they can arrive out of+order or even not arrive at all.++To get the key used to encrypt/decrypt each packet in the connection a peer+takes the session public key received in the handshake and the private key+counterpart of the key it sent it the handshake and generates a shared key from+it. This shared key will be identical for both peers. It is important to note+that connection keys must be wiped when the connection is killed.++To create an encrypted packet to be sent to the other peer, the data is+encrypted with the shared key for this connection and the base nonce that the+other peer sent in the handshake packet with the total number of encrypted+packets sent in the connection added to it ('base nonce' + 0 for the first+encrypted data packet sent, 'base nonce' + 1 for the second, etc. Note that+the nonce is treated as a big endian number for mathematical operations like+additions). The 2 byte (\texttt{uint16\_t}) number at the beginning of the+encrypted packet is the last 2 bytes of this 24 byte nonce.++To decrypt a received encrypted packet, the nonce the packet was encrypted with+is calculated using the base nonce that the peer sent to the other and the 2+byte number at the beginning of the packet. First we assume that packets will+most likely arrive out of order and that some will be lost but that packet loss+and out of orderness will never be enough to make the 2 byte number need an+extra byte. The packet is decrypted using the shared key for the connection+and the calculated nonce.++Toxcore uses the following method to calculate the nonce for each packet:++\begin{enumerate}+ \item \texttt{diff} = (2 byte number on the packet) - (last 2 bytes of the current saved+ base nonce) NOTE: treat the 3 variables as 16 bit unsigned ints, the result+ is expected to sometimes roll over.+ \item copy \texttt{saved\_base\_nonce} to \texttt{temp\_nonce}.+ \item \texttt{temp\_nonce = temp\_nonce + diff}. \texttt{temp\_nonce} is the correct nonce that+ can be used to decrypt the packet.+ \item \texttt{DATA\_NUM\_THRESHOLD} = (1/3 of the maximum number that can be stored in an+ unsigned 2 bit integer)+ \item if decryption succeeds and \texttt{diff > (DATA\_NUM\_THRESHOLD * 2)} then:+ \begin{itemize}+ \item \texttt{saved\_base\_nonce = saved\_base\_nonce + DATA\_NUM\_THRESHOLD}+ \end{itemize}+\end{enumerate}++First it takes the difference between the 2 byte number on the packet and the+last. Because the 3 values are unsigned 16 bit ints and rollover is part of+the math something like diff = (10 - 65536) means diff is equal to 11.++Then it copies the saved base nonce to a temp nonce buffer.++Then it adds diff to the nonce (the nonce is in big endian format).++After if decryption was successful it checks if diff was bigger than 2/3 of the+value that can be contained in a 16 bit unsigned int and increases the saved+base nonce by 1/3 of the maximum value if it succeeded.++This is only one of many ways that the nonce for each encrypted packet can be+calculated.++Encrypted packets that cannot be decrypted are simply dropped.++The reason for exchanging base nonces is because since the key for encrypting+packets is the same for received and sent packets there must be a cryptographic+way to make it impossible for someone to do an attack where they would replay+packets back to the sender and the sender would think that those packets came+from the other peer.++Data in the encrypted packets:++\begin{verbatim}+[our recvbuffers buffer_start, (highest packet number handled + 1), (big endian)]+[uint32_t packet number if lossless, sendbuffer buffer_end if lossy, (big endian)]+[data]+\end{verbatim}++Encrypted packets may be lossy or lossless. Lossy packets are simply encrypted+packets that are sent to the other. If they are lost, arrive in the wrong+order or even if an attacker duplicates them (be sure to take this into account+for anything that uses lossy packets) they will simply be decrypted as they+arrive and passed upwards to what should handle them depending on the data id.++Lossless packets are packets containing data that will be delivered in order by+the implementation of the protocol. In this protocol, the receiver tells the+sender which packet numbers he has received and which he has not and the sender+must resend any packets that are dropped. Any attempt at doubling packets will+cause all (except the first received) to be ignored.++Each lossless packet contains both a 4 byte number indicating the highest+packet number received and processed and a 4 byte packet number which is the+packet number of the data in the packet.++In lossy packets, the layout is the same except that instead of a packet+number, the second 4 byte number represents the packet number of a lossless+packet if one were sent right after. This number is used by the receiver to+know if any packets have been lost. (for example if it receives 4 packets with+numbers (0, 1, 2, 5) and then later a lossy packet with this second number as:+8 it knows that packets: 3, 4, 6, 7 have been lost and will request them)++How the reliability is achieved:++First it is important to say that packet numbers do roll over, the next number+after 0xFFFFFFFF (maximum value in 4 bytes) is 0. Hence, all the mathematical+operations dealing with packet numbers are assumed to be done only on unsigned+32 bit integer unless said otherwise. For example 0 - 0xFFFFFFFF would equal+to 1 because of the rollover.++When sending a lossless packet, the packet is created with its packet number+being the number of the last lossless packet created + 1 (starting at 0). The+packet numbers are used for both reliability and in ordered delivery and so+must be sequential.++The packet is then stored along with its packet number in order for the peer to+be able to send it again if the receiver does not receive it. Packets are only+removed from storage when the receiver confirms they have received them.++The receiver receives packets and stores them along with their packet number.+When a receiver receives a packet he stores the packet along with its packet+number in an array. If there is already a packet with that number in the+buffer, the packet is dropped. If the packet number is smaller than the last+packet number that was processed, the packet is dropped. A processed packet+means it was removed from the buffer and passed upwards to the relevant module.++Assuming a new connection, the sender sends 5 lossless packets to the receiver:+0, 1, 2, 3, 4 are the packet numbers sent and the receiver receives: 3, 2, 0, 2+in that order.++The receiver will save the packets and discards the second packet with the+number 2, he has: 0, 2, 3 in his buffer. He will pass the first packet to the+relevant module and remove it from the array but since packet number 1 is+missing he will stop there. Contents of the buffer are now: 2, 3. The+receiver knows packet number 1 is missing and will request it from the sender+by using a packet request packet:++data ids:++\begin{tabular}{l|l}+ ID & Data \\+ \hline+ 0 & padding (skipped until we hit a non zero (data id) byte) \\+ 1 & packet request packet (lossy packet) \\+ 2 & connection kill packet (lossy packet) \\+ ... & ... \\+ 16+ & reserved for Messenger usage (lossless packets) \\+ 192+ & reserved for Messenger usage (lossy packets) \\+ 255 & reserved for Messenger usage (lossless packet) \\+\end{tabular}++Connection kill packets tell the other that the connection is over.++Packet numbers are the first byte of data in the packet.++packet request packet:++\begin{verbatim}+[uint8_t (1)][uint8_t num][uint8_t num][uint8_t num]...[uint8_t num]+\end{verbatim}++Packet request packets are used by one side of the connection to request+packets from the other. To create a full packet request packet, the one+requesting the packet takes the last packet number that was processed (sent to+the relevant module and removed from the array (0 in the example above)).+Subtract the number of the first missing packet from that number (1 - 0) = 1.+Which means the full packet to request packet number 1 will look like:++\begin{verbatim}+[uint32_t 1]+[uint32_t 0]+[uint8_t 1][uint8_t 1]+\end{verbatim}++If packet number 4 was being requested as well, take the difference between the+packet number and the last packet number being requested (4 - 1) = 3. So the+packet will look like:++\begin{verbatim}+[uint32_t 1]+[uint32_t 0]+[uint8_t 1][uint8_t 1][uint8_t 3]+\end{verbatim}++But what if the number is greater than 255? Let's say the peer needs to request+packets 3, 6, 1024, the packet will look like:++\begin{verbatim}+[uint32_t 1]+[uint32_t 2]+[uint8_t 1][uint8_t 3][uint8_t 3][uint8_t 0][uint8_t 0][uint8_t 0][uint8_t 253]+\end{verbatim}++Each 0 in the packet represents adding 255 until a non 0 byte is reached which+is then added and the resulting requested number is what is left.++This request is designed to be small when requesting packets in real network+conditions where the requested packet numbers will be close to each other.+Putting each requested 4 byte packet number would be very simple but would make+the request packets unnecessarily large which is why the packets look like+this.++When a request packet is received, it will be decoded and all packets in+between the requested packets will be assumed to be successfully received by+the other.++Packet request packets are sent at least every 1 second in toxcore and more+when packets are being received.++The current formula used is (note that this formula is likely sub-optimal):++\begin{verbatim}+REQUEST_PACKETS_COMPARE_CONSTANT = 50.0 double request_packet_interval =+(REQUEST_PACKETS_COMPARE_CONSTANT /+(((double)num_packets_array(&conn->recv_array) + 1.0) / (conn->packet_recv_rate++ 1.0)));+\end{verbatim}++\texttt{num\_packets\_array(&conn->recv\_array)} returns the difference between+the highest packet number received and the last one handled. In the toxcore+code it refers to the total size of the current array (with the holes which are+the placeholders for not yet received packets that are known to be missing).++\texttt{conn->packet\_recv\_rate} is the number of data packets successfully+received per second.++This formula was created with the logic that the higher the 'delay' in packets+(\texttt{num\_packets\_array(&conn->recv\_array)}) vs the speed of packets+received, the more request packets should be sent.++Requested packets are resent every time they can be resent as in they will obey+the congestion control and not bypass it. They are resent once, subsequent+request packets will be used to know if the packet was received or if it should+be resent.++The ping or rtt (round trip time) between two peers can be calculated by saving+the time each packet was sent and taking the difference between the time the+latest packet confirmed received by a request packet was sent and the time the+request packet was received. The rtt can be calculated for every request+packet. The lowest one (for all packets) will be the closest to the real ping.++This ping or rtt can be used to know if a request packet that requests a packet+we just sent should be resent right away or we should wait or not for the next+one (to know if the other side actually had time to receive the packet).++The congestion control algorithm has the goal of guessing how many packets can+be sent through the link every second before none can be sent through anymore.+How it works is basically to send packets faster and faster until none can go+through the link and then stop sending them faster than that.++Currently the congestion control uses the following formula in toxcore however+that is probably not the best way to do it.++The current formula is to take the difference between the current size of the+send queue and the size of the send queue 1.2 seconds ago, take the total+number of packets sent in the last 1.2 seconds and subtract the previous number+from it.++Then divide this number by 1.2 to get a packet speed per second. If this speed+is lower than the minimum send rate of 8 packets per second, set it to 8.++A congestion event can be defined as an event when the number of requested+packets exceeds the number of packets the congestion control says can be sent+during this frame. If a congestion event occurred during the last 2 seconds,+the packet send rate of the connection is set to the send rate previously+calculated, if not it is set to that send rate times 1.25 in order to increase+the speed.++Like I said this isn't perfect and a better solution can likely be found or the+numbers tweaked.++To fix the possible issue where it would be impossible to send very low+bandwidth data like text messages when sending high bandwidth data like files+it is possible to make priority packets ignore the congestion control+completely by placing them into the send packet queue and sending them even if+the congestion control says not to. This is used in toxcore for all non file+transfer packets to prevent file transfers from preventing normal message+packets from being sent.++\chapter{network.txt}++The network module is the lowest file in toxcore that everything else depends+on. This module is basically a UDP socket wrapper, serves as the sorting+ground for packets received by the socket, initializes and uninitializes the+socket. It also contains many socket, networking related and some other+functions like a monotonic time function used by other toxcore modules.++Things of note in this module are the maximum UDP packet size define+(\texttt{MAX\_UDP\_PACKET\_SIZE}) which sets the maximum UDP packet size toxcore+can send and receive. The list of all UDP packet ids: \texttt{NET\_PACKET\_*}.+UDP packet ids are the value of the first byte of each UDP packet and is how+each packet gets sorted to the right module that can handle it.+\texttt{networking\_registerhandler()} is used by higher level modules in order+to tell the network object which packets to send to which module via a+callback.++It also contains datastructures used for ip addresses in toxcore. IP4 and IP6+are the datastructures for ipv4 and ipv6 addresses, IP is the datastructure for+storing either (the family can be set to \texttt{AF\_INET} (ipv4) or+\texttt{AF\_INET6} (ipv6). It can be set to another value like+\texttt{TCP\_ONION\_FAMILY}, \texttt{TCP\_INET}, \texttt{TCP\_INET6} or+\texttt{TCP\_FAMILY} which are invalid values in the network modules but valid+values in some other module and denote a special type of ip) and+\texttt{IP\_Port} stores an IP datastructure with a port.++Since the network module interacts directly with the underlying operating+system with its socket functions it has code to make it work on windows, linux,+etc... unlike most modules that sit at a higher level.++The network module currently uses the polling method to read from the UDP+socket. The \texttt{networking\_poll()} function is called to read all the+packets from the socket and pass them to the callbacks set using the+\texttt{networking\_registerhandler()} function. The reason it uses polling is+simply because it was easier to write it that way, another method would be+better here.++The goal of this module is to provide an easy interface to a UDP socket and+other networking related functions.++\chapter{Onion}++The goal of the onion module in Tox is to prevent peers that are not friends+from finding out the temporary DHT public key from a known long term public key+of the peer and to prevent peers from discovering the long term public key of+peers when only the temporary DHT key is known.++It makes sure only friends of a peer can find it and connect to it and+indirectly makes sure non friends cannot find the ip address of the peer when+knowing the Tox address of the friend.++The only way to prevent peers in the network from associating the temporary DHT+public key with the long term public key is to not broadcast the long term key+and only give others in the network that are not friends the DHT public key.++The onion lets peers send their friends, whose real public key they know as it+is part of the Tox ID, their DHT public key so that the friends can then find+and connect to them without other peers being able to identify the real public+keys of peers.++So how does the onion work?++The onion works by enabling peers to announce their real public key to peers by+going through the onion path. It is like a DHT but through onion paths. In+fact it uses the DHT in order for peers to be able to find the peers with ids+closest to their public key by going through onion paths.++In order to announce its real public key anonymously to the Tox network while+using the onion, a peer first picks 3 random nodes that it knows (they can be+from anywhere: the DHT, connected TCP relays or nodes found while finding peers+with the onion). The nodes should be picked in a way that makes them unlikely+to be operated by the same person perhaps by looking at the ip addresses and+looking if they are in the same subnet or other ways. More research is needed+to make sure nodes are picked in the safest way possible.++The reason for 3 nodes is that 3 hops is what they use in Tor and other+anonymous onion based networks.++These nodes are referred to as nodes A, B and C. Note that if a peer cannot+communicate via UDP, its first peer will be one of the TCP relays it is+connected to, which will be used to send its onion packet to the network.++TCP relays can only be node A or the first peer in the chain as the TCP relay+is essentially acting as a gateway to the network. The data sent to the TCP+Client module to be sent as a TCP onion packet by the module is different from+the one sent directly via UDP. This is because it doesn't need to be encrypted+(the connection to the TCP relay server is already encrypted).++First I will explain how communicating via onion packets work.++Note: nonce is a 24 byte nonce. The nested nonces are all the same as the+outer nonce.++Onion packet (request):++Initial (TCP) data sent as the data of an onion packet through the TCP client+module:++\begin{itemize}+ \item \texttt{IP\_Port} of node B+ \item A random public key PK1+ \item Encrypted with the secret key SK1 and the public key of Node B and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} of node C+ \item A random public key PK2+ \item Encrypted with the secret key SK2 and the public key of Node C and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} of node D+ \item Data to send to Node D+ \end{itemize}+ \end{itemize}+\end{itemize}++Initial (UDP) (sent from us to node A):++\begin{itemize}+ \item \texttt{uint8\_t} (0x80) packet id+ \item Nonce+ \item Our temporary DHT public key+ \item Encrypted with our temporary DHT secret key and the public key of Node A and+ the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} of node B+ \item A random public key PK1+ \item Encrypted with the secret key SK1 and the public key of Node B and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} of node C+ \item A random public key PK2+ \item Encrypted with the secret key SK2 and the public key of Node C and the+ nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} of node D+ \item Data to send to Node D+ \end{itemize}+ \end{itemize}+ \end{itemize}+\end{itemize}++(sent from node A to node B):++\begin{itemize}+ \item \texttt{uint8\_t} (0x81) packet id+ \item Nonce+ \item A random public key PK1+ \item Encrypted with the secret key SK1 and the public key of Node B and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} of node C+ \item A random public key PK2+ \item Encrypted with the secret key SK2 and the public key of Node C and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} of node D+ \item Data to send to Node D+ \end{itemize}+ \end{itemize}+ \item Nonce+ \item Encrypted with temporary symmetric key of Node A and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} (of us)+ \end{itemize}+\end{itemize}++(sent from node B to node C):++\begin{itemize}+ \item \texttt{uint8\_t} (0x82) packet id+ \item Nonce+ \item A random public key PK1+ \item Encrypted with the secret key SK1 and the public key of Node C and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} of node D+ \item Data to send to Node D+ \end{itemize}+ \item Nonce+ \item Encrypted with temporary symmetric key of Node B and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} (of Node A)+ \item Nonce+ \item Encrypted with temporary symmetric key of Node A and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} (of us)+ \end{itemize}+ \end{itemize}+\end{itemize}++(sent from node C to node D):++\begin{itemize}+ \item Data to send to Node D+ \item Nonce+ \item Encrypted with temporary symmetric key of Node C and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} (of Node B)+ \item Nonce+ \item Encrypted with temporary symmetric key of Node B and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} (of Node A)+ \item Nonce+ \item Encrypted with temporary symmetric key of Node A and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} (of us)+ \end{itemize}+ \end{itemize}+ \end{itemize}+\end{itemize}++Onion packet (response):++initial (sent from node D to node C):++\begin{itemize}+ \item \texttt{uint8\_t} (0x8c) packet id+ \item Nonce+ \item Encrypted with the temporary symmetric key of Node C and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} (of Node B)+ \item Nonce+ \item Encrypted with the temporary symmetric key of Node B and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} (of Node A)+ \item Nonce+ \item Encrypted with the temporary symmetric key of Node A and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} (of us)+ \end{itemize}+ \end{itemize}+ \end{itemize}+ \item Data to send back+\end{itemize}++(sent from node C to node B):++\begin{itemize}+ \item \texttt{uint8\_t} (0x8d) packet id+ \item Nonce+ \item Encrypted with the temporary symmetric key of Node B and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} (of Node A)+ \item Nonce+ \item Encrypted with the temporary symmetric key of Node A and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} (of us)+ \end{itemize}+ \end{itemize}+ \item Data to send back+\end{itemize}++(sent from node B to node A):++\begin{itemize}+ \item \texttt{uint8\_t} (0x8e) packet id+ \item Nonce+ \item Encrypted with the temporary symmetric key of Node A and the nonce:+ \begin{itemize}+ \item \texttt{IP\_Port} (of us)+ \end{itemize}+ \item Data to send back+\end{itemize}++(sent from node A to us):++\begin{itemize}+ \item Data to send back+\end{itemize}++Each packet is encrypted multiple times so that only node A will be able to+receive and decrypt the first packet and know where to send it to, node B will+only be able to receive that decrypted packet, decrypt it again and know where+to send it and so on. You will also notice a piece of encrypted data (the+sendback) at the end of the packet that grows larger and larger at every layer+with the IP of the previous node in it. This is how the node receiving the end+data (Node D) will be able to send data back.++When a peer receives an onion packet, they will decrypt it, encrypt the+coordinates (IP/port) of the source along with the already existing encrypted+data (if it exists) with a symmetric key known only by the peer and only+refreshed every hour (in toxcore) as a security measure to force expire paths.++Here's a diagram how it works:++\begin{verbatim}+peer+ -> [onion1[onion2[onion3[data]]]] -> Node A+ -> [onion2[onion3[data]]][sendbackA] -> Node B+ -> [onion3[data]][sendbackB[sendbackA]] -> Node C+ -> [data][SendbackC[sendbackB[sendbackA]]]-> Node D (end)+\end{verbatim}++\begin{verbatim}+Node D+ -> [SendbackC[sendbackB[sendbackA]]][response] -> Node C+ -> [sendbackB[sendbackA]][response] -> Node B+ -> [sendbackA][response] -> Node A+ -> [response] -> peer+\end{verbatim}++The random public keys in the onion packets are temporary public keys generated+for and used for that onion path only. This is done in order to make it+difficult for others to link different paths together. Each encrypted layer+must have a different public key. This is the reason why there are multiple+keys in the packet definintions above.++The nonce is used to encrypt all the layers of encryption. This 24 byte nonce+should be randomly generated. If it isn't randomly generated and has a+relation to nonces used for other paths it could be possible to tie different+onion paths together.++The \texttt{IP\_Port} is an ip and port in packed format:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{TOX\_AF\_INET} (2) for IPv4 or \texttt{TOX\_AF\_INET6} (10) for IPv6 \\+ \texttt{4 $|$ 16} & IP address (4 bytes if IPv4, 16 if IPv6) \\+ \texttt{12 $|$ 0} & Zeroes \\+ \texttt{2} & \texttt{uint16\_t} Port \\+\end{tabular}++If IPv4 the format is padded with 12 bytes of zeroes so that both IPv4 and IPv6+have the same stored size.++The \texttt{IP\_Port} will always end up being of size 19 bytes. This is to+make it hard to know if an ipv4 or ipv6 ip is in the packet just by looking at+the size. The 12 bytes of zeros when ipv4 must be set to 0 and not left+uninitialized as some info may be leaked this way if it stays uninitialized.+All numbers here are in big endian format.++The \texttt{IP\_Port} in the sendback data can be in any format as long as the+length is 19 bytes because only the one who writes it can decrypt it and read+it, however, using the previous format is recommended because of code reuse.+The nonce in the sendback data must be a 24 byte nonce.++Each onion layers has a different packed id that identifies it so that an+implementation knows exactly how to handle them. Note that any data being sent+back must be encrypted, appear random and not leak information in any way as+all the nodes in the path will see it.++If anything is wrong with the received onion packets (decryption fails) the+implementation should drop them.++The implementation should have code for each different type of packet that+handles it, adds (or decrypts) a sendback and sends it to the next peer in the+path. There are a lot of packets but an implementation should be very+straightforward.++Note that if the first node in the path is a TCP relay, the TCP relay must put+an identifier (instead of an IP/Port) in the sendback so that it knows that any+response should be sent to the appropriate peer connected to the TCP relay.++This explained how to create onion packets and how they are sent back. Next is+what is actually sent and received on top of these onion packets or paths.++Note: nonce is a 24 byte nonce.++announce request packet:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x83) \\+ \texttt{24} & Nonce \\+ \texttt{32} & A public key (real or temporary) \\+ \texttt{?} & Payload \\+\end{tabular}++The public key is our real long term public key if we want to announce+ourselves, a temporary one if we are searching for friends.++The payload is encrypted with the secret key part of the sent public key, the+public key of Node D and the nonce, and contains:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{32} & Ping ID \\+ \texttt{32} & Public key we are searching for \\+ \texttt{32} & Public key that we want those sending back data packets to use \\+ \texttt{8} & Data to send back in response \\+\end{tabular}++If the ping id is zero, respond with an 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:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x85) \\+ \texttt{32} & Public key of destination node \\+ \texttt{24} & Nonce \\+ \texttt{32} & Temporary just generated public key \\+ variable & Payload \\+\end{tabular}++The payload is encrypted with that temporary secret key and the nonce and the+public key from the announce response packet of the destination node. 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:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{32} & Real public key of sender \\+ variable & Payload \\+\end{tabular}++The payload is encrypted with real secret key of the sender, the nonce in the+data packet and the real public key of the receiver:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} id \\+ variable & Data (optional) \\+\end{tabular}++Data sent to us:++announce response packet:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x84) \\+ \texttt{8} & Data to send back in response \\+ \texttt{24} & Nonce \\+ variable & Payload \\+\end{tabular}++The payload is encrypted with the DHT secret key of Node D, the public key in+the request and the nonce:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} \texttt{is\_stored} \\+ \texttt{32} & Ping ID or Public Key \\+ variable & Maximum of 4 nodes in packed node format (see DHT) \\+\end{tabular}++The packet contains a ping ID if \texttt{is\_stored} is 0 or 2, or the public+key that must be used to send data packets if \texttt{is\_stored} is 1.++If the \texttt{is\_stored} is not 0, it means the information to reach the+public key we are searching for is stored on this node. \texttt{is\_stored} is+2 as a response to a peer trying to announce himself to tell the peer that he+is currently announced successfully.++data to route response packet:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x86) \\+ \texttt{24} & Nonce \\+ \texttt{32} & Temporary just generated public key \\+ variable & Payload \\+\end{tabular}++The payload is encrypted with that temporary secret key, the nonce and the+public key from the announce response packet of the destination node.++There are 2 types of request packets and 2 'response' packets to go with them.+The announce request is used to announce ourselves to a node and announce+response packet is used by the node to respond to this packet. The data to+route request packet is a packet used to send packets through the node to+another peer that has announced itself and that we have found. The data to+route response packet is what the node transforms this packet into.++To announce ourselves to the network we must first find, using announce+packets, the peers with the DHT public key closest to our real public key. We+must then announce ourselves to these peers. Friends will then be able to send+messages to us using data to route packets by sending them to these peers. To+find the peers we have announced ourselves to, our friends will find the peers+closest to our real public key and ask them if they know us. They will then be+able to use the peers that know us to send us some messages that will contain+their DHT public key (which we need to know to connect directly to them), TCP+relays that they are connected to (so we can connect to them with these relays+if we need to) and some DHT peers they are connected to (so we can find them+faster in the DHT).++Announce request packets are the same packets used slightly differently if we+are announcing ourselves or searching for peers that know one of our friends.++If we are announcing ourselves we must put our real long term public key in the+packet and encrypt it with our long term private key. This is so the peer we+are announcing ourselves to can be sure that we actually own that public key.+If we are looking for peers we use a temporary public key used only for packets+looking for that peer in order to leak as little information as possible. The+\texttt{ping\_id} is a 32 byte number which is sent to us in the announce+response and we must send back to the peer in another announce request. This+is done in order to prevent people from easily announcing themselves many times+as they have to prove they can respond to packets from the peer before the peer+will let them announce themselves. This \texttt{ping\_id} is set to 0 when none+is known.++The public key we are searching for is set to our long term public key when+announcing ourselves and set to the long term public key of the friend we are+searching for if we are looking for peers.++When announcing ourselves, the public key we want others to use to send us data+back is set to a temporary public key and we use the private key part of this+key to decrypt packet routing data sent to us. This public key is to prevent+peers from saving old data to route packets from previous sessions and be able+to replay them in future Tox sessions. This key is set to zero when searching+for peers.++The sendback data is an 8 byte number that will be sent back in the announce+packet response. Its goal is to be used to learn which announce request packet+the response is responding to, and hence its location in the unencrypted part+of the response. This is needed in toxcore to find and check info about the+packet in order to decrypt it and handle it correctly. Toxcore uses it as an+index to its special \texttt{ping\_array}.++Why don't we use different packets instead of having one announce packet+request and one response that does everything? It makes it a lot more difficult+for possible attackers to know if we are merely announcing ourselves or if we+are looking for friends as the packets for both look the same and are the same+size.++The unencrypted part of an announce response packet contains the sendback data,+which was sent in the request this packet is responding to and a 24 byte random+nonce used to encrypt the encrypted part.++The \texttt{is\_stored} number is set to either 0, 1 or 2. 0 means that the+public key that was being searched in the request isn't stored or known by this+peer. 1 means that it is and 2 means that we are announced successfully at+that node. Both 1 and 2 are needed so that when clients are restarted it is+possible to reannounce without waiting for the timeout of the previous+announce. This would not otherwise be possible as a client would receive+response 1 without a \texttt{ping\_id} which is needed in order to reannounce+successfully.++When the \texttt{is\_stored} number is 0 or 2, the next 32 bytes is a+\texttt{ping\_id}. When \texttt{is\_stored} is 1 it corresponds to a public key+(the send back data public key set by the friend in their announce request)+that must be used to encrypt and send data to the friend.++Then there is an optional maximum 4 nodes, in DHT packed nodes format (see+DHT), attached to the response which denote the 4 DHT peers with the DHT public+keys closest to the searched public key in the announce request known by the+peer (see DHT). To find these peers, toxcore uses the same function as is used+to find peers for get node DHT responses. Peers wanting to announce themselves+or searching for peers that 'know' their friends will recursively query closer+and closer peers until they find the closest possible and then either announce+themselves to them or just ping them every once in a while to know if their+friend can be contacted. Note that the distance function used for this is the+same as the Tox DHT.++Data to route request packets are packets used to send data directly to another+peer via a node that knows that peer. The public key is the public key of the+final destination where we want the packet to be sent (the real public key of+our friend). The nonce is a 24 byte random nonce and the public key is a+random temporary public key used to encrypt the data in the packet and, if+possible, only to send packets to this friend (we want to leak as little info+to the network as possible so we use temp public keys as we don't want a peer+to see the same public keys and be able to link things together). The data is+encrypted data that we want to send to the peer with the public key.++The route response packets are just the last elements (nonce, public key,+encrypted data) of the data to route request packet copied into a new packet+and sent to the appropriate destination.++To handle onion announce packets, toxcore first receives an announce packet and+decrypts it.++Toxcore generates \texttt{ping\_id}s by taking a 32 byte sha hash of the current+time, some secret bytes generated when the instance is created, the current+time divided by a 300 second timeout, the public key of the requester and the+source ip/port that the packet was received from. Since the ip/port that the+packet was received from is in the \texttt{ping\_id}, the announce packets being+sent with a ping id must be sent using the same path as the packet that we+received the \texttt{ping\_id} from or announcing will fail.++The reason for this 300 second timeout in toxcore is that it gives a reasonable+time (300 to 600 seconds) for peers to announce themselves.++Toxcore generates 2 different ping ids, the first is generated with the current+time (divided by 300) and the second with the current time + 300 (divided by 300).+The two ping ids are then compared to the ping ids in the received packets.+The reason for doing this is that storing every ping id received might be+expensive and leave us vulnerable to a DoS attack, this method makes sure that+the other cannot generate \texttt{ping\_id}s and must ask for them. The reason+for the 2 \texttt{ping\_id}s is that we want to make sure that the timeout is at+least 300 seconds and cannot be 0.++If one of the two ping ids is equal to the ping id in the announce request,+the sendback data public key and the sendback data are stored in the+datastructure used to store announced peers. If the implementation has a+limit to how many announced entries it can store, it should only store the+entries closest (determined by the DHT distance function) to its DHT public+key. If the entry is already there, the information will simply be updated+with the new one and the timeout will be reset for that entry.++Toxcore has a timeout of 300 seconds for announce entries after which they are+removed which is long enough to make sure the entries don't expire prematurely+but not long enough for peers to stay announced for extended amounts of time+after they go offline.++Toxcore will then copy the 4 DHT nodes closest to the public key being searched+to a new packet (the response).++Toxcore will look if the public key being searched is in the datastructure. If+it isn't it will copy the second generated \texttt{ping\_id} (the one generated+with the current time plus 300 seconds) to the response, set the+\texttt{is\_stored} number to 0 and send the packet back.++If the public key is in the datastructure, it will check whether the public key+that was used to encrypt the announce packet is equal to the announced public+key, if it isn't then it means that the peer is searching for a peer and that+we know it. This means the \texttt{is\_stored} is set to 1 and the sending back+data public key in the announce entry is copied to the packet.++If it (key used to encrypt the announce packet) is equal (to the announced+public key which is also the 'public key we are searching for' in the announce+packet) meaning the peer is announcing itself and an entry for it exists, the+sending back data public key is checked to see if it equals the one in the+packet. If it is not equal it means that it is outdated, probably because the+announcing peer's toxcore instance was restarted and so their+\texttt{is\_stored} is set to 0, if it is equal it means the peer is announced+correctly so the \texttt{is\_stored} is set to 2. The second generated+\texttt{ping\_id} is then copied to the packet.++Once the packet is contructed a random 24 byte nonce is generated, the packet+is encrypted (the shared key used to decrypt the request can be saved and used+to encrypt the response to save an expensive key derivation operation), the+data to send back is copied to the unencrypted part and the packet is sent back+as an onion response packet.++In order to announce itself using onion announce packets toxcore first takes+DHT peers, picks random ones and builds onion paths with them by saving 3+nodes, calling it a path, generating some keypairs for encrypting the onion+packets and using them to send onion packets. If the peer is only connected+with TCP, the initial nodes will be bootstrap nodes and connected TCP relays+(for the first peer in the path). Once the peer is connected to the onion he+can fill up his list of known peers with peers sent in announce responses if+needed.++Onion paths have different timeouts depending on whether the path is confirmed+or unconfirmed. Unconfirmed paths (paths that core has never received any+responses from) have a timeout of 4 seconds with 2 tries before they are deemed+non working. This is because, due to network conditions, there may be a large+number of newly created paths that do not work and so trying them a lot would+make finding a working path take much longer. The timeout for a confirmed path+(from which a response was received) is 10 seconds with 4 tries without a+response. A confirmed path has a maximum lifetime of 1200 seconds to make+possible deanonimization attacks more difficult.++Toxcore saves a maximum of 12 paths: 6 paths are reserved for announcing+ourselves and 6 others are used to search for friends. This may not be the+safest way (some nodes may be able to associate friends together) however it is+much more performant than having different paths for each friend. The main+benefit is that the announcing and searching are done with different paths,+which makes it difficult to know that peer with real public key X is friends+with Y and Z. More research is needed to find the best way to do this. At+first toxcore did have different paths for each friend, however, that meant+that each friend path was almost never used (and checked). When using a low+amount of paths for searching there is less resources needed to find good+paths. 6 paths are used because 4 was too low and caused some performance+issues because it took longer to find some good paths at the beginning because+only 4 could be tried at a time. A too high number meanwhile would mean each+path is used (and tested) less. The reason why the numbers are the same for+both types of paths is for code simplification purposes.++To search/announce itself to peers, toxcore keeps the 8 closest peers (12 for+announcing) to each key it is searching (or announcing itself to). To+populate these it starts by sending announce requests to random peers for all+the public keys it is searching for. It then recursively searches closer and+closer peers (DHT distance function) until it no longer finds any. It is+important to make sure it is not too aggressive at searching the peers as some+might no longer be online but peers might still send announce responses with+their information. Toxcore keeps lists of last pinged nodes for each key+searched so as not to ping dead nodes too aggressively.++Toxcore decides if it will send an announce packet to one of the 4 peers in the+announce response by checking if the peer would be stored as one of the stored+closest peers if it responded; if it would not be it doesn't send an announce+request, if it would be it sends one.++Peers are only put in the closest peers array if they respond to an announce+request. If the peers fail to respond to 3 announce requests they are deemed+timed out and removed. When sending an announce request to a peer to which we+have been announcing ourselves for at least 90 seconds and which has failed to+respond to the previous 2 requests, toxcore uses a random path for the request.+This reduces the chances that a good node will be removed due to bad paths.++The reason for the numbers of peers being 8 and 12 is that lower numbers might+make searching for and announcing too unreliable and a higher number too+bandwidth/resource intensive.++Toxcore uses \texttt{ping\_array} (see \texttt{ping\_array}) for the 8 byte+sendback data in announce packets to store information that it will need to+handle the response (key to decrypt it, why was it sent? (to announce ourselves+or to search? For what key? and some other info)). For security purposes it+checks to make sure the packet was received from the right ip/port and checks+if the key in the unencrypted part of the packet is the right public key.++For peers we are announcing ourselves to, if we are not announced to them+toxcore tries every 3 seconds to announce ourselves to them until they return+that we have announced ourselves to them, then initially toxcore sends an+announce request packet every 15 seconds to see if we are still announced and+reannounce ourselves at the same time. Toxcore sends every announce packet+with the \texttt{ping\_id} previously received from that peer with the same+path (if possible). Toxcore use a timeout of 120 seconds rather than 15+seconds if we have been announcing to the peer for at least 90 seconds, and+the onion path we are are using for the peer has also been alive for at least+90 seconds, and we have not been waiting for at least 15 seconds for a+response to a request sent to the peer, nor for at least 10 seconds for a+response to a request sent via the path. The timeout of at most 120 seconds+means a \texttt{ping\_id} received in the last packet will not have had time+to expire (300 second minimum timeout) before it is resent 120 seconds later.++For friends this is slightly different. It is important to start searching for+friends after we are fully announced. Assuming a perfect network, we would+only need to do a search for friend public keys only when first starting the+instance (or going offline and back online) as peers starting up after us would+be able to find us immediately just by searching for us. If we start searching+for friends after we are announced we prevent a scenario where 2 friends start+their clients at the same time but are unable to find each other right away+because they start searching for each other while they have not announced+themselves.++For this reason, after the peer is announced successfully, for 17 seconds+announce packets are sent aggressively every 3 seconds to each known close peer+(in the list of 8 peers) to search aggressively for peers that know the peer we+are searching for.++After this, toxcore sends requests once per 15 seconds initially, then+uses linear backoff to increase the interval. In detail, the interval used+when searching for a given friend is at least 15 and at most 2400 seconds, and+within these bounds is calculated as one quarter of the time since we began+searching for the friend, or since the friend was last seen. For this purpose,+a friend is considered to be seen when some peer reports that the friend is+announced, or we receive a DHT Public Key packet from the friend, or we obtain+a new DHT key for them from a group, or a friend connection for the friend+goes offline.++There are other ways this could be done and which would still work but, if+making your own implementation, keep in mind that these are likely not the most+optimized way to do things.++If we find peers (more than 1) that know a friend we will send them an onion+data packet with our DHT public key, up to 2 TCP relays we are connected to and+2 DHT peers close to us to help the friend connect back to us.++Onion data packets are packets sent as the data of data to route packets.++Onion data packets:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{32} & Long term public key of sender \\+ variable & Payload \\+\end{tabular}++The payload is encrypted with long term private key of the sender, the long+term public key of the receiver and the nonce used in the data to route request+packet used to send this onion data packet (shaves off 24 bytes).++DHT public key packet:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x9c) \\+ \texttt{8} & \texttt{uint64\_t} \texttt{no\_replay} \\+ \texttt{32} & Our DHT public key \\+ \texttt{[39, 204]} & Maximum of 4 nodes in packed format \\+\end{tabular}++The packet will only be accepted if the \texttt{no\_replay} number is greater+than the \texttt{no\_replay} number in the last packet received.++The nodes sent in the packet comprise 2 TCP relays to which we are+connected (or fewer if there are not 2 available) and a number of DHT nodes+from our Close List, with the total number of nodes sent being at most 4. The+nodes chosen from the Close List are those closest in DHT distance to us. This+allows the friend to find us more easily in the DHT, or to connect to us via a+TCP relay.++Why another round of encryption? We have to prove to the receiver that we own+the long term public key we say we own when sending them our DHT public key.+Friend requests are also sent using onion data packets but their exact format+is explained in Messenger.++The \texttt{no\_replay} number is protection if someone tries to replay an older+packet and should be set to an always increasing number. It is 8 bytes so you+should set a high resolution monotonic time as the value.++We send this packet every 30 seconds if there is more than one peer (in the 8)+that says they our friend is announced on them. This packet can also be sent+through the DHT module as a DHT request packet (see DHT) if we know the DHT+public key of the friend and are looking for them in the DHT but have not+connected to them yet. 30 second is a reasonable timeout to not flood the+network with too many packets while making sure the other will eventually+receive the packet. Since packets are sent through every peer that knows the+friend, resending it right away without waiting has a high likelihood of+failure as the chances of packet loss happening to all (up to to 8) packets+sent is low.++When sent as a DHT request packet (this is the data sent in the DHT request+packet):++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0x9c) \\+ \texttt{32} & Long term public key of sender \\+ \texttt{24} & Nonce \\+ variable & Encrypted payload \\+\end{tabular}++The payload is encrypted with long term private key of sender, the long term+public key of receiver and the nonce, and contains the DHT public key packet.++When sent as a DHT request packet the DHT public key packet is (before being+sent as the data of a DHT request packet) encrypted with the long term keys of+both the sender and receiver and put in that format. This is done for the same+reason as the double encryption of the onion data packet.++Toxcore tries to resend this packet through the DHT every 20 seconds. 20+seconds is a reasonable resend rate which isn't too aggressive.++Toxcore has a DHT request packet handler that passes received DHT public key+packets from the DHT module to this module.++If we receive a DHT public key packet, we will first check if the DHT packet is+from a friend, if it is not from a friend, it will be discarded. The+\texttt{no\_replay} will then be checked to see if it is good and no packet with+a lower one was received during the session. The DHT key, the TCP nodes in the+packed nodes and the DHT nodes in the packed nodes will be passed to their+relevant modules. The fact that we have the DHT public key of a friend means+this module has achieved its goal.++If a friend is online and connected to us, the onion will stop all of its+actions for that friend. If the peer goes offline it will restart searching+for the friend as if toxcore was just started.++If toxcore goes offline (no onion traffic for 75 seconds) toxcore will+aggressively reannounce itself and search for friends as if it was just+started.++\chapter{Ping array}++Ping array is an array used in toxcore to store data for pings. It enables the+storage of arbitrary data that can then be retrieved later by passing the 8+byte ping id that was returned when the data was stored. It also frees data+from pings that are older than a ping expiring delay set when initializing the+array.++Ping arrays are initialized with a size and a timeout parameter. The size+parameter denotes the maximum number of entries in the array and the timeout+denotes the number of seconds to keep an entry in the array. Timeout and size+must be bigger than 0.++Adding an entry to the ping array will make it return an 8 byte number that can+be used as the ping number of a ping packet. This number is generated by first+generating a random 8 byte number (toxcore uses the cryptographic secure random+number generator), dividing then multiplying it by the total size of the array+and then adding the index of the element that was added. This generates a+random looking number that will return the index of the element that was added+to the array. This number is also stored along with the added data and the+current time (to check for timeouts). Data is added to the array in a cyclical+manner (0, 1, 2, 3... (array size - 1), 0, 1, ...). If the array is full, the+oldest element is overwritten.++To get data from the ping array, the ping number is passed to the function to+get the data from the array. The modulo of the ping number with the total size+of the array will return the index at which the data is. If there is no data+stored at this index, the function returns an error. The ping number is then+checked against the ping number stored for this element, if it is not equal the+function returns an error. If the array element has timed out, the function+returns an error. If all the checks succeed the function returns the exact+data that was stored and it is removed from the array.++Ping array is used in many places in toxcore to efficiently keep track of sent+packets.++\input{src/Network/Tox/SaveData.lhs}+\input{src/Network/Tox/Testing.lhs}
+ src/Network/Tox/Application/GroupChats.lhs view
@@ -0,0 +1,388 @@+\chapter{DHT Group Chats}++This document details the groupchat implementation, giving a high level overview+of all the important features and aspects, as well as some important low level+implementation details. This documentation reflects what is currently+implemented at the time of writing; it is not speculative. For detailed API docs+see the groupchats section of the tox.h header file.++\section{Features}++\begin{itemize}+ \item Private messages+ \item Action messages (/me)+ \item Public groups (peers may join via a public key)+ \item Private groups (peers require a friend invite)+ \item Permanence (a group cannot 'die' as long as at least one peer retains+ their group credentials)+ \item Persistence across client restarts+ \item Ability to set peer limits+ \item Moderation (kicking, banning, silencing)+ \item Permanent group names (set on creation)+ \item Topics (may only be set by moderators and the founder)+ \item Password protection+ \item Self-repairing (auto-rejoin on disconnect, group split protection, state+ syncing)+ \item Identity separation from the Tox ID+ \item Ability to ignore peers+ \item Unique nicknames which can be set on a per-group basis+ \item Peer statuses (online, away, busy) which can be set on a per-group basis+ \item Custom parting/exit messages+\end{itemize}++\section{Group roles}++There are four distinct roles which are hierarchical in nature (higher roles+have all the privileges of lower roles).++\begin{itemize}+ \item \textbf{Founder} - The group's creator. May set all other peers roles+ to anything except founder. May also set the group password, toggle the+ privacy state, and set the peer limit.+ \item \textbf{Moderator} - Promoted by the founder. May kick, ban and set+ the user and observer roles for peers below this role. May also set the+ topic.+ \item \textbf{User} - Default non-founder role. May communicate with other+ peers normally.+ \item \textbf{Observer} - Demoted by moderators and the founder. May observe+ the group and ignore peers; may not communicate with other peers or with the+ group.+\end{itemize}++\section{Group types}++Groups can have two types: private and public. The type can be set on creation,+and may also be toggled by the group founder at any point after creation.+(\emph{Note: password protection is completely independent of the group type})++\subsection{Public}++Anyone may join the group using the Chat ID. If the group is public, information+about peers inside the group, including their IP addresses and group public keys+(but not their Tox ID's) is visible to anyone with access to a node storing+their DHT announcement. See the \href{#dht-announcements}{DHT Announcements}+section for details.++\subsection{Private}++The only way to join a private group is by having someone in your friend list+send you an invite. If the group is private, no peer/group information+(mentioned in the Public section) is present in the DHT; the DHT is not used for+any purpose at all. If a public group is set to private, all DHT information+related to the group will expire within a few minutes.++\section{Cryptography}++Groupchats use the+\href{https://en.wikipedia.org/wiki/NaCl_(software)}{NaCl/libsodium cryptography+library} for all cryptography related operations. All group communication is+end-to-end encrypted. Message confidentiality, integrity, and repudability are+guaranteed via+\href{https://en.wikipedia.org/wiki/Authenticated_encryption}{authenticated+encryption}, and \href{https://en.wikipedia.org/wiki/Forward_secrecy}{perfect+forward secrecy} is also provided.++One of the most important security improvements from the old groupchat+implementation is the removal of a message-relay mechanism that uses a+group-wide shared key. Instead, connections are 1-to-1 (a complete graph),+meaning an outbound message is sent once per peer, and encrypted/decrypted using+a key unique to each peer. This prevents MITM attacks that were previously+possible. This additionally ensures that private messages are truly private.++Groups make use of 13 unique keys in total: Two permanent keypairs (encryption+and signature), two group keypairs (encryption and signature), one session+keypair (encryption), one shared symmetric key (encryption), and one temp DHT+keypair (encryption).++The Tox ID/Tox public key is not used for any purpose. As such, neither peers in+a given group nor in the group DHT can be matched with their Tox ID. In other+words, there is no way of identifying a peer aside from their IP address,+nickname, and group public key. (\emph{Note: group nicknames can be different+from the client's main nickname that their friends see}).++\subsection{Permanent keypairs}++When a peer creates or joins a group they generate two permanent keypairs: an+encryption keypair and a signature keypair, both of which are unique to the+group. The two public keys are the only guaranteed way to identify a peer, and+both keypairs will persist for as long as a peer remains in the group (even+across client restarts). If a peer exits the group these keypairs will be lost+forever.++This encryption keypair is not used for any encryption operations except for the+initial handshake when connecting to another peer. For usage details on the+signature key, see the \href{#moderation}{Moderation} section.++\subsection{Session keypair/shared symmetric key}++When two peers establish a connection they each generate a session encryption+keypair and share one another's resulting public key. With their own session+secret key and the other's session public key, they will both generate the same+symmetric encryption key. This symmetric key will be used for all further+encryption operations between them for the current session (i.e. until one of+them disconnects).++The purpose of this extra key exchange is to prevent an adversary from+decrypting messages from previous sessions in event that a secret encryption key+becomes compromised. This is known as forward secrecy.++\subsection{Group keypairs}++The group founder generates two additional permanent keypairs when the group is+created: an encryption keypair, and a signature keypair. The public signature+key is considered the \textbf{Chat ID} and is used as the group's permanent+identifier, allowing other peers to join public groups via the DHT. Every peer+in the group holds a copy of the group's public encryption key along with the+public signature key/Chat ID.++The group secret keys are similar to the permanent keypairs in that they will+persist across client restarts, but will be lost forever if the founder exits+the group. This is particularly important as administration related+functionality will not work without these keys. See the+\href{#founders}{Founders} section for usage details.++\subsection{Temporary DHT keypair}++All group related DHT procedures make use of toxcore's temp DHT keypair. This+keypair is generated when the Tox object is initialized and does not persist+across client restarts. See the \href{#dht-announcements}{DHT Announcements}+section for further details.++\section{Founders}++The peer who creates the group is the group's founder. Founders have a set of+admin privileges, including:++\begin{itemize}+ \item Promoting and demoting moderators+ \item The ability to kick/ban moderators+ \item Setting the peer limit+ \item Setting the group's privacy state+ \item Setting group passwords+\end{itemize}++\subsection{Shared state}++Groups contain a data structure called the \textbf{shared state} which is given+to every peer who joins the group. In this structure resides all data pertaining+to the group that must only be modifiable by the group founder. This includes+things like the group name, the group type, the peer limit, and the password.+Additionally, the shared state holds a copy of the group founder's public+encryption and signature keys, which is how other peers in the group are able to+verify the identity of the group founder.++The shared state is signed by the founder using the group secret signature key.+As the founder is the only peer who holds this secret key, this ensures that the+shared state may be safely shared by untrusted peers, even in the absence of the+founder.++When the founder modifies the shared state, he increments the shared state+version, signs the new shared state data with the group secret signature key,+and broadcasts the new shared state data along with its signature to the entire+group. When a peer receives this broadcast, he uses the group public signature+key to verify that the data was signed with the group secret signature key, and+also verifies that the new version is not older than the current version.++\subsection{Moderation}++The founder has the ability to promote other peers to the moderator role.+Moderators have all the privileges of normal users, and additionally have the+power to kick, ban, and unban, as well as give peers below the moderator role+the roles of user and observer (see the \href{#group-roles}{Group roles}+section). Moderators can also modify the group topic. Moderators have no power+over one another; only the founder can kick, ban, or change the role of a+moderator.++\subsection{Kicks/bans}++When a peer is kicked or banned from the group, his chat instance and all its+associated data will be destroyed. This includes all public and secret keys.+Additionally, the the peer will not receive any notifiactions; it will simply+appear to them as if the group is inactive.++\subsection{Moderator list}++Each peer holds a copy of the \textbf{moderator list}, which is an array of+public signature keys of peers who currently have the moderator role (including+those who are offline). A hash (sha256) of this list called the+\textbf{\verb'mod_list_hash'} is stored in the shared state, which is itself+signed by the founder using the group secret signature key. This allows the+moderator list to be shared between untrusted peers, even in the absence of the+founder, while maintaining moderator verifiability.++When the founder modifies the moderator list, he updates the+\verb'mod_list_hash', increments the shared state version, signs the new shared+state, broadcasts the new shared state data along with its signature to the+entire group, then broadcasts the new moderator list to the entire group. When a+peer receives this moderator list (having already verified the new shared+state), he creates a hash of the new list and verifies that it is identical to+the \verb'mod_list_hash'.++\subsection{Sanctions list}++Each peer holds a copy of the \textbf{sanctions list}. This list holds two+sublists: Banned peers, and peers with the observer role, or the \textbf{ban+list} and the \textbf{observer list} respectively. The ban list contains entries+of peers who have been banned, including their last used nickname, IP+address/port, and a unique ID. The sanctions list contains entries of peers who+have been demoted to the observer role, including just their public encryption+key.++All entries additionally contain a timestamp of the time the entry was made, the+public signature key of the peer who set the sanction, and a signature of the+entry's data, which is signed by the peer who created the entry using their+secret signature key. Individual entries are verified by ensuring that the+entry's public signature key belongs to the founder or is present in the+moderator list, and then verifying that the entry's data was signed by the owner+of that key.++Although each individual entry can be verified, we still need a way to verify+that the list as a whole is complete and identical for every peer, otherwise any+peer would be able to remove entries arbitrarily, or replace the list with an+older version. Therefore each peer holds a copy of the \textbf{sanctions list+credentials}. This is a data structure that holds the version, a hash (sha256)+of all sanctions list entries plus the version, the public signature key of the+last peer to have modified the sanctions list, and a signature of the hash,+which is created by that key.++When a moderator or founder modifies the sanctions list, he will increment the+version, create a new hash, sign the hash+version with his secret signature key,+and replace the old public signature key with his own. He will then broadcast+the new changes (not the entire list) to the entire group along with the new+credentials. When a peer receives this broadcast, he will verify that the new+credentials version is not older than the current version and verify that the+changes were made by a moderator or the founder. If adding an entry, he will+verify that the entry was signed by the signature key of the entry's creator.++When the founder kicks, bans or demotes a moderator, he will first go through+the sanctions list and re-sign each entry made by that moderator with his own+founder key, then re-broadcast the sanctions list to the entire group. This is+necessary to guarantee that all sanctions list entries and its credentials are+signed by a current moderator or the founder at all times.++\textbf{Note:} \emph{The sanctions list is not saved to the Tox save file,+meaning that if the group ever becomes empty, the sanctions list will be reset.+This is in contrast to the shared state and moderator list, which are both saved+and will persist even if the group becomes empty.}++\section{Topics}++Founders and moderators have the ability to set the \textbf{topic}, which is+simply an arbitrary string of characters. The integrity of a topic is maintained+in a similar manner as sanctions entries, using a data structure called+\textbf{\verb'topic_info'}. This is a struct which contains the topic, a+version, and the public key of the peer who set it.++When a peer modifies the topic, they will increment the version, sign the new+topic+version with their secret signature key, replace the public key with their+own, then broadcast the new \verb'topic_info' data along with the signature to+the entire group. When a peer receives this broadcast, they will first check if+the public signature key of the setter either belongs to the founder, or is in+the moderator list. They will then verify the signature using the setter's+public signature key, and finally they will ensure that the version is not older+than the current topic version.++If the moderator who set the current topic is kicked, banned, or demoted, the+founder will re-sign the topic using his own signature key, and rebroadcast it+to the entire group.++\section{State syncing}++Peers send four unsigned 32-bit integers along with their ping packets: Their+peer count\footnote{We use a "real" peer count, which is the number of confirmed+peers in the peerlist (that is, peers who you have successfully handshaked and+exchanged peer info with).}, their shared state version, their sanctions+credentials version, and their topic version. If a peer receives a ping in which+any of these values are greater than their own, this indicates that they may be+out of sync with the rest of the group. In this case they will do one of two+things: If they already have a sync request flagged for this peer, they will+send a sync request. Otherwise they will set the flag and wait until the next+ping arrives (this waiting is to correct for false-positives in the case of high+network latency). The flag is reset after a sync request is sent, or whenever a+ping is received in which all data is in sync.++\section{Group syncing}++In order to prevent entirely separate subgroups with the same Chat ID from being+created, be it due to network issues or a malicious MITM attempt, it's necessary+for groups to periodically search the DHT for announced nodes that match the+group's Chat ID but are not present in the group. In case an unknown node is+found, an attempt will be made to connect with it. If successful, the state sync+mechanism will merge the subgroups shortly.++Since we don't want to spam the DHT with a redundant number of requests that+grows linearly with the size of the group, peers will take turns doing the+search. Peers decide independently if it's their turn to search. Each peer has+the same base timer T, and every interval of T they will do a search with a+probability P which is inversely proportionate to the number of peers N. For+example, if N=1 then P=1.0. If N=4 then P=0.25. If N=100 then P=0.01 and so on.+This guarantees that a given group will do 1 search per T interval on average+regardless of its size, and it also ensures that a full spectrum of the network+is searched. Moreover, because peers act independently rather than in+coordination, malicious peers have little exploit potential (e.g. attempting to+stop the group from searching the DHT).++In addition, peers who join a group via the DHT will attempt to connect to any+nodes that are not in their freshly synced peer list.++\section{DHT Announcements}++Groupchats make use of the Tox DHT network in order to allow for groups that can+be joined by anyone who possesses the Chat ID. As all of the information stored+in or passed through the DHT can be viewed by any of the involved nodes, these+types of groups are considered to be public. Private groups in contrast do not+make use of the DHT for any purpose, and as such require a friend invite in+order to join.++\subsection{Announcement requests}++When peers create or successfully join a public group they send an+\textbf{announcement request}, containing information about the group that+they're announcing and themselves to K of their close DHT nodes. The information+in this request includes the announcer's group public encryption key and IP+address/port, as well as the Chat ID of the group. The DHT attempts to store+this announcement in the node that's closest to the Chat ID (\textbf{closeness}+is calculated by the DHT's close function). DHT nodes can store up to N+announcements each, after which they will replace the oldest announcements+first. See the \href{#redundancy}{Redundancy} section for details on how DDoS+attacks are mitigated.++\subsection{Get nodes requests}++When peers attempt to join a public group using the Chat ID they send a+\textbf{get nodes request}, containing their IP/port, their group public+encryption key, and the Chat ID to K of their close nodes. Those nodes will then+check if any of their announcement entries match the supplied Chat ID. If not,+they will relay the message to K of their own close nodes who will repeat the+process (note that the close function guarantees that each successive relay will+bring us closer to the Chat ID until we either find one of its entries, or have+traversed the entire DHT network).++Once a node finds an entry with the queried Chat ID it will send a \textbf{send+nodes response} to the original node who made the request. The response will+contain at least one entry (possibly more) which will hold the group public+encryption key and the IP address/port of a peer who had previously made an+announcement request for Chat ID. With this information the requester will+automatically initiate the handshake protocol and attempt to join the group.++\subsection{Redundancy}++DHT nodes will send ping requests to all of their announcement entries+periodically in order to ensure that they are still present in the+network/group. When a peer goes offline or leaves a group, they no longer+respond to these ping requests, and the nodes holding their entries will discard+them.++There are scenarios in which an announcement may be dropped from the network,+such as if the sole node holding the entry goes offline, or in the case of DDOS+attack which attempts to push all old entries out of the DHT. In order to ensure+that those announcements are not permanently lost, announcers will periodically+check when they last received a ping request for a given announcement. After a+certain amount of time without receiving a ping request they will assume that+their entry is no longer in the DHT network and re-announce themselves. This+ensures that every peer present in a group has an active announcement in the DHT+at all times, and it also ensures that a group cannot become 'lost'.++\begin{code}+module Network.Tox.Application.GroupChats where+\end{code}
+ src/Network/Tox/Binary.hs view
@@ -0,0 +1,209 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Safe #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Network.Tox.Binary+ ( typeName+ , encode, encodeC, encodeS+ , decode, decodeC, decodeS+ ) where++import Data.Binary (Binary)+import Data.ByteString (ByteString)+import Data.MessagePack (MessagePack,+ fromObject, toObject)+import qualified Data.MessagePack as MessagePack+import Data.Proxy (Proxy (..))+import Data.Typeable (Typeable)+import qualified Data.Typeable as Typeable+import Data.Word (Word64)+import Network.MessagePack.Client (Client)+import qualified Network.MessagePack.Client as Client+import Network.MessagePack.Server (Server)+import qualified Network.MessagePack.Server as Server++import qualified Network.Tox.Encoding as Encoding++import qualified Network.Tox.Crypto.Box as T+import qualified Network.Tox.Crypto.Key as T+import qualified Network.Tox.Crypto.KeyPair as T+import qualified Network.Tox.DHT.DhtPacket as T+import qualified Network.Tox.DHT.DhtRequestPacket as T+import qualified Network.Tox.DHT.NodesRequest as T+import qualified Network.Tox.DHT.NodesResponse as T+import qualified Network.Tox.DHT.PingPacket as T+import qualified Network.Tox.DHT.RpcPacket as T+import qualified Network.Tox.NodeInfo.HostAddress as T+import qualified Network.Tox.NodeInfo.NodeInfo as T+import qualified Network.Tox.NodeInfo.PortNumber as T+import qualified Network.Tox.NodeInfo.SocketAddress as T+import qualified Network.Tox.NodeInfo.TransportProtocol as T+import qualified Network.Tox.Protocol.Packet as T+import qualified Network.Tox.Protocol.PacketKind as T+++typeName :: Typeable a => Proxy a -> String+typeName (Proxy :: Proxy a) =+ show . Typeable.typeOf $ (undefined :: a)+++data KnownType+ = CipherText T.CipherText+ | DhtPacket T.DhtPacket+ | DhtRequestPacket T.DhtRequestPacket+ | HostAddress T.HostAddress+ | Word64 Word64+ | Key T.PublicKey+ | KeyPair T.KeyPair+ | NodeInfo T.NodeInfo+ | NodesRequest T.NodesRequest+ | NodesResponse T.NodesResponse+ | Packet (T.Packet Word64)+ | PacketKind T.PacketKind+ | PingPacket T.PingPacket+ | PlainText T.PlainText+ | PortNumber T.PortNumber+ | RpcPacket (T.RpcPacket Word64)+ | SocketAddress T.SocketAddress+ | TransportProtocol T.TransportProtocol+++knownTypeToObject :: KnownType -> MessagePack.Object+knownTypeToObject = \case+ CipherText x -> toObject x+ DhtPacket x -> toObject x+ DhtRequestPacket x -> toObject x+ HostAddress x -> toObject x+ Word64 x -> toObject x+ Key x -> toObject x+ KeyPair x -> toObject x+ NodeInfo x -> toObject x+ NodesRequest x -> toObject x+ NodesResponse x -> toObject x+ Packet x -> toObject x+ PacketKind x -> toObject x+ PingPacket x -> toObject x+ PlainText x -> toObject x+ PortNumber x -> toObject x+ RpcPacket x -> toObject x+ SocketAddress x -> toObject x+ TransportProtocol x -> toObject x+++knownTypeEncode :: KnownType -> ByteString+knownTypeEncode = \case+ CipherText x -> encode x+ DhtPacket x -> encode x+ DhtRequestPacket x -> encode x+ HostAddress x -> encode x+ Word64 x -> encode x+ Key x -> encode x+ KeyPair x -> encode x+ NodeInfo x -> encode x+ NodesRequest x -> encode x+ NodesResponse x -> encode x+ Packet x -> encode x+ PacketKind x -> encode x+ PingPacket x -> encode x+ PlainText x -> encode x+ PortNumber x -> encode x+ RpcPacket x -> encode x+ SocketAddress x -> encode x+ TransportProtocol x -> encode x++++--------------------------------------------------------------------------------+--+-- :: decode+--+--------------------------------------------------------------------------------+++decode :: Binary a => ByteString -> Maybe a+decode = Encoding.decode++decodeC :: forall a. (Typeable a, MessagePack a)+ => ByteString -> Client (Maybe a)+decodeC = Client.call "Binary.decode" $ typeName (Proxy :: Proxy a)++decodeS :: Server.Method IO+decodeS = Server.method "Binary.decode"+ (Server.MethodDocs+ [ Server.MethodVal "typeName" "String"+ , Server.MethodVal "encoded" "ByteString"+ ] $ Server.MethodVal "value" "a")+ decodeKnownType++ where+ decodeKnownType :: String -> ByteString -> Server (Maybe MessagePack.Object)+ decodeKnownType = \case+ "CipherText" -> go CipherText+ "DhtPacket" -> go DhtPacket+ "DhtRequestPacket" -> go DhtRequestPacket+ "HostAddress" -> go HostAddress+ "Word64" -> go Word64+ "Key PublicKey" -> go Key+ "KeyPair" -> go KeyPair+ "NodeInfo" -> go NodeInfo+ "NodesRequest" -> go NodesRequest+ "NodesResponse" -> go NodesResponse+ "Packet Word64" -> go Packet+ "PacketKind" -> go PacketKind+ "PingPacket" -> go PingPacket+ "PlainText" -> go PlainText+ "PortNumber" -> go PortNumber+ "RpcPacket Word64" -> go RpcPacket+ "SocketAddress" -> go SocketAddress+ "TransportProtocol" -> go TransportProtocol+ tycon -> fail $ "unknown type: " ++ tycon++ go f = return . fmap (knownTypeToObject . f) . Encoding.decode+++--------------------------------------------------------------------------------+--+-- :: encode+--+--------------------------------------------------------------------------------+++encode :: Binary a => a -> ByteString+encode = Encoding.encode++encodeC :: forall a. (Typeable a, MessagePack a)+ => a -> Client ByteString+encodeC x = Client.call "Binary.encode" (show $ Typeable.typeOf x) x++encodeS :: Server.Method IO+encodeS = Server.method "Binary.encode"+ (Server.MethodDocs+ [ Server.MethodVal "typeName" "String"+ , Server.MethodVal "value" "a"+ ] $ Server.MethodVal "encoded" "ByteString")+ encodeKnownType++ where+ encodeKnownType :: String -> MessagePack.Object -> Server ByteString+ encodeKnownType = \case+ "CipherText" -> go CipherText+ "DhtPacket" -> go DhtPacket+ "DhtRequestPacket" -> go DhtRequestPacket+ "HostAddress" -> go HostAddress+ "Word64" -> go Word64+ "Key PublicKey" -> go Key+ "KeyPair" -> go KeyPair+ "NodeInfo" -> go NodeInfo+ "NodesRequest" -> go NodesRequest+ "NodesResponse" -> go NodesResponse+ "Packet Word64" -> go Packet+ "PacketKind" -> go PacketKind+ "PingPacket" -> go PingPacket+ "PlainText" -> go PlainText+ "PortNumber" -> go PortNumber+ "RpcPacket Word64" -> go RpcPacket+ "SocketAddress" -> go SocketAddress+ "TransportProtocol" -> go TransportProtocol+ tycon -> fail $ "unknown type: " ++ tycon++ go f = fmap (knownTypeEncode . f) . fromObject
− src/Network/Tox/C.hs
@@ -1,10 +0,0 @@-module Network.Tox.C- ( module M- ) where--import Network.Tox.C.Callbacks as M-import Network.Tox.C.Constants as M-import Network.Tox.C.Options as M-import Network.Tox.C.Tox as M-import Network.Tox.C.Type as M-import Network.Tox.C.Version as M
− src/Network/Tox/C/CEnum.hs
@@ -1,37 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE Trustworthy #-}-module Network.Tox.C.CEnum where--import Control.Applicative ((<$>))-import Foreign.C.Types (CInt)-import Foreign.Marshal.Alloc (alloca)-import Foreign.Ptr (Ptr)-import Foreign.Storable (Storable (..))---newtype CEnum a = CEnum { unCEnum :: CInt }- deriving (Storable)--instance (Enum a, Show a) => Show (CEnum a) where- show cen = show (toEnum $ fromIntegral $ unCEnum cen :: a)---toCEnum :: Enum a => a -> CEnum a-toCEnum = CEnum . fromIntegral . fromEnum---fromCEnum :: Enum a => CEnum a -> a-fromCEnum = toEnum . fromIntegral . unCEnum---type CErr err = Ptr (CEnum err)--callErrFun :: (Eq err, Enum err, Bounded err)- => (CErr err -> IO r) -> IO (Either err r)-callErrFun f = alloca $ \errPtr -> do- res <- f errPtr- err <- toEnum . fromIntegral . unCEnum <$> peek errPtr- return $ if err /= minBound- then Left err- else Right res
− src/Network/Tox/C/Callbacks.hs
@@ -1,89 +0,0 @@-module Network.Tox.C.Callbacks where--import Control.Exception (bracket)-import Foreign.Ptr (freeHaskellFunPtr, nullFunPtr)--import qualified Network.Tox.C.Tox as Tox-import Network.Tox.C.Type (Tox)----- | Low level event handler. The functions in this class are directly--- registered with the corresponding C callback. We use 'StablePtr' to pass--- opaque Haskell values around in C.-class CHandler a where- cSelfConnectionStatus :: Tox.SelfConnectionStatusCb a- cSelfConnectionStatus _ _ = return- cFriendName :: Tox.FriendNameCb a- cFriendName _ _ _ = return- cFriendStatusMessage :: Tox.FriendStatusMessageCb a- cFriendStatusMessage _ _ _ = return- cFriendStatus :: Tox.FriendStatusCb a- cFriendStatus _ _ _ = return- cFriendConnectionStatus :: Tox.FriendConnectionStatusCb a- cFriendConnectionStatus _ _ _ = return- cFriendTyping :: Tox.FriendTypingCb a- cFriendTyping _ _ _ = return- cFriendReadReceipt :: Tox.FriendReadReceiptCb a- cFriendReadReceipt _ _ _ = return- cFriendRequest :: Tox.FriendRequestCb a- cFriendRequest _ _ _ = return- cFriendMessage :: Tox.FriendMessageCb a- cFriendMessage _ _ _ _ = return- cFileRecvControl :: Tox.FileRecvControlCb a- cFileRecvControl _ _ _ _ = return- cFileChunkRequest :: Tox.FileChunkRequestCb a- cFileChunkRequest _ _ _ _ _ = return- cFileRecv :: Tox.FileRecvCb a- cFileRecv _ _ _ _ _ _ = return- cFileRecvChunk :: Tox.FileRecvChunkCb a- cFileRecvChunk _ _ _ _ _ = return- cConferenceInvite :: Tox.ConferenceInviteCb a- cConferenceInvite _ _ _ _ = return- cConferenceMessage :: Tox.ConferenceMessageCb a- cConferenceMessage _ _ _ _ _ = return- cConferenceTitle :: Tox.ConferenceTitleCb a- cConferenceTitle _ _ _ _ = return- cConferencePeerName :: Tox.ConferencePeerNameCb a- cConferencePeerName _ _ _ _ = return- cConferencePeerListChanged :: Tox.ConferencePeerListChangedCb a- cConferencePeerListChanged _ _ = return- cFriendLossyPacket :: Tox.FriendLossyPacketCb a- cFriendLossyPacket _ _ _ = return- cFriendLosslessPacket :: Tox.FriendLosslessPacketCb a- cFriendLosslessPacket _ _ _ = return----- | Installs an event handler into the passed 'Tox' instance. After performing--- the IO action, all event handlers are reset to null. This function does not--- save the original event handlers.-withCHandler :: CHandler a => Tox a -> IO r -> IO r-withCHandler tox =- install Tox.tox_callback_self_connection_status (Tox.selfConnectionStatusCb cSelfConnectionStatus ) .- install Tox.tox_callback_friend_name (Tox.friendNameCb cFriendName ) .- install Tox.tox_callback_friend_status_message (Tox.friendStatusMessageCb cFriendStatusMessage ) .- install Tox.tox_callback_friend_status (Tox.friendStatusCb cFriendStatus ) .- install Tox.tox_callback_friend_connection_status (Tox.friendConnectionStatusCb cFriendConnectionStatus ) .- install Tox.tox_callback_friend_typing (Tox.friendTypingCb cFriendTyping ) .- install Tox.tox_callback_friend_read_receipt (Tox.friendReadReceiptCb cFriendReadReceipt ) .- install Tox.tox_callback_friend_request (Tox.friendRequestCb cFriendRequest ) .- install Tox.tox_callback_friend_message (Tox.friendMessageCb cFriendMessage ) .- install Tox.tox_callback_file_recv_control (Tox.fileRecvControlCb cFileRecvControl ) .- install Tox.tox_callback_file_chunk_request (Tox.fileChunkRequestCb cFileChunkRequest ) .- install Tox.tox_callback_file_recv (Tox.fileRecvCb cFileRecv ) .- install Tox.tox_callback_file_recv_chunk (Tox.fileRecvChunkCb cFileRecvChunk ) .- install Tox.tox_callback_conference_invite (Tox.conferenceInviteCb cConferenceInvite ) .- install Tox.tox_callback_conference_message (Tox.conferenceMessageCb cConferenceMessage ) .- install Tox.tox_callback_conference_title (Tox.conferenceTitleCb cConferenceTitle ) .- install Tox.tox_callback_conference_peer_name (Tox.conferencePeerNameCb cConferencePeerName ) .- install Tox.tox_callback_conference_peer_list_changed (Tox.conferencePeerListChangedCb cConferencePeerListChanged ) .- install Tox.tox_callback_friend_lossy_packet (Tox.friendLossyPacketCb cFriendLossyPacket ) .- install Tox.tox_callback_friend_lossless_packet (Tox.friendLosslessPacketCb cFriendLosslessPacket )- where- install cInstall wrapper action =- bracket wrapper (uninstall cInstall) $ \cb -> do- () <- cInstall tox cb- action-- uninstall cInstall cb = do- freeHaskellFunPtr cb- cInstall tox nullFunPtr
− src/Network/Tox/C/Constants.hs
@@ -1,50 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}-{-# LANGUAGE Trustworthy #-}-module Network.Tox.C.Constants where--import Data.Word (Word32)----------------------------------------------------------------------------------------- :: Numeric constants---------------------------------------------------------------------------------------- | The size of a Tox Public Key in bytes.-foreign import ccall tox_public_key_size :: Word32---- | The size of a Tox Secret Key in bytes.-foreign import ccall tox_secret_key_size :: Word32---- | The size of a Tox address in bytes. Tox addresses are in the format--- [Public Key ('tox_public_key_size' bytes)][nospam (4 bytes)][checksum (2 bytes)].------ The checksum is computed over the Public Key and the nospam value. The first--- byte is an XOR of all the even bytes (0, 2, 4, ...), the second byte is an--- XOR of all the odd bytes (1, 3, 5, ...) of the Public Key and nospam.-foreign import ccall tox_address_size :: Word32---- | Maximum length of a nickname in bytes.-foreign import ccall tox_max_name_length :: Word32---- | Maximum length of a status message in bytes.-foreign import ccall tox_max_status_message_length :: Word32---- | Maximum length of a friend request message in bytes.-foreign import ccall tox_max_friend_request_length :: Word32---- | Maximum length of a single message after which it should be split.-foreign import ccall tox_max_message_length :: Word32---- | Maximum size of custom packets. TODO: should be LENGTH?-foreign import ccall tox_max_custom_packet_size :: Word32---- | The number of bytes in a hash generated by tox_hash.-foreign import ccall tox_hash_length :: Word32---- | The number of bytes in a file id.-foreign import ccall tox_file_id_length :: Word32---- | Maximum file name length for file transfers.-foreign import ccall tox_max_filename_length :: Word32
− src/Network/Tox/C/Options.hs
@@ -1,317 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE Safe #-}-module Network.Tox.C.Options where--import Control.Applicative ((<$>))-import Control.Exception (bracket)-import Data.ByteString (ByteString)-import qualified Data.ByteString as BS-import Data.Default.Class (Default (..))-import Data.Word (Word16)-import Foreign.C.String (CString, peekCString, withCString)-import Foreign.C.Types (CInt (..), CSize (..))-import Foreign.Ptr (Ptr, nullPtr)-import GHC.Generics (Generic)--import Network.Tox.C.CEnum---------------------------------------------------------------------------------------- :: Startup options----------------------------------------------------------------------------------------- | Type of proxy used to connect to TCP relays.-data ProxyType- = ProxyTypeNone- -- Don't use a proxy.- | ProxyTypeHttp- -- HTTP proxy using CONNECT.- | ProxyTypeSocks5- -- SOCKS proxy for simple socket pipes.- deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)----- Type of savedata to create the Tox instance from.-data SavedataType- = SavedataTypeNone- -- No savedata.- | SavedataTypeToxSave- -- Savedata is one that was obtained from tox_get_savedata- | SavedataTypeSecretKey- -- Savedata is a secret key of length 'tox_secret_key_size'- deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)----- This struct contains all the startup options for Tox. You can either allocate--- this object yourself, and pass it to tox_options_default, or call--- tox_options_new to get a new default options object.-data Options = Options- { ipv6Enabled :: Bool- -- The type of socket to create.- --- -- If this is set to false, an IPv4 socket is created, which subsequently- -- only allows IPv4 communication.- -- If it is set to true, an IPv6 socket is created, allowing both IPv4 and- -- IPv6 communication.-- , udpEnabled :: Bool- -- Enable the use of UDP communication when available.- --- -- Setting this to false will force Tox to use TCP only. Communications will- -- need to be relayed through a TCP relay node, potentially slowing them- -- down. Disabling UDP support is necessary when using anonymous proxies or- -- Tor.-- , proxyType :: ProxyType- -- Pass communications through a proxy.-- , proxyHost :: String- -- The IP address or DNS name of the proxy to be used.- --- -- If used, this must be non-'nullPtr' and be a valid DNS name. The name- -- must not exceed 255 ('tox_max_filename_length') characters, and be in a- -- NUL-terminated C string format (255 chars + 1 NUL byte).- --- -- This member is ignored (it can be 'nullPtr') if proxy_type is- -- ProxyTypeNone.-- , proxyPort :: Word16- -- The port to use to connect to the proxy server.- --- -- Ports must be in the range (1, 65535). The value is ignored if proxy_type- -- is ProxyTypeNone.-- , startPort :: Word16- -- The start port of the inclusive port range to attempt to use.- --- -- If both 'startPort' and 'endPort' are 0, the default port range will be- -- used: [33445, 33545].- --- -- If either 'startPort' or 'endPort' is 0 while the other is non-zero, the- -- non-zero port will be the only port in the range.- --- -- Having 'startPort' > 'endport' will yield the same behavior as if- -- 'startPort' and 'endPort' were swapped.-- , endPort :: Word16- -- The end port of the inclusive port range to attempt to use.-- , tcpPort :: Word16- -- The port to use for the TCP server (relay). If 0, the TCP server is- -- disabled.- --- -- Enabling it is not required for Tox to function properly.- --- -- When enabled, your Tox instance can act as a TCP relay for other Tox- -- instance. This leads to increased traffic, thus when writing a client it- -- is recommended to enable TCP server only if the user has an option to- -- disable it.-- , savedataType :: SavedataType- -- The type of savedata to load from.-- , savedataData :: ByteString- -- The savedata bytes.- }- deriving (Eq, Read, Show, Generic)---instance Default Options where- def = Options- { ipv6Enabled = True- , udpEnabled = True- , proxyType = ProxyTypeNone- , proxyHost = ""- , proxyPort = 0- , startPort = 0- , endPort = 0- , tcpPort = 0- , savedataType = SavedataTypeNone- , savedataData = BS.empty- }---data OptionsStruct-type OptionsPtr = Ptr OptionsStruct---foreign import ccall tox_options_get_ipv6_enabled :: OptionsPtr -> IO Bool-foreign import ccall tox_options_get_udp_enabled :: OptionsPtr -> IO Bool-foreign import ccall tox_options_get_proxy_type :: OptionsPtr -> IO (CEnum ProxyType)-foreign import ccall tox_options_get_proxy_host :: OptionsPtr -> IO CString-foreign import ccall tox_options_get_proxy_port :: OptionsPtr -> IO Word16-foreign import ccall tox_options_get_start_port :: OptionsPtr -> IO Word16-foreign import ccall tox_options_get_end_port :: OptionsPtr -> IO Word16-foreign import ccall tox_options_get_tcp_port :: OptionsPtr -> IO Word16-foreign import ccall tox_options_get_savedata_type :: OptionsPtr -> IO (CEnum SavedataType)-foreign import ccall tox_options_get_savedata_data :: OptionsPtr -> IO CString-foreign import ccall tox_options_get_savedata_length :: OptionsPtr -> IO CSize--foreign import ccall tox_options_set_ipv6_enabled :: OptionsPtr -> Bool -> IO ()-foreign import ccall tox_options_set_udp_enabled :: OptionsPtr -> Bool -> IO ()-foreign import ccall tox_options_set_proxy_type :: OptionsPtr -> CEnum ProxyType -> IO ()-foreign import ccall tox_options_set_proxy_host :: OptionsPtr -> CString -> IO ()-foreign import ccall tox_options_set_proxy_port :: OptionsPtr -> Word16 -> IO ()-foreign import ccall tox_options_set_start_port :: OptionsPtr -> Word16 -> IO ()-foreign import ccall tox_options_set_end_port :: OptionsPtr -> Word16 -> IO ()-foreign import ccall tox_options_set_tcp_port :: OptionsPtr -> Word16 -> IO ()-foreign import ccall tox_options_set_savedata_type :: OptionsPtr -> CEnum SavedataType -> IO ()-foreign import ccall tox_options_set_savedata_data :: OptionsPtr -> CString -> CSize -> IO ()-foreign import ccall tox_options_set_savedata_length :: OptionsPtr -> CSize -> IO ()---data ErrOptionsNew- = ErrOptionsNewOk- -- The function returned successfully.-- | ErrOptionsNewMalloc- -- The function was unable to allocate enough memory to store the internal- -- structures for the Tox options object.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Allocates a new Tox_Options object and initialises it with the default--- options. This function can be used to preserve long term ABI compatibility by--- giving the responsibility of allocation and deallocation to the Tox library.------ Objects returned from this function must be freed using the tox_options_free--- function.------ @return A new 'OptionsPtr' with default options or 'nullPtr' on failure.-foreign import ccall tox_options_new :: CErr ErrOptionsNew -> IO OptionsPtr--toxOptionsNew :: IO (Either ErrOptionsNew OptionsPtr)-toxOptionsNew = callErrFun tox_options_new----- | Releases all resources associated with an options objects.------ Passing a pointer that was not returned by tox_options_new results in--- undefined behaviour.-foreign import ccall tox_options_free :: OptionsPtr -> IO ()---withToxOptions :: (OptionsPtr -> IO r) -> IO (Either ErrOptionsNew r)-withToxOptions f =- bracket toxOptionsNew (either (const $ return ()) tox_options_free) $ \case- Left err -> return $ Left err- Right ok -> Right <$> f ok----- | Read 'Options' from an 'OptionsPtr'.------ If the passed pointer is 'nullPtr', the behaviour is undefined.-peekToxOptions :: OptionsPtr -> IO Options-peekToxOptions ptr = do- cIpv6Enabled <- tox_options_get_ipv6_enabled ptr- cUdpEnabled <- tox_options_get_udp_enabled ptr- cProxyType <- tox_options_get_proxy_type ptr- cProxyHost <- tox_options_get_proxy_host ptr >>= peekNullableString- cProxyPort <- tox_options_get_proxy_port ptr- cStartPort <- tox_options_get_start_port ptr- cEndPort <- tox_options_get_end_port ptr- cTcpPort <- tox_options_get_tcp_port ptr- cSavedataType <- tox_options_get_savedata_type ptr- cSavedataData <- tox_options_get_savedata_data ptr- cSavedataLength <- tox_options_get_savedata_length ptr- cSavedata <- BS.packCStringLen- ( cSavedataData- , fromIntegral cSavedataLength)- return Options- { ipv6Enabled = cIpv6Enabled- , udpEnabled = cUdpEnabled- , proxyType = fromCEnum cProxyType- , proxyHost = cProxyHost- , proxyPort = cProxyPort- , startPort = cStartPort- , endPort = cEndPort- , tcpPort = cTcpPort- , savedataType = fromCEnum cSavedataType- , savedataData = cSavedata- }-- where- -- 'peekCString' doesn't handle NULL strings as empty, unlike- -- 'packCStringLen', which ignores the pointer to zero-length 'CString's.- peekNullableString p =- if p == nullPtr- then return ""- else peekCString p----- | Save the options from the passed 'OptionsPtr', perform an IO action, and--- restore the original values.------ If the passed pointer is 'nullPtr', the behaviour is undefined.-saveToxOptions :: OptionsPtr -> IO r -> IO r-saveToxOptions ptr =- bracket saveOptions restoreOptions . const- where- saveOptions = do- v0 <- tox_options_get_ipv6_enabled ptr- v1 <- tox_options_get_udp_enabled ptr- v2 <- tox_options_get_proxy_type ptr- v3 <- tox_options_get_proxy_host ptr- v4 <- tox_options_get_proxy_port ptr- v5 <- tox_options_get_start_port ptr- v6 <- tox_options_get_end_port ptr- v7 <- tox_options_get_tcp_port ptr- v8 <- tox_options_get_savedata_type ptr- sd <- tox_options_get_savedata_data ptr- sl <- tox_options_get_savedata_length ptr- return (v0, v1, v2, v3, v4, v5, v6, v7, v8, sd, sl)-- restoreOptions (v0, v1, v2, v3, v4, v5, v6, v7, v8, sd, sl) = do- tox_options_set_ipv6_enabled ptr v0- tox_options_set_udp_enabled ptr v1- tox_options_set_proxy_type ptr v2- tox_options_set_proxy_host ptr v3- tox_options_set_proxy_port ptr v4- tox_options_set_start_port ptr v5- tox_options_set_end_port ptr v6- tox_options_set_tcp_port ptr v7- tox_options_set_savedata_type ptr v8- tox_options_set_savedata_data ptr sd sl- tox_options_set_savedata_length ptr sl----- | Fill in the 'Options' values into the 'OptionsPtr' and perform the IO--- action afterwards.------ This function restores the original values from the 'OptionsPtr' after--- performing the action.------ If the passed pointer is 'nullPtr', the behaviour is undefined.-pokeToxOptions :: Options -> OptionsPtr -> IO r -> IO r-pokeToxOptions options ptr action =- saveToxOptions ptr $- withCString (proxyHost options) $ \host ->- BS.useAsCStringLen (savedataData options) $ \(saveData, saveLenInt) -> do- let saveLen = fromIntegral saveLenInt- tox_options_set_ipv6_enabled ptr $ ipv6Enabled options- tox_options_set_udp_enabled ptr $ udpEnabled options- tox_options_set_proxy_type ptr $ toCEnum $ proxyType options- tox_options_set_proxy_host ptr host- tox_options_set_proxy_port ptr $ proxyPort options- tox_options_set_start_port ptr $ startPort options- tox_options_set_end_port ptr $ endPort options- tox_options_set_tcp_port ptr $ tcpPort options- tox_options_set_savedata_type ptr $ toCEnum $ savedataType options- tox_options_set_savedata_data ptr saveData saveLen- tox_options_set_savedata_length ptr saveLen- action----- | Allocate a new C options pointer, fills in the values from 'Options',--- calls the processor function, and deallocates the options pointer.------ The 'OptionsPtr' passed to the processor function is never 'nullPtr'. If--- allocation fails, the IO action evaluates to 'Left' with an appropriate--- error code.-withOptions :: Options -> (OptionsPtr -> IO r) -> IO (Either ErrOptionsNew r)-withOptions options f =- withToxOptions $ \ptr ->- pokeToxOptions options ptr (f ptr)
− src/Network/Tox/C/Tox.hs
@@ -1,2405 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE Safe #-}--- | Public core API for Tox clients.------ Every function that can fail takes a function-specific error code pointer--- that can be used to diagnose problems with the Tox state or the function--- arguments. The error code pointer can be 'nullPtr', which does not influence--- the function's behaviour, but can be done if the reason for failure is--- irrelevant to the client.------ The exception to this rule are simple allocation functions whose only failure--- mode is allocation failure. They return 'nullPtr' in that case, and do not--- set an error code.------ Every error code type has an OK value to which functions will set their error--- code value on success. Clients can keep their error code uninitialised before--- passing it to a function. The library guarantees that after returning, the--- value pointed to by the error code pointer has been initialised.------ Functions with pointer parameters often have a 'nullPtr' error code, meaning--- they could not perform any operation, because one of the required parameters--- was 'nullPtr'. Some functions operate correctly or are defined as effectless--- on 'nullPtr'.------ Some functions additionally return a value outside their return type domain,--- or a bool containing true on success and false on failure.------ All functions that take a Tox instance pointer will cause undefined behaviour--- when passed a 'nullPtr' Tox pointer.------ All integer values are expected in host byte order.------ Functions with parameters with enum types cause unspecified behaviour if the--- enumeration value is outside the valid range of the type. If possible, the--- function will try to use a sane default, but there will be no error code, and--- one possible action for the function to take is to have no effect.------ \subsection events Events and callbacks------ Events are handled by callbacks. One callback can be registered per event.--- All events have a callback function type named `tox_{event}_cb` and a--- function to register it named `tox_callback_{event}`. Passing a 'nullPtr'--- callback will result in no callback being registered for that event. Only one--- callback per event can be registered, so if a client needs multiple event--- listeners, it needs to implement the dispatch functionality itself.------ \subsection threading Threading implications------ It is possible to run multiple concurrent threads with a Tox instance for--- each thread. It is also possible to run all Tox instances in the same thread.--- A common way to run Tox (multiple or single instance) is to have one thread--- running a simple tox_iterate loop, sleeping for tox_iteration_interval--- milliseconds on each iteration.------ If you want to access a single Tox instance from multiple threads, access to--- the instance must be synchronised. While multiple threads can concurrently--- access multiple different Tox instances, no more than one API function can--- operate on a single instance at any given time.------ Functions that write to variable length byte arrays will always have a size--- function associated with them. The result of this size function is only valid--- until another mutating function (one that takes a pointer to non-const Tox)--- is called. Thus, clients must ensure that no other thread calls a mutating--- function between the call to the size function and the call to the retrieval--- function.------ E.g. to get the current nickname, one would write------ \code--- CSize length = tox_self_get_name_size(tox);--- CString name = malloc(length);--- if (!name) abort();--- tox_self_get_name(tox, name);--- \endcode------ If any other thread calls tox_self_set_name while this thread is allocating--- memory, the length may have become invalid, and the call to tox_self_get_name--- may cause undefined behaviour.----module Network.Tox.C.Tox where--import Control.Applicative ((<$>))-import Control.Concurrent.MVar (MVar, modifyMVar_)-import Control.Exception (bracket)-import Control.Monad ((>=>))-import qualified Data.ByteString as BS-import Data.Word (Word16, Word32, Word64)-import Foreign.C.String (CString, peekCStringLen, withCString,- withCStringLen)-import Foreign.C.Types (CChar (..), CInt (..), CSize (..),- CTime (..))-import Foreign.Marshal.Alloc (alloca)-import Foreign.Marshal.Array (allocaArray, peekArray)-import Foreign.Ptr (FunPtr, Ptr, nullPtr)-import Foreign.StablePtr (deRefStablePtr, freeStablePtr,- newStablePtr)-import Foreign.Storable (peek)-import System.Posix.Types (EpochTime)--import Network.Tox.C.CEnum-import Network.Tox.C.Constants-import Network.Tox.C.Options-import Network.Tox.C.Type----------------------------------------------------------------------------------------- :: Global types---------------------------------------------------------------------------------------- Should we introduce such types?--- newtype FriendNum = FriendNum { friendNum :: Word32 } deriving (Eq, Ord, Read, Show)--- newtype ConferenceNum = ConferenceNum { conferenceNum :: Word32 } deriving (Eq, Ord, Read, Show)--- newtype PeerNum = PeerNum { peerNum :: Word32 } deriving (Eq, Ord, Read, Show)--- newtype FileNum = FileNum { fileNum :: Word32 } deriving (Eq, Ord, Read, Show)---- | Represents the possible statuses a client can have.-data UserStatus- = UserStatusNone- -- ^ User is online and available.- | UserStatusAway- -- ^ User is away. Clients can set this e.g. after a user defined inactivity- -- time.- | UserStatusBusy- -- ^ User is busy. Signals to other clients that this client does not- -- currently wish to communicate.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Represents message types for tox_friend_send_message and group chat--- messages.-data MessageType- = MessageTypeNormal- -- ^ Normal text message. Similar to PRIVMSG on IRC.- | MessageTypeAction- -- ^ A message describing an user action. This is similar to /me (CTCP- -- ACTION) on IRC.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----------------------------------------------------------------------------------------- :: Creation and destruction---------------------------------------------------------------------------------------data ErrNew- = ErrNewOk- -- The function returned successfully.-- | ErrNewNull- -- One of the arguments to the function was 'nullPtr' when it was not- -- expected.-- | ErrNewMalloc- -- The function was unable to allocate enough memory to store the internal- -- structures for the Tox object.-- | ErrNewPortAlloc- -- The function was unable to bind to a port. This may mean that all ports- -- have already been bound, e.g. by other Tox instances, or it may mean a- -- permission error. You may be able to gather more information from errno.-- | ErrNewProxyBadType- -- proxy_type was invalid.-- | ErrNewProxyBadHost- -- proxy_type was valid but the proxy_host passed had an invalid format or- -- was 'nullPtr'.-- | ErrNewProxyBadPort- -- proxy_type was valid, but the proxy_port was invalid.-- | ErrNewProxyNotFound- -- The proxy address passed could not be resolved.-- | ErrNewLoadEncrypted- -- The byte array to be loaded contained an encrypted save.-- | ErrNewLoadBadFormat- -- The data format was invalid. This can happen when loading data that was- -- saved by an older version of Tox, or when the data has been corrupted.- -- When loading from badly formatted data, some data may have been loaded,- -- and the rest is discarded. Passing an invalid length parameter also- -- causes this error.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- @brief Creates and initialises a new Tox instance with the options passed.------ This function will bring the instance into a valid state. Running the event--- loop with a new instance will operate correctly.------ If loading failed or succeeded only partially, the new or partially loaded--- instance is returned and an error code is set.------ @param options An options object as described above. If this parameter is--- 'nullPtr', the default options are used.------ @see tox_iterate for the event loop.------ @return A new Tox instance pointer on success or 'nullPtr' on failure.-foreign import ccall tox_new :: OptionsPtr -> CErr ErrNew -> IO (Tox a)--toxNew :: OptionsPtr -> IO (Either ErrNew (Tox a))-toxNew = callErrFun . tox_new---- | Releases all resources associated with the Tox instance and disconnects--- from the network.------ After calling this function, the Tox pointer becomes invalid. No other--- functions can be called, and the pointer value can no longer be read.-foreign import ccall tox_kill :: Tox a -> IO ()--toxKill :: Tox a -> IO ()-toxKill = tox_kill--withTox :: OptionsPtr -> (Tox a -> IO r) -> IO (Either ErrNew r)-withTox options f =- bracket (toxNew options) (either (const $ return ()) toxKill) $ \case- Left err -> return $ Left err- Right ok -> Right <$> f ok---withDefaultTox :: (Tox a -> IO r) -> IO (Either ErrNew r)-withDefaultTox = withTox nullPtr----- | Calculates the number of bytes required to store the tox instance with--- tox_get_savedata. This function cannot fail. The result is always greater--- than 0.------ @see threading for concurrency implications.-foreign import ccall tox_get_savedata_size :: Tox a -> IO CSize---- | Store all information associated with the tox instance to a byte array.------ @param data A memory region large enough to store the tox instance data.--- Call tox_get_savedata_size to find the number of bytes required. If this--- parameter is 'nullPtr', this function has no effect.-foreign import ccall tox_get_savedata :: Tox a -> CString -> IO ()--toxGetSavedata :: Tox a -> IO BS.ByteString-toxGetSavedata tox = do- savedataLen <- tox_get_savedata_size tox- allocaArray (fromIntegral savedataLen) $ \savedataPtr -> do- tox_get_savedata tox savedataPtr- BS.packCStringLen (savedataPtr, fromIntegral savedataLen)----------------------------------------------------------------------------------------- :: Connection lifecycle and event loop---------------------------------------------------------------------------------------data ErrBootstrap- = ErrBootstrapOk- -- The function returned successfully.-- | ErrBootstrapNull- -- One of the arguments to the function was 'nullPtr' when it was not- -- expected.-- | ErrBootstrapBadHost- -- The address could not be resolved to an IP address, or the IP address- -- passed was invalid.-- | ErrBootstrapBadPort- -- The port passed was invalid. The valid port range is (1, 65535).- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Sends a "get nodes" request to the given bootstrap node with IP, port, and--- public key to setup connections.------ This function will attempt to connect to the node using UDP. You must use--- this function even if Options.udp_enabled was set to false.------ @param address The hostname or IP address (IPv4 or IPv6) of the node.--- @param port The port on the host on which the bootstrap Tox instance is--- listening.--- @param public_key The long term public key of the bootstrap node--- ('tox_public_key_size' bytes).--- @return true on success.-foreign import ccall tox_bootstrap :: Tox a -> CString -> Word16 -> CString -> CErr ErrBootstrap -> IO ()--callBootstrapFun- :: (Tox a -> CString -> Word16 -> CString -> CErr ErrBootstrap -> IO ())- -> Tox a -> String -> Word16 -> BS.ByteString -> IO (Either ErrBootstrap ())-callBootstrapFun f tox address port pubKey =- withCString address $ \address' ->- BS.useAsCString pubKey $ \pubKey' ->- callErrFun $ f tox address' (fromIntegral port) pubKey'--toxBootstrap :: Tox a -> String -> Word16 -> BS.ByteString -> IO (Either ErrBootstrap ())-toxBootstrap = callBootstrapFun tox_bootstrap----- | Adds additional host:port pair as TCP relay.------ This function can be used to initiate TCP connections to different ports on--- the same bootstrap node, or to add TCP relays without using them as--- bootstrap nodes.------ @param address The hostname or IP address (IPv4 or IPv6) of the TCP relay.--- @param port The port on the host on which the TCP relay is listening.--- @param public_key The long term public key of the TCP relay--- ('tox_public_key_size' bytes).--- @return true on success.-foreign import ccall tox_add_tcp_relay :: Tox a -> CString -> Word16 -> CString -> CErr ErrBootstrap -> IO ()--toxAddTcpRelay :: Tox a -> String -> Word16 -> BS.ByteString -> IO (Either ErrBootstrap ())-toxAddTcpRelay = callBootstrapFun tox_add_tcp_relay----- | Protocols that can be used to connect to the network or friends.-data Connection- = ConnectionNone- -- There is no connection. This instance, or the friend the state change is- -- about, is now offline.-- | ConnectionTcp- -- A TCP connection has been established. For the own instance, this means- -- it is connected through a TCP relay, only. For a friend, this means that- -- the connection to that particular friend goes through a TCP relay.-- | ConnectionUdp- -- A UDP connection has been established. For the own instance, this means- -- it is able to send UDP packets to DHT nodes, but may still be connected- -- to a TCP relay. For a friend, this means that the connection to that- -- particular friend was built using direct UDP packets.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | @param connection_status Whether we are connected to the DHT.-type SelfConnectionStatusCb a = Tox a -> Connection -> a -> IO a-type CSelfConnectionStatusCb a = Tox a -> CEnum Connection -> UserData a -> IO ()-foreign import ccall "wrapper" wrapSelfConnectionStatusCb :: CSelfConnectionStatusCb a -> IO (FunPtr (CSelfConnectionStatusCb a))--callSelfConnectionStatusCb :: SelfConnectionStatusCb a -> CSelfConnectionStatusCb a-callSelfConnectionStatusCb f tox conn = deRefStablePtr >=> (`modifyMVar_` f tox (fromCEnum conn))--selfConnectionStatusCb :: SelfConnectionStatusCb a -> IO (FunPtr (CSelfConnectionStatusCb a))-selfConnectionStatusCb = wrapSelfConnectionStatusCb . callSelfConnectionStatusCb----- | Set the callback for the `self_connection_status` event. Pass 'nullPtr' to--- unset.------ This event is triggered whenever there is a change in the DHT connection--- state. When disconnected, a client may choose to call tox_bootstrap again, to--- reconnect to the DHT. Note that this state may frequently change for short--- amounts of time. Clients should therefore not immediately bootstrap on--- receiving a disconnect.------ TODO: how long should a client wait before bootstrapping again?-foreign import ccall tox_callback_self_connection_status :: Tox a -> FunPtr (CSelfConnectionStatusCb a) -> IO ()---- | Return the time in milliseconds before tox_iterate() should be called again--- for optimal performance.-foreign import ccall tox_iteration_interval :: Tox a -> IO Word32-toxIterationInterval :: Tox a -> IO Word32-toxIterationInterval = tox_iteration_interval---- | The main loop that needs to be run in intervals of tox_iteration_interval()--- milliseconds.-foreign import ccall tox_iterate :: Tox a -> UserData a -> IO ()-toxIterate :: Tox a -> MVar a -> IO ()-toxIterate tox ud = bracket (newStablePtr ud) freeStablePtr (tox_iterate tox)----------------------------------------------------------------------------------------- :: Internal client information (Tox address/id)----------------------------------------------------------------------------------------- | Writes the Tox friend address of the client to a byte array. The address is--- not in human-readable format. If a client wants to display the address,--- formatting is required.------ @param address A memory region of at least 'tox_address_size' bytes. If this--- parameter is 'nullPtr', this function has no effect.--- @see 'tox_address_size' for the address format.-foreign import ccall tox_self_get_address :: Tox a -> CString -> IO ()--toxSelfGetAddress :: Tox a -> IO BS.ByteString-toxSelfGetAddress tox =- let addrLen = fromIntegral tox_address_size in- allocaArray addrLen $ \addrPtr -> do- tox_self_get_address tox addrPtr- BS.packCStringLen (addrPtr, addrLen)---- | Set the 4-byte nospam part of the address.------ @param nospam Any 32 bit unsigned integer.-foreign import ccall tox_self_set_nospam :: Tox a -> Word32 -> IO ()-toxSelfSetNospam :: Tox a -> Word32 -> IO ()-toxSelfSetNospam = tox_self_set_nospam---- | Get the 4-byte nospam part of the address.-foreign import ccall tox_self_get_nospam :: Tox a -> IO Word32-toxSelfGetNospam :: Tox a -> IO Word32-toxSelfGetNospam = tox_self_get_nospam---- | Copy the Tox Public Key (long term) from the Tox object.------ @param public_key A memory region of at least 'tox_public_key_size' bytes. If--- this parameter is 'nullPtr', this function has no effect.-foreign import ccall tox_self_get_public_key :: Tox a -> CString -> IO ()--toxSelfGetPublicKey :: Tox a -> IO BS.ByteString-toxSelfGetPublicKey tox =- let pkLen = fromIntegral tox_public_key_size in- allocaArray pkLen $ \pkPtr -> do- tox_self_get_public_key tox pkPtr- BS.packCStringLen (pkPtr, pkLen)---- | Copy the Tox Secret Key from the Tox object.------ @param secret_key A memory region of at least 'tox_secret_key_size' bytes. If--- this parameter is 'nullPtr', this function has no effect.-foreign import ccall tox_self_get_secret_key :: Tox a -> CString -> IO ()--toxSelfGetSecretKey :: Tox a -> IO BS.ByteString-toxSelfGetSecretKey tox =- let skLen = fromIntegral tox_secret_key_size in- allocaArray skLen $ \skPtr -> do- tox_self_get_secret_key tox skPtr- BS.packCStringLen (skPtr, skLen)----------------------------------------------------------------------------------------- :: User-visible client information (nickname/status)----------------------------------------------------------------------------------------- | Common error codes for all functions that set a piece of user-visible--- client information.-data ErrSetInfo- = ErrSetInfoOk- -- The function returned successfully.-- | ErrSetInfoNull- -- One of the arguments to the function was 'nullPtr' when it was not- -- expected.-- | ErrSetInfoTooLong- -- Information length exceeded maximum permissible size.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Set the nickname for the Tox client.------ Nickname length cannot exceed 'tox_max_name_length'. If length is 0, the name--- parameter is ignored (it can be 'nullPtr'), and the nickname is set back to--- empty.------ @param name A byte array containing the new nickname.--- @param length The size of the name byte array.------ @return true on success.-foreign import ccall tox_self_set_name :: Tox a -> CString -> CSize -> CErr ErrSetInfo -> IO ()-callSelfSetNameFun :: (Tox a -> CString -> CSize -> CErr ErrSetInfo -> IO ()) ->- Tox a -> String -> IO (Either ErrSetInfo ())-callSelfSetNameFun f tox name =- withCStringLen name $ \(nameStr, nameLen) ->- callErrFun $ f tox nameStr (fromIntegral nameLen)--toxSelfSetName :: Tox a -> String -> IO (Either ErrSetInfo ())-toxSelfSetName = callSelfSetNameFun tox_self_set_name----- | Return the length of the current nickname as passed to tox_self_set_name.------ If no nickname was set before calling this function, the name is empty,--- and this function returns 0.------ @see threading for concurrency implications.-foreign import ccall tox_self_get_name_size :: Tox a -> IO CSize---- | Write the nickname set by tox_self_set_name to a byte array.------ If no nickname was set before calling this function, the name is empty,--- and this function has no effect.------ Call tox_self_get_name_size to find out how much memory to allocate for--- the result.------ @param name A valid memory location large enough to hold the nickname.--- If this parameter is NULL, the function has no effect.-foreign import ccall tox_self_get_name :: Tox a -> CString -> IO ()--toxSelfGetName :: Tox a -> IO String-toxSelfGetName tox = do- nameLen <- tox_self_get_name_size tox- allocaArray (fromIntegral nameLen) $ \namePtr -> do- tox_self_get_name tox namePtr- peekCStringLen (namePtr, fromIntegral nameLen)----- | Set the client's status message.------ Status message length cannot exceed 'tox_max_status_message_length'. If--- length is 0, the status parameter is ignored (it can be 'nullPtr'), and the--- user status is set back to empty.-foreign import ccall tox_self_set_status_message :: Tox a -> CString -> CSize -> CErr ErrSetInfo -> IO ()-callSelfSetStatusMessageFun :: (Tox a -> CString -> CSize -> CErr ErrSetInfo -> IO ()) ->- Tox a -> String -> IO (Either ErrSetInfo ())-callSelfSetStatusMessageFun f tox statusMsg =- withCStringLen statusMsg $ \(statusMsgStr, statusMsgLen) ->- callErrFun $ f tox statusMsgStr (fromIntegral statusMsgLen)--toxSelfSetStatusMessage :: Tox a -> String -> IO (Either ErrSetInfo ())-toxSelfSetStatusMessage = callSelfSetStatusMessageFun tox_self_set_status_message----- | Return the length of the current status message as passed to tox_self_set_status_message.------ If no status message was set before calling this function, the status--- is empty, and this function returns 0.------ @see threading for concurrency implications.-foreign import ccall tox_self_get_status_message_size :: Tox a -> IO CSize----- | Write the status message set by tox_self_set_status_message to a byte array.------ If no status message was set before calling this function, the status is--- empty, and this function has no effect.------ Call tox_self_get_status_message_size to find out how much memory to allocate for--- the result.------ @param status_message A valid memory location large enough to hold the--- status message. If this parameter is NULL, the function has no effect.-foreign import ccall tox_self_get_status_message :: Tox a -> CString -> IO ()--toxSelfGetStatusMessage :: Tox a -> IO String-toxSelfGetStatusMessage tox = do- statusMessageLen <- tox_self_get_status_message_size tox- allocaArray (fromIntegral statusMessageLen) $ \statusMessagePtr -> do- tox_self_get_status_message tox statusMessagePtr- peekCStringLen (statusMessagePtr, fromIntegral statusMessageLen)----- | Set the client's user status.------ @param user_status One of the user statuses listed in the enumeration above.-foreign import ccall tox_self_set_status :: Tox a -> CEnum UserStatus -> IO ()-toxSelfSetStatus :: Tox a -> UserStatus -> IO ()-toxSelfSetStatus tox userStatus = tox_self_set_status tox $ toCEnum userStatus----------------------------------------------------------------------------------------- :: Friend list management---------------------------------------------------------------------------------------data ErrFriendAdd- = ErrFriendAddOk- -- The function returned successfully.-- | ErrFriendAddNull- -- One of the arguments to the function was 'nullPtr' when it was not- -- expected.-- | ErrFriendAddTooLong- -- The length of the friend request message exceeded- -- 'tox_max_friend_request_length'.-- | ErrFriendAddNoMessage- -- The friend request message was empty. This, and the TooLong code will- -- never be returned from tox_friend_add_norequest.-- | ErrFriendAddOwnKey- -- The friend address belongs to the sending client.-- | ErrFriendAddAlreadySent- -- A friend request has already been sent, or the address belongs to a- -- friend that is already on the friend list.-- | ErrFriendAddBadChecksum- -- The friend address checksum failed.-- | ErrFriendAddSetNewNospam- -- The friend was already there, but the nospam value was different.-- | ErrFriendAddMalloc- -- A memory allocation failed when trying to increase the friend list size.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Add a friend to the friend list and send a friend request.------ A friend request message must be at least 1 byte long and at most--- 'tox_max_friend_request_length'.------ Friend numbers are unique identifiers used in all functions that operate on--- friends. Once added, a friend number is stable for the lifetime of the Tox--- object. After saving the state and reloading it, the friend numbers may not--- be the same as before. Deleting a friend creates a gap in the friend number--- set, which is filled by the next adding of a friend. Any pattern in friend--- numbers should not be relied on.------ If more than INT32_MAX friends are added, this function causes undefined--- behaviour.------ @param address The address of the friend (returned by tox_self_get_address of--- the friend you wish to add) it must be 'tox_address_size' bytes.--- @param message The message that will be sent along with the friend request.--- @param length The length of the data byte array.------ @return the friend number on success, UINT32_MAX on failure.-foreign import ccall tox_friend_add :: Tox a -> CString -> CString -> CSize -> CErr ErrFriendAdd -> IO Word32-callFriendAddFun :: (Tox a -> CString -> CString -> CSize -> CErr ErrFriendAdd -> IO Word32) ->- Tox a -> BS.ByteString -> String -> IO (Either ErrFriendAdd Word32)-callFriendAddFun f tox address message =- withCStringLen message $ \(msgStr, msgLen) ->- BS.useAsCString address $ \addr' ->- callErrFun $ f tox addr' msgStr (fromIntegral msgLen)--toxFriendAdd :: Tox a -> BS.ByteString -> String -> IO (Either ErrFriendAdd Word32)-toxFriendAdd = callFriendAddFun tox_friend_add---- | Add a friend without sending a friend request.------ This function is used to add a friend in response to a friend request. If the--- client receives a friend request, it can be reasonably sure that the other--- client added this client as a friend, eliminating the need for a friend--- request.------ This function is also useful in a situation where both instances are--- controlled by the same entity, so that this entity can perform the mutual--- friend adding. In this case, there is no need for a friend request, either.------ @param public_key A byte array of length 'tox_public_key_size' containing the--- Public Key (not the Address) of the friend to add.------ @return the friend number on success, UINT32_MAX on failure.--- @see tox_friend_add for a more detailed description of friend numbers.-foreign import ccall tox_friend_add_norequest :: Tox a -> CString -> CErr ErrFriendAdd -> IO Word32-callFriendAddNorequestFun :: (Tox a -> CString -> CErr ErrFriendAdd -> IO Word32) ->- Tox a -> BS.ByteString -> IO (Either ErrFriendAdd Word32)-callFriendAddNorequestFun f tox address =- BS.useAsCString address $ \addr' ->- callErrFun $ f tox addr'--toxFriendAddNorequest :: Tox a -> BS.ByteString -> IO (Either ErrFriendAdd Word32)-toxFriendAddNorequest = callFriendAddNorequestFun tox_friend_add_norequest---data ErrFriendDelete- = ErrFriendDeleteOk- -- The function returned successfully.-- | ErrFriendDeleteFriendNotFound- -- There was no friend with the given friend number. No friends were- -- deleted.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Remove a friend from the friend list.------ This does not notify the friend of their deletion. After calling this--- function, this client will appear offline to the friend and no communication--- can occur between the two.------ @param friend_number Friend number for the friend to be deleted.------ @return true on success.-foreign import ccall tox_friend_delete :: Tox a -> Word32 -> CErr ErrFriendDelete -> IO ()--toxFriendDelete :: Tox a -> Word32 -> IO (Either ErrFriendDelete ())-toxFriendDelete tox fn = callErrFun $ tox_friend_delete tox fn----------------------------------------------------------------------------------------- :: Friend list queries---------------------------------------------------------------------------------------data ErrFriendByPublicKey- = ErrFriendByPublicKeyOk- -- The function returned successfully.-- | ErrFriendByPublicKeyNull- -- One of the arguments to the function was 'nullPtr' when it was not- -- expected.-- | ErrFriendByPublicKeyNotFound- -- No friend with the given Public Key exists on the friend list.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Return the friend number associated with that Public Key.------ @return the friend number on success, UINT32_MAX on failure.--- @param public_key A byte array containing the Public Key.-foreign import ccall tox_friend_by_public_key :: Tox a -> CString -> CErr ErrFriendByPublicKey -> IO Word32-callFriendByPublicKey :: (Tox a -> CString -> CErr ErrFriendByPublicKey -> IO Word32) ->- Tox a -> BS.ByteString -> IO (Either ErrFriendByPublicKey Word32)-callFriendByPublicKey f tox address =- BS.useAsCString address $ \addr' ->- callErrFun $ f tox addr'--toxFriendByPublicKey :: Tox a -> BS.ByteString -> IO (Either ErrFriendByPublicKey Word32)-toxFriendByPublicKey = callFriendByPublicKey tox_friend_by_public_key---- | Checks if a friend with the given friend number exists and returns true if--- it does.-foreign import ccall tox_friend_exists :: Tox a -> Word32 -> IO Bool-toxFriendExists :: Tox a -> Word32 -> IO Bool-toxFriendExists = tox_friend_exists---- | Return the number of friends on the friend list.------ This function can be used to determine how much memory to allocate for--- tox_self_get_friend_list.-foreign import ccall tox_self_get_friend_list_size :: Tox a -> IO CSize---- | Copy a list of valid friend numbers into an array.------ Call tox_self_get_friend_list_size to determine the number of elements to--- allocate.------ @param list A memory region with enough space to hold the friend list. If--- this parameter is 'nullPtr', this function has no effect.-foreign import ccall tox_self_get_friend_list :: Tox a -> Ptr Word32 -> IO ()--toxSelfGetFriendList :: Tox a -> IO [Word32]-toxSelfGetFriendList tox = do- friendListSize <- tox_self_get_friend_list_size tox- allocaArray (fromIntegral friendListSize) $ \friendListPtr -> do- tox_self_get_friend_list tox friendListPtr- peekArray (fromIntegral friendListSize) friendListPtr--data ErrFriendGetPublicKey- = ErrFriendGetPublicKeyOk- -- The function returned successfully.-- | ErrFriendGetPublicKeyFriendNotFound- -- No friend with the given number exists on the friend list.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Copies the Public Key associated with a given friend number to a byte--- array.------ @param friend_number The friend number you want the Public Key of.--- @param public_key A memory region of at least 'tox_public_key_size' bytes. If--- this parameter is 'nullPtr', this function has no effect.------ @return true on success.-foreign import ccall tox_friend_get_public_key :: Tox a -> Word32 -> CString -> CErr ErrFriendGetPublicKey -> IO Bool-callFriendGetPublicKey :: (Tox a -> Word32 -> CString -> CErr ErrFriendGetPublicKey -> IO Bool) ->- Tox a -> Word32 -> IO (Either ErrFriendGetPublicKey BS.ByteString)-callFriendGetPublicKey f tox fn =- let pkLen = fromIntegral tox_public_key_size in- alloca $ \errPtr ->- allocaArray pkLen $ \pkPtr -> do- _ <- f tox fn pkPtr errPtr- callGetPublicKey errPtr pkPtr pkLen--callGetPublicKey- :: (Bounded err, Enum err, Eq err)- => Ptr (CEnum err)- -> Ptr CChar- -> Int- -> IO (Either err BS.ByteString)-callGetPublicKey errPtr pkPtr pkLen = do- err <- toEnum . fromIntegral . unCEnum <$> peek errPtr- str <- BS.packCStringLen (pkPtr, pkLen)- return $ if err /= minBound- then Left err- else Right str--toxFriendGetPublicKey :: Tox a -> Word32 -> IO (Either ErrFriendGetPublicKey BS.ByteString)-toxFriendGetPublicKey = callFriendGetPublicKey tox_friend_get_public_key---data ErrFriendGetLastOnline- = ErrFriendGetLastOnlineOk- -- The function returned successfully.-- | ErrFriendGetLastOnlineFriendNotFound- -- No friend with the given number exists on the friend list.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Return a unix-time timestamp of the last time the friend associated with a given--- friend number was seen online. This function will return UINT64_MAX on error.------ @param friend_number The friend number you want to query.-foreign import ccall tox_friend_get_last_online :: Tox a -> Word32 -> CErr ErrFriendGetLastOnline -> IO Word64-callFriendGetLastOnline :: (Tox a -> Word32 -> CErr ErrFriendGetLastOnline -> IO Word64) ->- Tox a -> Word32 -> IO (Either ErrFriendGetLastOnline EpochTime)-callFriendGetLastOnline f tox fn = callErrFun (f tox fn >=> (return . CTime . fromIntegral))--toxFriendGetLastOnline :: Tox a -> Word32 -> IO (Either ErrFriendGetLastOnline EpochTime)-toxFriendGetLastOnline = callFriendGetLastOnline tox_friend_get_last_online----------------------------------------------------------------------------------------- :: Friend-specific state queries (can also be received through callbacks)----------------------------------------------------------------------------------------- | Common error codes for friend state query functions.-data ErrFriendQuery- = ErrFriendQueryOk- -- The function returned successfully.-- | ErrFriendQueryNull- -- The pointer parameter for storing the query result (name, message) was- -- NULL. Unlike the `_self_` variants of these functions, which have no effect- -- when a parameter is NULL, these functions return an error in that case.-- | ErrFriendQueryFriendNotFound- -- The friend_number did not designate a valid friend.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Return the length of the friend's name. If the friend number is invalid, the--- return value is unspecified.------ The return value is equal to the `length` argument received by the last--- `friend_name` callback.-foreign import ccall tox_friend_get_name_size :: Tox a -> Word32 -> CErr ErrFriendQuery -> IO CSize---- | Write the name of the friend designated by the given friend number to a byte--- array.------ Call tox_friend_get_name_size to determine the allocation size for the `name`--- parameter.------ The data written to `name` is equal to the data received by the last--- `friend_name` callback.------ @param name A valid memory region large enough to store the friend's name.------ @return true on success.-foreign import ccall tox_friend_get_name :: Tox a -> Word32 -> CString -> CErr ErrFriendQuery -> IO Bool--toxFriendGetName :: Tox a -> Word32 -> IO (Either ErrFriendQuery String)-toxFriendGetName tox fn = do- nameLenRes <- callErrFun $ tox_friend_get_name_size tox fn- case nameLenRes of- Left err -> return $ Left err- Right nameLen -> allocaArray (fromIntegral nameLen) $ \namePtr -> do- nameRes <- callErrFun $ tox_friend_get_name tox fn namePtr- case nameRes of- Left err -> return $ Left err- Right _ ->- Right <$> peekCStringLen (namePtr, fromIntegral nameLen)----- | @param friend_number The friend number of the friend whose name changed.--- @param name A byte array containing the same data as--- tox_friend_get_name would write to its `name` parameter.--- @param length A value equal to the return value of--- tox_friend_get_name_size.-type FriendNameCb a = Tox a -> Word32 -> String -> a -> IO a-type CFriendNameCb a = Tox a -> Word32 -> CString -> CSize -> UserData a -> IO ()-foreign import ccall "wrapper" wrapFriendNameCb :: CFriendNameCb a -> IO (FunPtr (CFriendNameCb a))--callFriendNameCb :: FriendNameCb a -> CFriendNameCb a-callFriendNameCb f tox fn nameStr nameLen udPtr = do- ud <- deRefStablePtr udPtr- name <- peekCStringLen (nameStr, fromIntegral nameLen)- modifyMVar_ ud $ f tox fn name--friendNameCb :: FriendNameCb a -> IO (FunPtr (CFriendNameCb a))-friendNameCb = wrapFriendNameCb . callFriendNameCb----- | Set the callback for the `friend_name` event. Pass 'nullPtr' to unset.------ This event is triggered when a friend changes their name.-foreign import ccall tox_callback_friend_name :: Tox a -> FunPtr (CFriendNameCb a) -> IO ()----- | @param friend_number The friend number of the friend whose status message--- changed.--- @param message A byte array containing the same data as--- tox_friend_get_status_message would write to its `status_message`--- parameter.--- @param length A value equal to the return value of--- tox_friend_get_status_message_size.-type FriendStatusMessageCb a = Tox a -> Word32 -> String -> a -> IO a-type CFriendStatusMessageCb a = Tox a -> Word32 -> CString -> CSize -> UserData a -> IO ()-foreign import ccall "wrapper" wrapFriendStatusMessageCb :: CFriendStatusMessageCb a -> IO (FunPtr (CFriendStatusMessageCb a))--callFriendStatusMessageCb :: FriendStatusMessageCb a -> CFriendStatusMessageCb a-callFriendStatusMessageCb f tox fn statusMessageStr statusMessageLen udPtr = do- ud <- deRefStablePtr udPtr- statusMessage <- peekCStringLen (statusMessageStr, fromIntegral statusMessageLen)- modifyMVar_ ud $ f tox fn statusMessage--friendStatusMessageCb :: FriendStatusMessageCb a -> IO (FunPtr (CFriendStatusMessageCb a))-friendStatusMessageCb = wrapFriendStatusMessageCb . callFriendStatusMessageCb----- | Return the length of the friend's status message. If the friend number is--- invalid, the return value is SIZE_MAX.-foreign import ccall tox_friend_get_status_message_size :: Tox a -> Word32 -> CErr ErrFriendQuery -> IO CSize---- | Write the status message of the friend designated by the given friend number to a byte--- array.------ Call tox_friend_get_status_message_size to determine the allocation size for the `status_name`--- parameter.------ The data written to `status_message` is equal to the data received by the last--- `friend_status_message` callback.------ @param status_message A valid memory region large enough to store the friend's status message.-foreign import ccall tox_friend_get_status_message :: Tox a -> Word32 -> CString -> CErr ErrFriendQuery -> IO Bool--toxFriendGetStatusMessage :: Tox a -> Word32 -> IO (Either ErrFriendQuery String)-toxFriendGetStatusMessage tox fn = do- statusMessageLenRes <- callErrFun $ tox_friend_get_status_message_size tox fn- case statusMessageLenRes of- Left err -> return $ Left err- Right statusMessageLen -> allocaArray (fromIntegral statusMessageLen) $ \statusMessagePtr -> do- statusMessageRes <- callErrFun $ tox_friend_get_status_message tox fn statusMessagePtr- case statusMessageRes of- Left err -> return $ Left err- Right _ ->- Right <$> peekCStringLen (statusMessagePtr, fromIntegral statusMessageLen)----- | Set the callback for the `friend_status_message` event. Pass 'nullPtr' to--- unset.------ This event is triggered when a friend changes their status message.-foreign import ccall tox_callback_friend_status_message :: Tox a -> FunPtr (CFriendStatusMessageCb a) -> IO ()----- | @param friend_number The friend number of the friend whose user status--- changed.--- @param status The new user status.-type FriendStatusCb a = Tox a -> Word32 -> UserStatus -> a -> IO a-type CFriendStatusCb a = Tox a -> Word32 -> CEnum UserStatus -> UserData a -> IO ()-foreign import ccall "wrapper" wrapFriendStatusCb :: CFriendStatusCb a -> IO (FunPtr (CFriendStatusCb a))--callFriendStatusCb :: FriendStatusCb a -> CFriendStatusCb a-callFriendStatusCb f tox fn status = deRefStablePtr >=> (`modifyMVar_` f tox fn (fromCEnum status))--friendStatusCb :: FriendStatusCb a -> IO (FunPtr (CFriendStatusCb a))-friendStatusCb = wrapFriendStatusCb . callFriendStatusCb----- | Set the callback for the `friend_status` event. Pass 'nullPtr' to unset.------ This event is triggered when a friend changes their user status.-foreign import ccall tox_callback_friend_status :: Tox a -> FunPtr (CFriendStatusCb a) -> IO ()----- | Check whether a friend is currently connected to this client.------ The result of this function is equal to the last value received by the--- `friend_connection_status` callback.------ @param friend_number The friend number for which to query the connection--- status.------ @return the friend's connection status as it was received through the--- `friend_connection_status` event.-foreign import ccall tox_friend_get_connection_status :: Tox a -> Word32 -> CErr ErrFriendQuery -> IO (CEnum Connection)--callFriendGetConnectionStatus :: (Tox a -> Word32 -> CErr ErrFriendQuery -> IO (CEnum Connection)) ->- Tox a -> Word32 -> IO (Either ErrFriendQuery Connection)-callFriendGetConnectionStatus f tox fn = callErrFun (f tox fn >=> (return . fromCEnum))--toxFriendGetConnectionStatus :: Tox a -> Word32 -> IO (Either ErrFriendQuery Connection)-toxFriendGetConnectionStatus = callFriendGetConnectionStatus tox_friend_get_connection_status----- | @param friend_number The friend number of the friend whose connection--- status changed.--- @param connection_status The result of calling--- tox_friend_get_connection_status on the passed friend_number.-type FriendConnectionStatusCb a = Tox a -> Word32 -> Connection -> a -> IO a-type CFriendConnectionStatusCb a = Tox a -> Word32 -> CEnum Connection -> UserData a -> IO ()-foreign import ccall "wrapper" wrapFriendConnectionStatusCb :: CFriendConnectionStatusCb a -> IO (FunPtr (CFriendConnectionStatusCb a))--callFriendConnectionStatusCb :: FriendConnectionStatusCb a -> CFriendConnectionStatusCb a-callFriendConnectionStatusCb f tox fn connectionStatus = deRefStablePtr >=> (`modifyMVar_` f tox fn (fromCEnum connectionStatus))--friendConnectionStatusCb :: FriendConnectionStatusCb a -> IO (FunPtr (CFriendConnectionStatusCb a))-friendConnectionStatusCb = wrapFriendConnectionStatusCb . callFriendConnectionStatusCb----- | Set the callback for the `friend_connection_status` event. Pass 'nullPtr'--- to unset.------ This event is triggered when a friend goes offline after having been online,--- or when a friend goes online.------ This callback is not called when adding friends. It is assumed that when--- adding friends, their connection status is initially offline.-foreign import ccall tox_callback_friend_connection_status :: Tox a -> FunPtr (CFriendConnectionStatusCb a) -> IO ()----- | Check whether a friend is currently typing a message.------ @param friend_number The friend number for which to query the typing status.------ @return true if the friend is typing.--- @return false if the friend is not typing, or the friend number was--- invalid. Inspect the error code to determine which case it is.-foreign import ccall tox_friend_get_typing :: Tox a -> Word32 -> CErr ErrFriendQuery -> IO Bool--callFriendGetTyping :: (Tox a -> Word32 -> CErr ErrFriendQuery -> IO Bool) ->- Tox a -> Word32 -> IO (Either ErrFriendQuery Bool)-callFriendGetTyping f tox fn = callErrFun $ f tox fn--toxFriendGetTyping :: Tox a -> Word32 -> IO (Either ErrFriendQuery Bool)-toxFriendGetTyping = callFriendGetTyping tox_friend_get_typing----- | @param friend_number The friend number of the friend who started or stopped--- typing.--- @param is_typing The result of calling tox_friend_get_typing on the passed--- friend_number.-type FriendTypingCb a = Tox a -> Word32 -> Bool -> a -> IO a-type CFriendTypingCb a = Tox a -> Word32 -> Bool -> UserData a -> IO ()-foreign import ccall "wrapper" wrapFriendTypingCb :: CFriendTypingCb a -> IO (FunPtr (CFriendTypingCb a))--callFriendTypingCb :: FriendTypingCb a -> CFriendTypingCb a-callFriendTypingCb f tox fn typing = deRefStablePtr >=> (`modifyMVar_` f tox fn typing)--friendTypingCb :: FriendTypingCb a -> IO (FunPtr (CFriendTypingCb a))-friendTypingCb = wrapFriendTypingCb . callFriendTypingCb---- | Set the callback for the `friend_typing` event. Pass 'nullPtr' to unset.------ This event is triggered when a friend starts or stops typing.-foreign import ccall tox_callback_friend_typing :: Tox a -> FunPtr (CFriendTypingCb a) -> IO ()----------------------------------------------------------------------------------------- :: Sending private messages---------------------------------------------------------------------------------------data ErrSetTyping- = ErrSetTypingOk- -- The function returned successfully.-- | ErrSetTypingFriendNotFound- -- The friend number did not designate a valid friend.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Set the client's typing status for a friend.------ The client is responsible for turning it on or off.------ @param friend_number The friend to which the client is typing a message.--- @param typing The typing status. True means the client is typing.------ @return true on success.-foreign import ccall tox_self_set_typing :: Tox a -> Word32 -> Bool -> CErr ErrSetTyping -> IO Bool-callSelfSetTyping :: (Tox a -> Word32 -> Bool -> CErr ErrSetTyping -> IO Bool) ->- Tox a -> Word32 -> Bool -> IO (Either ErrSetTyping Bool)-callSelfSetTyping f tox fn typing = callErrFun $ f tox fn typing--toxSelfSetTyping :: Tox a -> Word32 -> Bool -> IO (Either ErrSetTyping Bool)-toxSelfSetTyping = callSelfSetTyping tox_self_set_typing--data ErrFriendSendMessage- = ErrFriendSendMessageOk- -- The function returned successfully.-- | ErrFriendSendMessageNull- -- One of the arguments to the function was 'nullPtr' when it was not- -- expected.-- | ErrFriendSendMessageFriendNotFound- -- The friend number did not designate a valid friend.-- | ErrFriendSendMessageFriendNotConnected- -- This client is currently not connected to the friend.-- | ErrFriendSendMessageSendq- -- An allocation error occurred while increasing the send queue size.-- | ErrFriendSendMessageTooLong- -- Message length exceeded 'tox_max_message_length'.-- | ErrFriendSendMessageEmpty- -- Attempted to send a zero-length message.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Send a text chat message to an online friend.------ This function creates a chat message packet and pushes it into the send--- queue.------ The message length may not exceed 'tox_max_message_length'. Larger messages--- must be split by the client and sent as separate messages. Other clients can--- then reassemble the fragments. Messages may not be empty.------ The return value of this function is the message ID. If a read receipt is--- received, the triggered `friend_read_receipt` event will be passed this--- message ID.------ Message IDs are unique per friend. The first message ID is 0. Message IDs are--- incremented by 1 each time a message is sent. If UINT32_MAX messages were--- sent, the next message ID is 0.------ @param type Message type (normal, action, ...).--- @param friend_number The friend number of the friend to send the message to.--- @param message A non-'nullPtr' pointer to the first element of a byte array--- containing the message text.--- @param length Length of the message to be sent.-foreign import ccall tox_friend_send_message :: Tox a -> Word32 -> CEnum MessageType -> CString -> CSize -> CErr ErrFriendSendMessage -> IO Word32-callFriendSendMessage :: (Tox a -> Word32 -> CEnum MessageType -> CString -> CSize -> CErr ErrFriendSendMessage -> IO Word32) ->- Tox a -> Word32 -> MessageType -> String -> IO (Either ErrFriendSendMessage Word32)-callFriendSendMessage f tox fn messageType message =- withCStringLen message $ \(msgStr, msgLen) ->- callErrFun $ f tox fn (toCEnum messageType) msgStr (fromIntegral msgLen)--toxFriendSendMessage :: Tox a -> Word32 -> MessageType -> String -> IO (Either ErrFriendSendMessage Word32)-toxFriendSendMessage = callFriendSendMessage tox_friend_send_message----- | @param friend_number The friend number of the friend who received the--- message.--- @param message_id The message ID as returned from tox_friend_send_message--- corresponding to the message sent.-type FriendReadReceiptCb a = Tox a -> Word32 -> Word32 -> a -> IO a-type CFriendReadReceiptCb a = Tox a -> Word32 -> Word32 -> UserData a -> IO ()-foreign import ccall "wrapper" wrapFriendReadReceiptCb :: CFriendReadReceiptCb a -> IO (FunPtr (CFriendReadReceiptCb a))--callFriendReadReceiptCb :: FriendReadReceiptCb a -> CFriendReadReceiptCb a-callFriendReadReceiptCb f tox fn msgId = deRefStablePtr >=> (`modifyMVar_` f tox fn msgId)--friendReadReceiptCb :: FriendReadReceiptCb a -> IO (FunPtr (CFriendReadReceiptCb a))-friendReadReceiptCb = wrapFriendReadReceiptCb . callFriendReadReceiptCb----- | Set the callback for the `friend_read_receipt` event. Pass 'nullPtr' to--- unset.------ This event is triggered when the friend receives the message sent with--- tox_friend_send_message with the corresponding message ID.-foreign import ccall tox_callback_friend_read_receipt :: Tox a -> FunPtr (CFriendReadReceiptCb a) -> IO ()----------------------------------------------------------------------------------------- :: Receiving private messages and friend requests----------------------------------------------------------------------------------------- | @param public_key The Public Key of the user who sent the friend request.--- @param time_delta A delta in seconds between when the message was composed--- and when it is being transmitted. For messages that are sent immediately,--- it will be 0. If a message was written and couldn't be sent immediately--- (due to a connection failure, for example), the time_delta is an--- approximation of when it was composed.--- @param message The message they sent along with the request.--- @param length The size of the message byte array.-type FriendRequestCb a = Tox a -> BS.ByteString -> String -> a -> IO a-type CFriendRequestCb a = Tox a -> CString -> CString -> CSize -> UserData a -> IO ()-foreign import ccall "wrapper" wrapFriendRequestCb :: CFriendRequestCb a -> IO (FunPtr (CFriendRequestCb a))--callFriendRequestCb :: FriendRequestCb a -> CFriendRequestCb a-callFriendRequestCb f tox addrPtr nameStr nameLen udPtr = do- let addrLen = fromIntegral tox_address_size- ud <- deRefStablePtr udPtr- addr <- BS.packCStringLen (addrPtr, addrLen)- name <- peekCStringLen (nameStr, fromIntegral nameLen)- modifyMVar_ ud $ f tox addr name--friendRequestCb :: FriendRequestCb a -> IO (FunPtr (CFriendRequestCb a))-friendRequestCb = wrapFriendRequestCb . callFriendRequestCb----- | Set the callback for the `friend_request` event. Pass 'nullPtr' to unset.------ This event is triggered when a friend request is received.-foreign import ccall tox_callback_friend_request :: Tox a -> FunPtr (CFriendRequestCb a) -> IO ()---- | @param friend_number The friend number of the friend who sent the message.--- @param time_delta Time between composition and sending.--- @param message The message data they sent.--- @param length The size of the message byte array.------ @see friend_request for more information on time_delta.-type FriendMessageCb a = Tox a -> Word32 -> MessageType -> String -> a -> IO a-type CFriendMessageCb a = Tox a -> Word32 -> CEnum MessageType -> CString -> CSize -> UserData a -> IO ()-foreign import ccall "wrapper" wrapFriendMessageCb :: CFriendMessageCb a -> IO (FunPtr (CFriendMessageCb a))--callFriendMessageCb :: FriendMessageCb a -> CFriendMessageCb a-callFriendMessageCb f tox fn msgType msgStr msgLen udPtr = do- ud <- deRefStablePtr udPtr- msg <- peekCStringLen (msgStr, fromIntegral msgLen)- modifyMVar_ ud $ f tox fn (fromCEnum msgType) msg--friendMessageCb :: FriendMessageCb a -> IO (FunPtr (CFriendMessageCb a))-friendMessageCb = wrapFriendMessageCb . callFriendMessageCb----- | Set the callback for the `friend_message` event. Pass 'nullPtr' to unset.------ This event is triggered when a message from a friend is received.-foreign import ccall tox_callback_friend_message :: Tox a -> FunPtr (CFriendMessageCb a) -> IO ()----------------------------------------------------------------------------------------- :: File transmission: common between sending and receiving----------------------------------------------------------------------------------------- | Generates a cryptographic hash of the given data.------ This function may be used by clients for any purpose, but is provided--- primarily for validating cached avatars. This use is highly recommended to--- avoid unnecessary avatar updates.------ If hash is 'nullPtr' or data is 'nullPtr' while length is not 0 the function--- returns false, otherwise it returns true.------ This function is a wrapper to internal message-digest functions.------ @param hash A valid memory location the hash data. It must be at least--- 'tox_hash_length' bytes in size.--- @param data Data to be hashed or 'nullPtr'.--- @param length Size of the data array or 0.------ @return true if hash was not 'nullPtr'.-foreign import ccall tox_hash :: CString -> CString -> CSize -> IO Bool--toxHash :: BS.ByteString -> IO BS.ByteString-toxHash d =- let hashLen = fromIntegral tox_hash_length in- allocaArray hashLen $ \hashPtr ->- BS.useAsCStringLen d $ \(dataPtr, dataLen) -> do- _ <- tox_hash hashPtr dataPtr (fromIntegral dataLen)- BS.packCStringLen (dataPtr, fromIntegral dataLen)--data FileKind- = FileKindData- -- Arbitrary file data. Clients can choose to handle it based on the file- -- name or magic or any other way they choose.-- | FileKindAvatar- -- Avatar file_id. This consists of tox_hash(image). Avatar data. This- -- consists of the image data.- --- -- Avatars can be sent at any time the client wishes. Generally, a client- -- will send the avatar to a friend when that friend comes online, and to- -- all friends when the avatar changed. A client can save some traffic by- -- remembering which friend received the updated avatar already and only- -- send it if the friend has an out of date avatar.- --- -- Clients who receive avatar send requests can reject it (by sending- -- FileControlCancel before any other controls), or accept it (by sending- -- FileControlResume). The file_id of length 'tox_hash_length' bytes (same- -- length as 'tox_file_id_length') will contain the hash. A client can- -- compare this hash with a saved hash and send FileControlCancel to- -- terminate the avatar transfer if it matches.- --- -- When file_size is set to 0 in the transfer request it means that the- -- client has no avatar.- deriving (Eq, Ord, Enum, Bounded, Read, Show)---data FileControl- = FileControlResume- -- Sent by the receiving side to accept a file send request. Also sent after- -- a FileControlPause command to continue sending or receiving.-- | FileControlPause- -- Sent by clients to pause the file transfer. The initial state of a file- -- transfer is always paused on the receiving side and running on the- -- sending side. If both the sending and receiving side pause the transfer,- -- then both need to send FileControlResume for the transfer to resume.-- | FileControlCancel- -- Sent by the receiving side to reject a file send request before any other- -- commands are sent. Also sent by either side to terminate a file transfer.- deriving (Eq, Ord, Enum, Bounded, Read, Show)---data ErrFileControl- = ErrFileControlOk- -- The function returned successfully.-- | ErrFileControlFriendNotFound- -- The friend_number passed did not designate a valid friend.-- | ErrFileControlFriendNotConnected- -- This client is currently not connected to the friend.-- | ErrFileControlNotFound- -- No file transfer with the given file number was found for the given friend.-- | ErrFileControlNotPaused- -- A Resume control was sent, but the file transfer is running normally.-- | ErrFileControlDenied- -- A Resume control was sent, but the file transfer was paused by the other- -- party. Only the party that paused the transfer can resume it.-- | ErrFileControlAlreadyPaused- -- A Pause control was sent, but the file transfer was already paused.-- | ErrFileControlSendq- -- Packet queue is full.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Sends a file control command to a friend for a given file transfer.------ @param friend_number The friend number of the friend the file is being--- transferred to or received from.--- @param file_number The friend-specific identifier for the file transfer.--- @param control The control command to send.------ @return true on success.-foreign import ccall tox_file_control :: Tox a -> Word32 -> Word32 -> CEnum FileControl -> CErr ErrFileControl -> IO Bool--callFileControl :: (Tox a -> Word32 -> Word32 -> CEnum FileControl -> CErr ErrFileControl -> IO Bool) ->- Tox a -> Word32 -> Word32 -> FileControl -> IO (Either ErrFileControl Bool)-callFileControl f tox fn fileNum control = callErrFun $ f tox fn fileNum (toCEnum control)--toxFileControl :: Tox a -> Word32 -> Word32 -> FileControl -> IO (Either ErrFileControl Bool)-toxFileControl = callFileControl tox_file_control---- | When receiving FileControlCancel, the client should release the--- resources associated with the file number and consider the transfer failed.------ @param friend_number The friend number of the friend who is sending the file.--- @param file_number The friend-specific file number the data received is--- associated with.--- @param control The file control command received.-type FileRecvControlCb a = Tox a -> Word32 -> Word32 -> FileControl -> a -> IO a-type CFileRecvControlCb a = Tox a -> Word32 -> Word32 -> CEnum FileControl -> UserData a -> IO ()-foreign import ccall "wrapper" wrapFileRecvControlCb :: CFileRecvControlCb a -> IO (FunPtr (CFileRecvControlCb a))--callFileRecvControlCb :: FileRecvControlCb a -> CFileRecvControlCb a-callFileRecvControlCb f tox fn fileNum ctrlCmd = deRefStablePtr >=> (`modifyMVar_` f tox fn fileNum (fromCEnum ctrlCmd))--fileRecvControlCb :: FileRecvControlCb a -> IO (FunPtr (CFileRecvControlCb a))-fileRecvControlCb = wrapFileRecvControlCb . callFileRecvControlCb----- | Set the callback for the `file_recv_control` event. Pass 'nullPtr' to--- unset.------ This event is triggered when a file control command is received from a--- friend.-foreign import ccall tox_callback_file_recv_control :: Tox a -> FunPtr (CFileRecvControlCb a) -> IO ()--data ErrFileSeek- = ErrFileSeekOk- -- The function returned successfully.-- | ErrFileSeekFriendNotFound- -- The friend_number passed did not designate a valid friend.-- | ErrFileSeekFriendNotConnected- -- This client is currently not connected to the friend.-- | ErrFileSeekNotFound- -- No file transfer with the given file number was found for the given friend.-- | ErrFileSeekDenied- -- File was not in a state where it could be seeked.-- | ErrFileSeekInvalidPosition- -- Seek position was invalid-- | ErrFileSeekSendq- -- Packet queue is full.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Sends a file seek control command to a friend for a given file transfer.------ This function can only be called to resume a file transfer right before--- FileControlResume is sent.------ @param friend_number The friend number of the friend the file is being--- received from.--- @param file_number The friend-specific identifier for the file transfer.--- @param position The position that the file should be seeked to.-foreign import ccall tox_file_seek :: Tox a -> Word32 -> Word32 -> Word64 -> CErr ErrFileSeek -> IO Bool--callFileSeek :: (Tox a -> Word32 -> Word32 -> Word64 -> CErr ErrFileSeek -> IO Bool) ->- Tox a -> Word32 -> Word32 -> Word64 -> IO (Either ErrFileSeek Bool)-callFileSeek f tox fn fileNum pos = callErrFun $ f tox fn fileNum pos--toxFileSeek :: Tox a -> Word32 -> Word32 -> Word64 -> IO (Either ErrFileSeek Bool)-toxFileSeek = callFileSeek tox_file_seek---data ErrFileGet- = ErrFileGetOk- -- The function returned successfully.-- | ErrFileGetNull- -- One of the arguments to the function was 'nullPtr' when it was not- -- expected.-- | ErrFileGetFriendNotFound- -- The friend_number passed did not designate a valid friend.-- | ErrFileGetNotFound- -- No file transfer with the given file number was found for the given friend.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Copy the file id associated to the file transfer to a byte array.------ @param friend_number The friend number of the friend the file is being--- transferred to or received from.--- @param file_number The friend-specific identifier for the file transfer.--- @param file_id A memory region of at least 'tox_file_id_length' bytes. If--- this parameter is 'nullPtr', this function has no effect.------ @return true on success.-foreign import ccall tox_file_get_file_id :: Tox a -> Word32 -> Word32 -> CString -> CErr ErrFileGet -> IO Bool--callFileGetFileId :: (Tox a -> Word32 -> Word32 -> CString -> CErr ErrFileGet -> IO Bool) ->- Tox a -> Word32 -> Word32 -> IO (Either ErrFileGet BS.ByteString)-callFileGetFileId f tox fn fileNum =- let fileIdLen = fromIntegral tox_file_id_length in- alloca $ \errPtr ->- allocaArray fileIdLen $ \fileIdPtr -> do- _ <- f tox fn fileNum fileIdPtr errPtr- err <- toEnum . fromIntegral . unCEnum <$> peek errPtr- fileId <- BS.packCStringLen (fileIdPtr, fileIdLen)- return $ if err /= minBound- then Left err- else Right fileId--toxFileGetFileId :: Tox a -> Word32 -> Word32 -> IO (Either ErrFileGet BS.ByteString)-toxFileGetFileId = callFileGetFileId tox_file_get_file_id----------------------------------------------------------------------------------------- :: File transmission: sending---------------------------------------------------------------------------------------data ErrFileSend- = ErrFileSendOk- -- The function returned successfully.-- | ErrFileSendNull- -- One of the arguments to the function was 'nullPtr' when it was not- -- expected.-- | ErrFileSendFriendNotFound- -- The friend_number passed did not designate a valid friend.-- | ErrFileSendFriendNotConnected- -- This client is currently not connected to the friend.-- | ErrFileSendNameTooLong- -- Filename length exceeded 'tox_max_filename_length' bytes.-- | ErrFileSendTooMany- -- Too many ongoing transfers. The maximum number of concurrent file transfers- -- is 256 per friend per direction (sending and receiving).- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Send a file transmission request.------ Maximum filename length is 'tox_max_filename_length' bytes. The filename--- should generally just be a file name, not a path with directory names.------ If a non-UINT64_MAX file size is provided, it can be used by both sides to--- determine the sending progress. File size can be set to UINT64_MAX for--- streaming data of unknown size.------ File transmission occurs in chunks, which are requested through the--- `file_chunk_request` event.------ When a friend goes offline, all file transfers associated with the friend are--- purged from core.------ If the file contents change during a transfer, the behaviour is unspecified--- in general. What will actually happen depends on the mode in which the file--- was modified and how the client determines the file size.------ - If the file size was increased--- - and sending mode was streaming (file_size = UINT64_MAX), the behaviour--- will be as expected.--- - and sending mode was file (file_size != UINT64_MAX), the--- file_chunk_request callback will receive length = 0 when Core thinks--- the file transfer has finished. If the client remembers the file size as--- it was when sending the request, it will terminate the transfer normally.--- If the client re-reads the size, it will think the friend cancelled the--- transfer.--- - If the file size was decreased--- - and sending mode was streaming, the behaviour is as expected.--- - and sending mode was file, the callback will return 0 at the new--- (earlier) end-of-file, signalling to the friend that the transfer was--- cancelled.--- - If the file contents were modified--- - at a position before the current read, the two files (local and remote)--- will differ after the transfer terminates.--- - at a position after the current read, the file transfer will succeed as--- expected.--- - In either case, both sides will regard the transfer as complete and--- successful.------ @param friend_number The friend number of the friend the file send request--- should be sent to.--- @param kind The meaning of the file to be sent.--- @param file_size Size in bytes of the file the client wants to send,--- UINT64_MAX if unknown or streaming.--- @param file_id A file identifier of length 'tox_file_id_length' that can be--- used to uniquely identify file transfers across core restarts. If--- 'nullPtr', a random one will be generated by core. It can then be obtained--- by using tox_file_get_file_id().--- @param filename Name of the file. Does not need to be the actual name. This--- name will be sent along with the file send request.--- @param filename_length Size in bytes of the filename.------ @return A file number used as an identifier in subsequent callbacks. This--- number is per friend. File numbers are reused after a transfer terminates.--- On failure, this function returns UINT32_MAX. Any pattern in file numbers--- should not be relied on.-foreign import ccall tox_file_send :: Tox a -> Word32 -> CEnum FileKind -> Word64 -> CString -> CString -> CSize -> CErr ErrFileSend -> IO Word32-callFileSend :: (Tox a -> Word32 -> CEnum FileKind -> Word64 -> CString -> CString -> CSize -> CErr ErrFileSend -> IO Word32) ->- Tox a -> Word32 -> FileKind -> Word64 -> String -> IO (Either ErrFileSend Word32)-callFileSend f tox fn fileKind fileSize fileName =- withCStringLen fileName $ \(fileNamePtr, fileNameLen) ->- callErrFun $ f tox fn (toCEnum fileKind) fileSize nullPtr fileNamePtr (fromIntegral fileNameLen)--toxFileSend :: Tox a -> Word32 -> FileKind -> Word64 -> String -> IO (Either ErrFileSend Word32)-toxFileSend = callFileSend tox_file_send--data ErrFileSendChunk- = ErrFileSendChunkOk- -- The function returned successfully.-- | ErrFileSendChunkNull- -- The length parameter was non-zero, but data was 'nullPtr'.-- | ErrFileSendChunkFriendNotFound- -- The friend_number passed did not designate a valid friend.-- | ErrFileSendChunkFriendNotConnected- -- This client is currently not connected to the friend.-- | ErrFileSendChunkNotFound- -- No file transfer with the given file number was found for the given friend.-- | ErrFileSendChunkNotTransferring- -- File transfer was found but isn't in a transferring state: (paused, done,- -- broken, etc...) (happens only when not called from the request chunk- -- callback).-- | ErrFileSendChunkInvalidLength- -- Attempted to send more or less data than requested. The requested data- -- size is adjusted according to maximum transmission unit and the expected- -- end of the file. Trying to send less or more than requested will return- -- this error.-- | ErrFileSendChunkSendq- -- Packet queue is full.-- | ErrFileSendChunkWrongPosition- -- Position parameter was wrong.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Send a chunk of file data to a friend.------ This function is called in response to the `file_chunk_request` callback.--- The length parameter should be equal to the one received though the--- callback. If it is zero, the transfer is assumed complete. For files with--- known size, Core will know that the transfer is complete after the last byte--- has been received, so it is not necessary (though not harmful) to send a--- zero-length chunk to terminate. For streams, core will know that the--- transfer is finished if a chunk with length less than the length requested--- in the callback is sent.------ @param friend_number The friend number of the receiving friend for this file.--- @param file_number The file transfer identifier returned by tox_file_send.--- @param position The file or stream position from which to continue reading.--- @return true on success.-foreign import ccall tox_file_send_chunk :: Tox a -> Word32 -> Word32 -> Word64 -> CString -> CSize -> CErr ErrFileSendChunk -> IO Bool-callFileSendChunk :: (Tox a -> Word32 -> Word32 -> Word64 -> CString -> CSize -> CErr ErrFileSendChunk -> IO Bool) ->- Tox a -> Word32 -> Word32 -> Word64 -> BS.ByteString -> IO (Either ErrFileSendChunk Bool)-callFileSendChunk f tox fn fileNum pos d =- BS.useAsCStringLen d $ \(dataPtr, dataLen) ->- callErrFun $ f tox fn fileNum pos dataPtr (fromIntegral dataLen)--toxFileSendChunk :: Tox a -> Word32 -> Word32 -> Word64 -> BS.ByteString -> IO (Either ErrFileSendChunk Bool)-toxFileSendChunk = callFileSendChunk tox_file_send_chunk---- | If the length parameter is 0, the file transfer is finished, and the--- client's resources associated with the file number should be released. After--- a call with zero length, the file number can be reused for future file--- transfers.------ If the requested position is not equal to the client's idea of the current--- file or stream position, it will need to seek. In case of read-once streams,--- the client should keep the last read chunk so that a seek back can be--- supported. A seek-back only ever needs to read from the last requested--- chunk. This happens when a chunk was requested, but the send failed. A--- seek-back request can occur an arbitrary number of times for any given--- chunk.------ In response to receiving this callback, the client should call the function--- `tox_file_send_chunk` with the requested chunk. If the number of bytes sent--- through that function is zero, the file transfer is assumed complete. A--- client must send the full length of data requested with this callback.------ @param friend_number The friend number of the receiving friend for this file.--- @param file_number The file transfer identifier returned by tox_file_send.--- @param position The file or stream position from which to continue reading.--- @param length The number of bytes requested for the current chunk.-type FileChunkRequestCb a = Tox a -> Word32 -> Word32 -> Word64 -> CSize -> a -> IO a-type CFileChunkRequestCb a = Tox a -> Word32 -> Word32 -> Word64 -> CSize -> UserData a -> IO ()-foreign import ccall "wrapper" wrapFileChunkRequestCb :: CFileChunkRequestCb a -> IO (FunPtr (CFileChunkRequestCb a))--callFileChunkRequestCb :: FileChunkRequestCb a -> CFileChunkRequestCb a-callFileChunkRequestCb f tox fn fileNum pos len = deRefStablePtr >=> (`modifyMVar_` f tox fn fileNum pos len)--fileChunkRequestCb :: FileChunkRequestCb a -> IO (FunPtr (CFileChunkRequestCb a))-fileChunkRequestCb = wrapFileChunkRequestCb . callFileChunkRequestCb---- | Set the callback for the `file_chunk_request` event. Pass 'nullPtr' to--- unset.------ This event is triggered when Core is ready to send more file data.-foreign import ccall tox_callback_file_chunk_request :: Tox a -> FunPtr (CFileChunkRequestCb a) -> IO ()----------------------------------------------------------------------------------------- :: File transmission: receiving----------------------------------------------------------------------------------------- | The client should acquire resources to be associated with the file--- transfer. Incoming file transfers start in the Paused state. After this--- callback returns, a transfer can be rejected by sending a FileControlCancel--- control command before any other control commands. It can be accepted by--- sending FileControlResume.------ @param friend_number The friend number of the friend who is sending the file--- transfer request.--- @param file_number The friend-specific file number the data received is--- associated with.--- @param kind The meaning of the file to be sent.--- @param file_size Size in bytes of the file the client wants to send,--- UINT64_MAX if unknown or streaming.--- @param filename Name of the file. Does not need to be the actual name. This--- name will be sent along with the file send request.--- @param filename_length Size in bytes of the filename.-type FileRecvCb a = Tox a -> Word32 -> Word32 -> FileKind -> Word64 -> String -> a -> IO a-type CFileRecvCb a = Tox a -> Word32 -> Word32 -> CEnum FileKind -> Word64 -> CString -> CSize -> UserData a -> IO ()-foreign import ccall "wrapper" wrapFileRecvCb :: CFileRecvCb a -> IO (FunPtr (CFileRecvCb a))--callFileRecvCb :: FileRecvCb a -> CFileRecvCb a-callFileRecvCb f tox fn fileNum fileKind fileSize fileNameStr fileNameLen udPtr = do- ud <- deRefStablePtr udPtr- fileName <- peekCStringLen (fileNameStr, fromIntegral fileNameLen)- modifyMVar_ ud $ f tox fn fileNum (fromCEnum fileKind) fileSize fileName--fileRecvCb :: FileRecvCb a -> IO (FunPtr (CFileRecvCb a))-fileRecvCb = wrapFileRecvCb . callFileRecvCb----- | Set the callback for the `file_recv` event. Pass 'nullPtr' to unset.------ This event is triggered when a file transfer request is received.-foreign import ccall tox_callback_file_recv :: Tox a -> FunPtr (CFileRecvCb a) -> IO ()---- | When length is 0, the transfer is finished and the client should release--- the resources it acquired for the transfer. After a call with length = 0,--- the file number can be reused for new file transfers.------ If position is equal to file_size (received in the file_receive callback)--- when the transfer finishes, the file was received completely. Otherwise, if--- file_size was UINT64_MAX, streaming ended successfully when length is 0.------ @param friend_number The friend number of the friend who is sending the file.--- @param file_number The friend-specific file number the data received is--- associated with.--- @param position The file position of the first byte in data.--- @param data A byte array containing the received chunk.--- @param length The length of the received chunk.-type FileRecvChunkCb a = Tox a -> Word32 -> Word32 -> Word64 -> BS.ByteString -> a -> IO a-type CFileRecvChunkCb a = Tox a -> Word32 -> Word32 -> Word64 -> CString -> CSize -> UserData a -> IO ()-foreign import ccall "wrapper" wrapFileRecvChunkCb :: CFileRecvChunkCb a -> IO (FunPtr (CFileRecvChunkCb a))--callFileRecvChunkCb :: FileRecvChunkCb a -> CFileRecvChunkCb a-callFileRecvChunkCb f tox fn fileNum pos dataPtr dataLen udPtr = do- ud <- deRefStablePtr udPtr- d <- BS.packCStringLen (dataPtr, fromIntegral dataLen)- modifyMVar_ ud $ f tox fn fileNum pos d--fileRecvChunkCb :: FileRecvChunkCb a -> IO (FunPtr (CFileRecvChunkCb a))-fileRecvChunkCb = wrapFileRecvChunkCb . callFileRecvChunkCb----- | Set the callback for the `file_recv_chunk` event. Pass 'nullPtr' to unset.------ This event is first triggered when a file transfer request is received, and--- subsequently when a chunk of file data for an accepted request was received.-foreign import ccall tox_callback_file_recv_chunk :: Tox a -> FunPtr (CFileRecvChunkCb a) -> IO ()----------------------------------------------------------------------------------------- :: Conference management----------------------------------------------------------------------------------------- | Conference types for the conference_invite event.-data ConferenceType- = ConferenceTypeText- -- Text-only conferences that must be accepted with the tox_conference_join function.-- | ConferenceTypeAv- -- Video conference. The function to accept these is in toxav.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | The invitation will remain valid until the inviting friend goes offline--- or exits the conference.------ @param friend_number The friend who invited us.--- @param type The conference type (text only or audio/video).--- @param cookie A piece of data of variable length required to join the--- conference.--- @param length The length of the cookie.-type ConferenceInviteCb a = Tox a -> Word32 -> ConferenceType -> BS.ByteString -> a -> IO a-type CConferenceInviteCb a = Tox a -> Word32 -> CEnum ConferenceType -> CString -> CSize -> UserData a -> IO ()-foreign import ccall "wrapper" wrapConferenceInviteCb :: CConferenceInviteCb a -> IO (FunPtr (CConferenceInviteCb a))--callConferenceInviteCb :: ConferenceInviteCb a -> CConferenceInviteCb a-callConferenceInviteCb f tox fn confType cookiePtr cookieLen udPtr = do- ud <- deRefStablePtr udPtr- cookie <- BS.packCStringLen (cookiePtr, fromIntegral cookieLen)- modifyMVar_ ud $ f tox fn (fromCEnum confType) cookie--conferenceInviteCb :: ConferenceInviteCb a -> IO (FunPtr (CConferenceInviteCb a))-conferenceInviteCb = wrapConferenceInviteCb . callConferenceInviteCb----- | Set the callback for the `conference_invite` event. Pass NULL to unset.------ This event is triggered when the client is invited to join a conference.-foreign import ccall tox_callback_conference_invite :: Tox a -> FunPtr (CConferenceInviteCb a) -> IO ()----- | @param conference_number The conference number of the conference the message is intended for.--- @param peer_number The ID of the peer who sent the message.--- @param type The type of message (normal, action, ...).--- @param message The message data.--- @param length The length of the message.-type ConferenceMessageCb a = Tox a -> Word32 -> Word32 -> MessageType -> String -> a -> IO a-type CConferenceMessageCb a = Tox a -> Word32 -> Word32 -> CEnum MessageType -> CString -> CSize -> UserData a -> IO ()-foreign import ccall "wrapper" wrapConferenceMessageCb :: CConferenceMessageCb a -> IO (FunPtr (CConferenceMessageCb a))--callConferenceMessageCb :: ConferenceMessageCb a -> CConferenceMessageCb a-callConferenceMessageCb f tox gn pn msgType msgStr msgLen udPtr = do- ud <- deRefStablePtr udPtr- msg <- peekCStringLen (msgStr, fromIntegral msgLen)- modifyMVar_ ud $ f tox gn pn (fromCEnum msgType) msg--conferenceMessageCb :: ConferenceMessageCb a -> IO (FunPtr (CConferenceMessageCb a))-conferenceMessageCb = wrapConferenceMessageCb . callConferenceMessageCb----- | Set the callback for the `conference_message` event. Pass NULL to unset.------ This event is triggered when the client receives a conference message.-foreign import ccall tox_callback_conference_message :: Tox a -> FunPtr (CConferenceMessageCb a) -> IO ()----- | @param conference_number The conference number of the conference the title change is intended for.--- @param peer_number The ID of the peer who changed the title.--- @param title The title data.--- @param length The title length.-type ConferenceTitleCb a = Tox a -> Word32 -> Word32 -> String -> a -> IO a-type CConferenceTitleCb a = Tox a -> Word32 -> Word32 -> CString -> CSize -> UserData a -> IO ()-foreign import ccall "wrapper" wrapConferenceTitleCb :: CConferenceTitleCb a -> IO (FunPtr (CConferenceTitleCb a))--callConferenceTitleCb :: ConferenceTitleCb a -> CConferenceTitleCb a-callConferenceTitleCb f tox gn pn titleStr titleLen udPtr = do- ud <- deRefStablePtr udPtr- title <- peekCStringLen (titleStr, fromIntegral titleLen)- modifyMVar_ ud $ f tox gn pn title--conferenceTitleCb :: ConferenceTitleCb a -> IO (FunPtr (CConferenceTitleCb a))-conferenceTitleCb = wrapConferenceTitleCb . callConferenceTitleCb----- | Set the callback for the `conference_title` event. Pass NULL to unset.------ This event is triggered when a peer changes the conference title.------ If peer_number == UINT32_MAX, then author is unknown (e.g. initial joining the conference).-foreign import ccall tox_callback_conference_title :: Tox a -> FunPtr (CConferenceTitleCb a) -> IO ()----- | @param conference_number The conference number of the conference the peer is in.--- @param peer_number The ID of the peer who changed their name.--- @param name The new name of the peer.-type ConferencePeerNameCb a = Tox a -> Word32 -> Word32 -> String -> a -> IO a-type CConferencePeerNameCb a = Tox a -> Word32 -> Word32 -> CString -> CSize -> UserData a -> IO ()-foreign import ccall "wrapper" wrapConferencePeerNameCb :: CConferencePeerNameCb a -> IO (FunPtr (CConferencePeerNameCb a))--callConferencePeerNameCb :: ConferencePeerNameCb a -> CConferencePeerNameCb a-callConferencePeerNameCb f tox gn pn nameStr nameLen udPtr = do- ud <- deRefStablePtr udPtr- name <- peekCStringLen (nameStr, fromIntegral nameLen)- modifyMVar_ ud $ f tox gn pn name--conferencePeerNameCb :: ConferencePeerNameCb a -> IO (FunPtr (CConferencePeerNameCb a))-conferencePeerNameCb = wrapConferencePeerNameCb . callConferencePeerNameCb----- | Set the callback for the `conference_peer_name` event. Pass NULL to unset.------ This event is triggered when the peer changes their nickname.-foreign import ccall tox_callback_conference_peer_name :: Tox a -> FunPtr (CConferencePeerNameCb a) -> IO ()----- | @param conference_number The conference number of the conference for which the peer list changed.-type ConferencePeerListChangedCb a = Tox a -> Word32 -> a -> IO a-type CConferencePeerListChangedCb a = Tox a -> Word32 -> UserData a -> IO ()-foreign import ccall "wrapper" wrapConferencePeerListChangedCb :: CConferencePeerListChangedCb a -> IO (FunPtr (CConferencePeerListChangedCb a))--callConferencePeerListChangedCb :: ConferencePeerListChangedCb a -> CConferencePeerListChangedCb a-callConferencePeerListChangedCb f tox gn = deRefStablePtr >=> (`modifyMVar_` f tox gn)--conferencePeerListChangedCb :: ConferencePeerListChangedCb a -> IO (FunPtr (CConferencePeerListChangedCb a))-conferencePeerListChangedCb = wrapConferencePeerListChangedCb . callConferencePeerListChangedCb----- | Set the callback for the `conference_peer_list_changed` event. Pass NULL to unset.------ This event is triggered when the peer list changes (peer join, peer exit).-foreign import ccall tox_callback_conference_peer_list_changed :: Tox a -> FunPtr (CConferencePeerListChangedCb a) -> IO ()---data ErrConferenceNew- = ErrConferenceNewOk- -- The function returned successfully.-- | ErrConferenceNewInit- -- The conference instance failed to initialize.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Creates a new conference.------ This function creates a new text conference.------ @return conference number on success, or UINT32_MAX on failure.-foreign import ccall tox_conference_new :: Tox a -> CErr ErrConferenceNew -> IO Word32-callConferenceNew :: (Tox a -> CErr ErrConferenceNew -> IO Word32) ->- Tox a -> IO (Either ErrConferenceNew Word32)-callConferenceNew f tox = callErrFun $ f tox--toxConferenceNew :: Tox a -> IO (Either ErrConferenceNew Word32)-toxConferenceNew = callConferenceNew tox_conference_new---data ErrConferenceDelete- = ErrConferenceDeleteOk- -- The function returned successfully.-- | ErrConferenceDeleteConferenceNotFound- -- The conference number passed did not designate a valid conference.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | This function deletes a conference.------ @param conference_number The conference number of the conference to be deleted.------ @return true on success.-foreign import ccall tox_conference_delete :: Tox a -> Word32 -> CErr ErrConferenceDelete -> IO Bool-callConferenceDelete :: (Tox a -> Word32 -> CErr ErrConferenceDelete -> IO Bool) ->- Tox a -> Word32 -> IO (Either ErrConferenceDelete Bool)-callConferenceDelete f tox gn = callErrFun $ f tox gn--toxConferenceDelete :: Tox a -> Word32 -> IO (Either ErrConferenceDelete Bool)-toxConferenceDelete = callConferenceDelete tox_conference_delete----- | Error codes for peer info queries.-data ErrConferencePeerQuery- = ErrConferencePeerQueryOk- -- The function returned successfully.-- | ErrConferencePeerQueryConferenceNotFound- -- The conference number passed did not designate a valid conference.-- | ErrConferencePeerQueryPeerNotFound- -- The peer number passed did not designate a valid peer.-- | ErrConferencePeerQueryNoConnection- -- The client is not connected to the conference.- deriving (Eq, Ord, Enum, Bounded, Read, Show)------ | Return the number of peers in the conference. Return value is unspecified on failure.-foreign import ccall tox_conference_peer_count :: Tox a -> Word32 -> CErr ErrConferencePeerQuery -> IO Word32-callConferencePeerCount :: (Tox a -> Word32 -> CErr ErrConferencePeerQuery -> IO Word32) ->- Tox a -> Word32 -> IO (Either ErrConferencePeerQuery Word32)-callConferencePeerCount f tox gn = callErrFun $ f tox gn--toxConferencePeerCount :: Tox a -> Word32 -> IO (Either ErrConferencePeerQuery Word32)-toxConferencePeerCount = callConferencePeerCount tox_conference_peer_count----- | Return the length of the peer's name. Return value is unspecified on failure.-foreign import ccall tox_conference_peer_get_name_size :: Tox a -> Word32 -> Word32 -> CErr ErrConferencePeerQuery -> IO CSize----- | Copy the name of peer_number who is in conference_number to name.--- name must be at least TOX_MAX_NAME_LENGTH long.------ @return true on success.-foreign import ccall tox_conference_peer_get_name :: Tox a -> Word32 -> Word32 -> CString -> CErr ErrConferencePeerQuery -> IO Bool--toxConferencePeerGetName :: Tox a -> Word32 -> Word32 -> IO (Either ErrConferencePeerQuery String)-toxConferencePeerGetName tox gn pn = do- nameLenRes <- callErrFun $ tox_conference_peer_get_name_size tox gn pn- case nameLenRes of- Left err -> return $ Left err- Right nameLen -> allocaArray (fromIntegral nameLen) $ \namePtr -> do- nameRes <- callErrFun $ tox_conference_peer_get_name tox gn pn namePtr- case nameRes of- Left err -> return $ Left err- Right _ ->- Right <$> peekCStringLen (namePtr, fromIntegral nameLen)----- | Copy the public key of peer_number who is in conference_number to public_key.--- public_key must be TOX_PUBLIC_KEY_SIZE long.------ @return true on success.-foreign import ccall tox_conference_peer_get_public_key :: Tox a -> Word32 -> Word32 -> CString -> CErr ErrConferencePeerQuery -> IO Bool-callConferencePeerGetPublicKey :: (Tox a -> Word32 -> Word32 -> CString -> CErr ErrConferencePeerQuery -> IO Bool) ->- Tox a -> Word32 -> Word32 -> IO (Either ErrConferencePeerQuery BS.ByteString)-callConferencePeerGetPublicKey f tox gn pn =- let pkLen = fromIntegral tox_public_key_size in- alloca $ \errPtr ->- allocaArray pkLen $ \pkPtr -> do- _ <- f tox gn pn pkPtr errPtr- callGetPublicKey errPtr pkPtr pkLen--toxConferencePeerGetPublicKey :: Tox a -> Word32 -> Word32 -> IO (Either ErrConferencePeerQuery BS.ByteString)-toxConferencePeerGetPublicKey = callConferencePeerGetPublicKey tox_conference_peer_get_public_key----- | Return true if passed peer_number corresponds to our own.-foreign import ccall tox_conference_peer_number_is_ours :: Tox a -> Word32 -> Word32 -> CErr ErrConferencePeerQuery -> IO Bool-callConferencePeerNumberIsOurs :: (Tox a -> Word32 -> Word32 -> CErr ErrConferencePeerQuery -> IO Bool) ->- Tox a -> Word32 -> Word32 -> IO (Either ErrConferencePeerQuery Bool)-callConferencePeerNumberIsOurs f tox gn pn = callErrFun $ f tox gn pn--toxConferencePeerNumberIsOurs :: Tox a -> Word32 -> Word32 -> IO (Either ErrConferencePeerQuery Bool)-toxConferencePeerNumberIsOurs = callConferencePeerNumberIsOurs tox_conference_peer_number_is_ours---data ErrConferenceInvite- = ErrConferenceInviteOk- -- The function returned successfully.-- | ErrConferenceInviteConferenceNotFound- -- The conference number passed did not designate a valid conference.-- | ErrConferenceInviteFailSend- -- The invite packet failed to send.- deriving (Eq, Ord, Enum, Bounded, Read, Show)------ | Invites a friend to a conference.------ @param friend_number The friend number of the friend we want to invite.--- @param conference_number The conference number of the conference we want to invite the friend to.------ @return true on success.-foreign import ccall tox_conference_invite :: Tox a -> Word32 -> Word32 -> CErr ErrConferenceInvite -> IO Bool-callConferenceInvite :: (Tox a -> Word32 -> Word32 -> CErr ErrConferenceInvite -> IO Bool) ->- Tox a -> Word32 -> Word32 -> IO (Either ErrConferenceInvite Bool)-callConferenceInvite f tox fn gn = callErrFun $ f tox fn gn--toxConferenceInvite :: Tox a -> Word32 -> Word32 -> IO (Either ErrConferenceInvite Bool)-toxConferenceInvite = callConferenceInvite tox_conference_invite---data ErrConferenceJoin- = ErrConferenceJoinOk- -- The function returned successfully.-- | ErrConferenceJoinInvalidLength- -- The cookie passed has an invalid length.-- | ErrConferenceJoinWrongType- -- The conference is not the expected type. This indicates an invalid cookie.-- | ErrConferenceJoinFriendNotFound- -- The friend number passed does not designate a valid friend.-- | ErrConferenceJoinDuplicate- -- Client is already in this conference.-- | ErrConferenceJoinInitFail- -- Conference instance failed to initialize.-- | ErrConferenceJoinFailSend- -- The join packet failed to send.- deriving (Eq, Ord, Enum, Bounded, Read, Show)------ | Joins a conference that the client has been invited to.------ @param friend_number The friend number of the friend who sent the invite.--- @param cookie Received via the `conference_invite` event.--- @param length The size of cookie.------ @return conference number on success, UINT32_MAX on failure.-foreign import ccall tox_conference_join :: Tox a -> Word32 -> CString -> CSize -> CErr ErrConferenceJoin -> IO Word32-callConferenceJoin :: (Tox a -> Word32 -> CString -> CSize -> CErr ErrConferenceJoin -> IO Word32) ->- Tox a -> Word32 -> BS.ByteString -> IO (Either ErrConferenceJoin Word32)-callConferenceJoin f tox fn cookie =- BS.useAsCStringLen cookie $ \(cookiePtr, cookieLen) ->- callErrFun $ f tox fn cookiePtr (fromIntegral cookieLen)--toxConferenceJoin :: Tox a -> Word32 -> BS.ByteString -> IO (Either ErrConferenceJoin Word32)-toxConferenceJoin = callConferenceJoin tox_conference_join---data ErrConferenceSendMessage- = ErrConferenceSendMessageOk- -- The function returned successfully.-- | ErrConferenceSendMessageConferenceNotFound- -- The conference number passed did not designate a valid conference.-- | ErrConferenceSendMessageTooLong- -- The message is too long.-- | ErrConferenceSendMessageNoConnection- -- The client is not connected to the conference.-- | ErrConferenceSendMessageFailSend- -- The message packet failed to send.- deriving (Eq, Ord, Enum, Bounded, Read, Show)------ | Send a text chat message to the conference.------ This function creates a conference message packet and pushes it into the send--- queue.------ The message length may not exceed TOX_MAX_MESSAGE_LENGTH. Larger messages--- must be split by the client and sent as separate messages. Other clients can--- then reassemble the fragments.------ @param conference_number The conference number of the conference the message is intended for.--- @param type Message type (normal, action, ...).--- @param message A non-NULL pointer to the first element of a byte array--- containing the message text.--- @param length Length of the message to be sent.------ @return true on success.-foreign import ccall tox_conference_send_message :: Tox a -> Word32 -> CEnum MessageType -> CString -> CSize -> CErr ErrConferenceSendMessage -> IO Bool-callConferenceSendMessage :: (Tox a -> Word32 -> CEnum MessageType -> CString -> CSize -> CErr ErrConferenceSendMessage -> IO Bool) ->- Tox a -> Word32 -> MessageType -> String -> IO (Either ErrConferenceSendMessage Bool)-callConferenceSendMessage f tox gn messageType message =- withCStringLen message $ \(msgPtr, msgLen) ->- callErrFun $ f tox gn (toCEnum messageType) msgPtr (fromIntegral msgLen)--toxConferenceSendMessage :: Tox a -> Word32 -> MessageType -> String -> IO (Either ErrConferenceSendMessage Bool)-toxConferenceSendMessage = callConferenceSendMessage tox_conference_send_message---data ErrConferenceTitle- = ErrConferenceTitleOk- -- The function returned successfully.-- | ErrConferenceTitleConferenceNotFound- -- The conference number passed did not designate a valid conference.-- | ErrConferenceTitleInvalidLength- -- The title is too long or empty.-- | ErrConferenceTitleFailSend- -- The title packet failed to send.- deriving (Eq, Ord, Enum, Bounded, Read, Show)------ | Return the length of the conference title. Return value is unspecified on failure.------ The return value is equal to the `length` argument received by the last--- `conference_title` callback.-foreign import ccall tox_conference_get_title_size :: Tox a -> Word32 -> CErr ErrConferenceTitle -> IO CSize----- | Write the title designated by the given conference number to a byte array.------ Call tox_conference_get_title_size to determine the allocation size for the `title` parameter.------ The data written to `title` is equal to the data received by the last--- `conference_title` callback.------ @param title A valid memory region large enough to store the title.--- If this parameter is NULL, this function has no effect.------ @return true on success.-foreign import ccall tox_conference_get_title :: Tox a -> Word32 -> CString -> CErr ErrConferenceTitle -> IO Bool--toxConferenceGetTitle :: Tox a -> Word32 -> IO (Either ErrConferenceTitle String)-toxConferenceGetTitle tox gn = do- titleLenRes <- callErrFun $ tox_conference_get_title_size tox gn- case titleLenRes of- Left err -> return $ Left err- Right titleLen -> allocaArray (fromIntegral titleLen) $ \titlePtr -> do- titleRes <- callErrFun $ tox_conference_get_title tox gn titlePtr- case titleRes of- Left err -> return $ Left err- Right _ ->- Right <$> peekCStringLen (titlePtr, fromIntegral titleLen)---- | Set the conference title and broadcast it to the rest of the conference.------ Title length cannot be longer than TOX_MAX_NAME_LENGTH.------ @return true on success.-foreign import ccall tox_conference_set_title :: Tox a -> Word32 -> CString -> CSize -> CErr ErrConferenceTitle -> IO Bool-callConferenceSetTitle :: (Tox a -> Word32 -> CString -> CSize -> CErr ErrConferenceTitle -> IO Bool) ->- Tox a -> Word32 -> String -> IO (Either ErrConferenceTitle Bool)-callConferenceSetTitle f tox gn title =- withCStringLen title $ \(titlePtr, titleLen) ->- callErrFun $ f tox gn titlePtr (fromIntegral titleLen)--toxConferenceSetTitle :: Tox a -> Word32 -> String -> IO (Either ErrConferenceTitle Bool)-toxConferenceSetTitle = callConferenceSetTitle tox_conference_set_title----- | Return the number of conferences in the Tox instance.--- This should be used to determine how much memory to allocate for `tox_conference_get_chatlist`.-foreign import ccall tox_conference_get_chatlist_size :: Tox a -> IO CSize----- | Copy a list of valid conference IDs into the array chatlist. Determine how much space--- to allocate for the array with the `tox_conference_get_chatlist_size` function.-foreign import ccall tox_conference_get_chatlist :: Tox a -> Ptr Word32 -> IO ()--toxConferenceGetChatlist :: Tox a -> IO [Word32]-toxConferenceGetChatlist tox = do- chatListSize <- tox_conference_get_chatlist_size tox- allocaArray (fromIntegral chatListSize) $ \chatListPtr -> do- tox_conference_get_chatlist tox chatListPtr- peekArray (fromIntegral chatListSize) chatListPtr---- | Returns the type of conference (TOX_CONFERENCE_TYPE) that conference_number is. Return value is--- unspecified on failure.-data ErrConferenceGetType- = ErrConferenceGetTypeOk- -- The function returned successfully.-- | ErrConferenceGetTypeConferenceNotFound- -- The conference number passed did not designate a valid conference.- deriving (Eq, Ord, Enum, Bounded, Read, Show)--foreign import ccall tox_conference_get_type :: Tox a -> Word32 -> CErr ErrConferenceGetType -> IO (CEnum ConferenceType)-callConferenceGetType :: (Tox a -> Word32 -> CErr ErrConferenceGetType -> IO (CEnum ConferenceType)) ->- Tox a -> Word32 -> IO (Either ErrConferenceGetType ConferenceType)-callConferenceGetType f tox gn = callErrFun (f tox gn >=> (return . fromCEnum))--toxConferenceGetType :: Tox a -> Word32 -> IO (Either ErrConferenceGetType ConferenceType)-toxConferenceGetType = callConferenceGetType tox_conference_get_type----------------------------------------------------------------------------------------- :: Low-level custom packet sending and receiving---------------------------------------------------------------------------------------data ErrFriendCustomPacket- = ErrFriendCustomPacketOk- -- The function returned successfully.-- | ErrFriendCustomPacketNull- -- One of the arguments to the function was 'nullPtr' when it was not- -- expected.-- | ErrFriendCustomPacketFriendNotFound- -- The friend number did not designate a valid friend.-- | ErrFriendCustomPacketFriendNotConnected- -- This client is currently not connected to the friend.-- | ErrFriendCustomPacketInvalid- -- The first byte of data was not in the specified range for the packet- -- type. This range is 200-254 for lossy, and 160-191 for lossless packets.-- | ErrFriendCustomPacketEmpty- -- Attempted to send an empty packet.-- | ErrFriendCustomPacketTooLong- -- Packet data length exceeded 'tox_max_custom_packet_size'.-- | ErrFriendCustomPacketSendq- -- Packet queue is full.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Send a custom lossy packet to a friend.------ The first byte of data must be in the range 200-254. Maximum length of a--- custom packet is 'tox_max_custom_packet_size'.------ Lossy packets behave like UDP packets, meaning they might never reach the--- other side or might arrive more than once (if someone is messing with the--- connection) or might arrive in the wrong order.------ Unless latency is an issue, it is recommended that you use lossless custom--- packets instead.------ @param friend_number The friend number of the friend this lossy packet--- should be sent to.--- @param data A byte array containing the packet data.--- @param length The length of the packet data byte array.------ @return true on success.-foreign import ccall tox_friend_send_lossy_packet :: Tox a -> Word32 -> CString -> CSize -> CErr ErrFriendCustomPacket -> IO Bool-callFriendLossyPacket :: (Tox a -> Word32 -> CString -> CSize -> CErr ErrFriendCustomPacket -> IO Bool) ->- Tox a -> Word32 -> BS.ByteString -> IO (Either ErrFriendCustomPacket Bool)-callFriendLossyPacket f tox fn d =- BS.useAsCStringLen d $ \(dataPtr, dataLen) ->- callErrFun $ f tox fn dataPtr (fromIntegral dataLen)--toxFriendLossyPacket :: Tox a -> Word32 -> BS.ByteString -> IO (Either ErrFriendCustomPacket Bool)-toxFriendLossyPacket = callFriendLossyPacket tox_friend_send_lossy_packet---- | Send a custom lossless packet to a friend.------ The first byte of data must be in the range 160-191. Maximum length of a--- custom packet is 'tox_max_custom_packet_size'.------ Lossless packet behaviour is comparable to TCP (reliability, arrive in order)--- but with packets instead of a stream.------ @param friend_number The friend number of the friend this lossless packet--- should be sent to.--- @param data A byte array containing the packet data.--- @param length The length of the packet data byte array.------ @return true on success.-foreign import ccall tox_friend_send_lossless_packet :: Tox a -> Word32 -> CString -> CSize -> CErr ErrFriendCustomPacket -> IO Bool-callFriendLosslessPacket :: (Tox a -> Word32 -> CString -> CSize -> CErr ErrFriendCustomPacket -> IO Bool) ->- Tox a -> Word32 -> BS.ByteString -> IO (Either ErrFriendCustomPacket Bool)-callFriendLosslessPacket f tox fn d =- BS.useAsCStringLen d $ \(dataPtr, dataLen) ->- callErrFun $ f tox fn dataPtr (fromIntegral dataLen)--toxFriendLosslessPacket :: Tox a -> Word32 -> BS.ByteString -> IO (Either ErrFriendCustomPacket Bool)-toxFriendLosslessPacket = callFriendLosslessPacket tox_friend_send_lossless_packet--callFriendCustomPacketCb :: FriendLosslessPacketCb a -> CFriendLosslessPacketCb a-callFriendCustomPacketCb f tox fn dataPtr dataLen udPtr = do- ud <- deRefStablePtr udPtr- d <- BS.packCStringLen (dataPtr, fromIntegral dataLen)- modifyMVar_ ud $ f tox fn d---- | @param friend_number The friend number of the friend who sent a lossy--- packet.--- @param data A byte array containing the received packet data.--- @param length The length of the packet data byte array.-type FriendLossyPacketCb a = Tox a -> Word32 -> BS.ByteString -> a -> IO a-type CFriendLossyPacketCb a = Tox a -> Word32 -> CString -> CSize -> UserData a -> IO ()-foreign import ccall "wrapper" wrapFriendLossyPacketCb :: CFriendLossyPacketCb a -> IO (FunPtr (CFriendLossyPacketCb a))--callFriendLossyPacketCb :: FriendLossyPacketCb a -> CFriendLossyPacketCb a-callFriendLossyPacketCb = callFriendCustomPacketCb--friendLossyPacketCb :: FriendLossyPacketCb a -> IO (FunPtr (CFriendLossyPacketCb a))-friendLossyPacketCb = wrapFriendLossyPacketCb . callFriendLossyPacketCb----- | Set the callback for the `friend_lossy_packet` event. Pass 'nullPtr' to--- unset.----foreign import ccall tox_callback_friend_lossy_packet :: Tox a -> FunPtr (CFriendLossyPacketCb a) -> IO ()---- | @param friend_number The friend number of the friend who sent the packet.--- @param data A byte array containing the received packet data.--- @param length The length of the packet data byte array.-type FriendLosslessPacketCb a = Tox a -> Word32 -> BS.ByteString -> a -> IO a-type CFriendLosslessPacketCb a = Tox a -> Word32 -> CString -> CSize -> UserData a -> IO ()-foreign import ccall "wrapper" wrapFriendLosslessPacketCb :: CFriendLosslessPacketCb a -> IO (FunPtr (CFriendLosslessPacketCb a))--callFriendLosslessPacketCb :: FriendLosslessPacketCb a -> CFriendLosslessPacketCb a-callFriendLosslessPacketCb = callFriendCustomPacketCb--friendLosslessPacketCb :: FriendLosslessPacketCb a -> IO (FunPtr (CFriendLosslessPacketCb a))-friendLosslessPacketCb = wrapFriendLosslessPacketCb . callFriendLosslessPacketCb----- | Set the callback for the `friend_lossless_packet` event. Pass 'nullPtr' to--- unset.----foreign import ccall tox_callback_friend_lossless_packet :: Tox a -> FunPtr (CFriendLosslessPacketCb a) -> IO ()----------------------------------------------------------------------------------------- :: Low-level network information----------------------------------------------------------------------------------------- | Writes the temporary DHT public key of this instance to a byte array.------ This can be used in combination with an externally accessible IP address and--- the bound port (from tox_self_get_udp_port) to run a temporary bootstrap--- node.------ Be aware that every time a new instance is created, the DHT public key--- changes, meaning this cannot be used to run a permanent bootstrap node.------ @param dht_id A memory region of at least 'tox_public_key_size' bytes. If--- this parameter is 'nullPtr', this function has no effect.-foreign import ccall tox_self_get_dht_id :: Tox a -> CString -> IO ()--toxSelfGetDhtId :: Tox a -> IO BS.ByteString-toxSelfGetDhtId tox =- let idLen = fromIntegral tox_public_key_size in- allocaArray idLen $ \idPtr -> do- tox_self_get_dht_id tox idPtr- BS.packCStringLen (idPtr, idLen)--data ErrGetPort- = ErrGetPortOk- -- The function returned successfully.-- | ErrGetPortNotBound- -- The instance was not bound to any port.- deriving (Eq, Ord, Enum, Bounded, Read, Show)----- | Return the UDP port this Tox instance is bound to.-foreign import ccall tox_self_get_udp_port :: Tox a -> CErr ErrGetPort -> IO Word16-callSelfGetUdpPort :: (Tox a -> CErr ErrGetPort -> IO Word16) ->- Tox a -> IO (Either ErrGetPort Word16)-callSelfGetUdpPort f tox = callErrFun $ f tox--toxSelfGetUdpPort :: Tox a -> IO (Either ErrGetPort Word16)-toxSelfGetUdpPort = callSelfGetUdpPort tox_self_get_udp_port---- | Return the TCP port this Tox instance is bound to. This is only relevant if--- the instance is acting as a TCP relay.-foreign import ccall tox_self_get_tcp_port :: Tox a -> CErr ErrGetPort -> IO Word16-callSelfGetTcpPort :: (Tox a -> CErr ErrGetPort -> IO Word16) ->- Tox a -> IO (Either ErrGetPort Word16)-callSelfGetTcpPort f tox = callErrFun $ f tox--toxSelfGetTcpPort :: Tox a -> IO (Either ErrGetPort Word16)-toxSelfGetTcpPort = callSelfGetTcpPort tox_self_get_udp_port
− src/Network/Tox/C/Type.hs
@@ -1,17 +0,0 @@-{-# LANGUAGE Safe #-}-module Network.Tox.C.Type where--import Control.Concurrent.MVar (MVar)-import Foreign.Ptr (Ptr)-import Foreign.StablePtr (StablePtr)----- | The Tox instance type. All the state associated with a connection is held--- within the instance. Multiple instances can exist and operate concurrently.--- The maximum number of Tox instances that can exist on a single network device--- is limited. Note that this is not just a per-process limit, since the--- limiting factor is the number of usable ports on a device.-data ToxStruct a-type Tox a = Ptr (ToxStruct a)--type UserData a = StablePtr (MVar a)
− src/Network/Tox/C/Version.hs
@@ -1,27 +0,0 @@-{-# LANGUAGE Trustworthy #-}-module Network.Tox.C.Version where--import Data.Word (Word32)---------------------------------------------------------------------------------------- :: API version---------------------------------------------------------------------------------------- | The major version number. Incremented when the API or ABI changes in an--- incompatible way.-foreign import ccall tox_version_major :: Word32---- | The minor version number. Incremented when functionality is added without--- breaking the API or ABI. Set to 0 when the major version number is--- incremented.-foreign import ccall tox_version_minor :: Word32---- | The patch or revision number. Incremented when bugfixes are applied without--- changing any functionality or API or ABI.-foreign import ccall tox_version_patch :: Word32---- | Return whether the compiled library version is compatible with the passed--- version numbers.-foreign import ccall tox_version_is_compatible :: Word32 -> Word32 -> Word32 -> Bool
+ src/Network/Tox/Crypto.lhs view
@@ -0,0 +1,16 @@+\chapter{Crypto}++\begin{code}+{-# LANGUAGE Safe #-}+module Network.Tox.Crypto where+\end{code}++The Crypto module contains all the functions and data types related to+cryptography. This includes random number generation, encryption and+decryption, key generation, operations on nonces and generating random nonces.++\input{src/Network/Tox/Crypto/Key.lhs}+\input{src/Network/Tox/Crypto/KeyPair.lhs}+\input{src/Network/Tox/Crypto/CombinedKey.lhs}+\input{src/Network/Tox/Crypto/Nonce.lhs}+\input{src/Network/Tox/Crypto/Box.lhs}
+ src/Network/Tox/Crypto/Box.lhs view
@@ -0,0 +1,184 @@+\section{Box}++\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.Crypto.Box+ ( PlainText (..)+ , CipherText+ , cipherText+ , unCipherText+ , decode+ , encode+ , decrypt, decryptR+ , encrypt, encryptR+ ) where++import Control.Applicative ((<$>), (<*>))+import qualified Crypto.Saltine.Core.Box as Sodium (boxAfterNM,+ boxOpenAfterNM)+import qualified Crypto.Saltine.Internal.ByteSizes as ByteSizes+import Data.Binary (Binary, get, put)+import Data.Binary.Get (Decoder (..), pushChunk,+ runGetIncremental)+import Data.Binary.Put (runPut)+import Data.ByteString (ByteString)+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Base16 as Base16+import qualified Data.ByteString.Lazy as LazyByteString+import Data.MessagePack (MessagePack (..))+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Network.MessagePack.Rpc (Doc (..))+import qualified Network.MessagePack.Rpc as Rpc+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)+import Text.Read (readPrec)++import Network.Tox.Crypto.Key (CombinedKey, Key (..),+ Nonce)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}+++\end{code}++The Tox protocol differentiates between two types of text: Plain Text and+Cipher Text. Cipher Text may be transmitted over untrusted data channels.+Plain Text can be Sensitive or Non Sensitive. Sensitive Plain Text must be+transformed into Cipher Text using the encryption function before it can be+transmitted over untrusted data channels.++\begin{code}+++newtype PlainText = PlainText { unPlainText :: ByteString }+ deriving (Eq, Binary, Generic, Typeable)++instance MessagePack PlainText++instance Show PlainText where+ show = show . Base16.encode . unPlainText++instance Read PlainText where+ readPrec = PlainText . fst . Base16.decode <$> readPrec+++newtype CipherText = CipherText { unCipherText :: ByteString }+ deriving (Eq, Typeable)++cipherText :: Monad m => ByteString -> m CipherText+cipherText bs+ | ByteString.length bs >= ByteSizes.boxMac = return $ CipherText bs+ | otherwise = fail "ciphertext is too short"++instance Binary CipherText where+ put = put . unCipherText+ get = get >>= cipherText++instance MessagePack CipherText where+ toObject = toObject . unCipherText+ fromObject x = do+ bs <- fromObject x+ cipherText bs++instance Show CipherText where+ show = show . Base16.encode . unCipherText++instance Read CipherText where+ readPrec = fst . Base16.decode <$> readPrec >>= cipherText+++encode :: Binary a => a -> PlainText+encode =+ PlainText . LazyByteString.toStrict . runPut . put+++decode :: (Monad m, Binary a) => PlainText -> m a+decode (PlainText bytes) =+ finish $ pushChunk (runGetIncremental get) bytes+ where+ finish = \case+ Done _ _ output -> return output+ Fail _ _ msg -> fail msg+ Partial f -> finish $ f Nothing+++\end{code}++The encryption function takes a Combined Key, a Nonce, and a Plain Text, and+returns a Cipher Text. It uses \texttt{crypto\_box\_afternm} to perform the+encryption. The meaning of the sentence "encrypting with a secret key, a+public key, and a nonce" is: compute a combined key from the secret key and the+public key and then use the encryption function for the transformation.++\begin{code}++encrypt :: CombinedKey -> Nonce -> PlainText -> CipherText+encrypt (Key ck) (Key nonce) (PlainText bytes) =+ CipherText $ Sodium.boxAfterNM ck nonce bytes++encryptR :: Rpc.Rpc (CombinedKey -> Nonce -> PlainText -> Rpc.Returns CipherText)+encryptR =+ Rpc.stubs "Box.encrypt"+ (Arg "key" $ Arg "nonce" $ Arg "plain" $ Ret "encrypted")+ encrypt++\end{code}++The decryption function takes a Combined Key, a Nonce, and a Cipher Text, and+returns either a Plain Text or an error. It uses+\texttt{crypto\_box\_open\_afternm} from the NaCl library. Since the cipher is+symmetric, the encryption function can also perform decryption, but will not+perform message authentication, so the implementation must be careful to use+the correct functions.++\begin{code}++decrypt :: CombinedKey -> Nonce -> CipherText -> Maybe PlainText+decrypt (Key ck) (Key nonce) (CipherText bytes) =+ PlainText <$> Sodium.boxOpenAfterNM ck nonce bytes++decryptR :: Rpc.Rpc (CombinedKey -> Nonce -> CipherText -> Rpc.Returns (Maybe PlainText))+decryptR =+ Rpc.stubs "Box.decrypt"+ (Arg "key" $ Arg "nonce" $ Arg "encrypted" $ Ret "plain")+ decrypt++\end{code}++\texttt{crypto\_box} uses xsalsa20 symmetric encryption and poly1305+authentication.++The create and handle request functions are the encrypt and decrypt functions+for a type of DHT packets used to send data directly to other DHT nodes. To be+honest they should probably be in the DHT module but they seem to fit better+here. TODO: What exactly are these functions?+++\begin{code}+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary PlainText where+ arbitrary = PlainText . ByteString.pack <$> arbitrary+++instance Arbitrary CipherText where+ arbitrary = encrypt <$> arbitrary <*> arbitrary <*> arbitrary++\end{code}
+ src/Network/Tox/Crypto/CombinedKey.lhs view
@@ -0,0 +1,58 @@+\subsection{Combined Key}++\begin{code}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.Crypto.CombinedKey where++import qualified Crypto.Saltine.Core.Box as Sodium (beforeNM)+import Network.MessagePack.Rpc (Doc (..))+import qualified Network.MessagePack.Rpc as Rpc++import Network.Tox.Crypto.Key (CombinedKey, Key (..), PublicKey,+ SecretKey)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}++\end{code}++A Combined Key is computed from a Secret Key and a Public Key using the NaCl+function \texttt{crypto\_box\_beforenm}. Given two Key Pairs KP1 (SK1, PK1) and+KP2 (SK2, PK2), the Combined Key computed from (SK1, PK2) equals the one+computed from (SK2, PK1). This allows for symmetric encryption, as peers can+derive the same shared key from their own secret key and their peer's public+key.++\begin{code}++precompute :: SecretKey -> PublicKey -> CombinedKey+precompute (Key sk) (Key pk) =+ Key $ Sodium.beforeNM sk pk+++precomputeR :: Rpc.Rpc (SecretKey -> PublicKey -> Rpc.Returns CombinedKey)+precomputeR =+ Rpc.stubs "CombinedKey.precompute"+ (Arg "sk" $ Arg "pk" $ Ret "key")+ precompute+++\end{code}++In the Tox protocol, packets are encrypted using the public key of the receiver+and the secret key of the sender. The receiver decrypts the packets using the+receiver's secret key and the sender's public key.++The fact that the same key is used to encrypt and decrypt packets on both sides+means that packets being sent could be replayed back to the sender if there is+nothing to prevent it.++The shared key generation is the most resource intensive part of the+encryption/decryption which means that resource usage can be reduced+considerably by saving the shared keys and reusing them later as much as+possible.
+ src/Network/Tox/Crypto/Key.lhs view
@@ -0,0 +1,152 @@+\section{Key}++\begin{code}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.Crypto.Key where++import Control.Applicative ((<$>))+import Control.Monad ((>=>))+import qualified Crypto.Saltine.Class as Sodium (IsEncoding,+ decode, encode)+import qualified Crypto.Saltine.Core.Box as Sodium (CombinedKey,+ Nonce, PublicKey,+ SecretKey)+import qualified Crypto.Saltine.Internal.ByteSizes as Sodium (boxBeforeNM,+ boxNonce, boxPK,+ boxSK)+import Data.Binary (Binary)+import qualified Data.Binary as Binary (get, put)+import qualified Data.Binary.Get as Binary (getByteString,+ runGet)+import qualified Data.Binary.Put as Binary (putByteString)+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Base16 as Base16+import qualified Data.ByteString.Lazy as LazyByteString+import Data.MessagePack (MessagePack (..))+import Data.Proxy (Proxy (..))+import Data.Typeable (Typeable)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)+import qualified Test.QuickCheck.Arbitrary as Arbitrary+import Text.Read (readPrec)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}++\end{code}++A Crypto Number is a large fixed size unsigned (non-negative) integer. Its binary+encoding is as a Big Endian integer in exactly the encoded byte size. Its+human-readable encoding is as a base-16 number encoded as String. The NaCl+implementation \href{https://github.com/jedisct1/libsodium}{libsodium} supplies+the functions \texttt{sodium\_bin2hex} and \texttt{sodium\_hex2bin} to aid in+implementing the human-readable encoding. The in-memory encoding of these+crypto numbers in NaCl already satisfies the binary encoding, so for+applications directly using those APIs, binary encoding and decoding is the+\href{https://en.wikipedia.org/wiki/Identity_function}{identity function}.++\begin{code}++class Sodium.IsEncoding a => CryptoNumber a where+ encodedByteSize :: Proxy a -> Int++\end{code}++Tox uses four kinds of Crypto Numbers:++\begin{tabular}{l|l|l}+ Type & Bits & Encoded byte size \\+ \hline+ Public Key & 256 & 32 \\+ Secret Key & 256 & 32 \\+ Combined Key & 256 & 32 \\+ Nonce & 192 & 24 \\+\end{tabular}++\begin{code}++instance CryptoNumber Sodium.PublicKey where { encodedByteSize Proxy = Sodium.boxPK }+instance CryptoNumber Sodium.SecretKey where { encodedByteSize Proxy = Sodium.boxSK }+instance CryptoNumber Sodium.CombinedKey where { encodedByteSize Proxy = Sodium.boxBeforeNM }+instance CryptoNumber Sodium.Nonce where { encodedByteSize Proxy = Sodium.boxNonce }++deriving instance Typeable Sodium.PublicKey+deriving instance Typeable Sodium.SecretKey+deriving instance Typeable Sodium.CombinedKey+deriving instance Typeable Sodium.Nonce++newtype Key a = Key { unKey :: a }+ deriving (Eq, Ord, Typeable)++type PublicKey = Key Sodium.PublicKey+type SecretKey = Key Sodium.SecretKey+type CombinedKey = Key Sodium.CombinedKey+type Nonce = Key Sodium.Nonce++instance Sodium.IsEncoding a => Sodium.IsEncoding (Key a) where+ encode = Sodium.encode . unKey+ decode = fmap Key . Sodium.decode+++keyToInteger :: Sodium.IsEncoding a => Key a -> Integer+keyToInteger =+ Binary.runGet Binary.get . encode+ where+ prefix = LazyByteString.pack+ [ 0x01 -- Tag: big integer+ , 0x01 -- Sign: positive+ , 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20 -- Length: 32 bytes+ ]+ encode =+ LazyByteString.append prefix+ . LazyByteString.reverse+ . LazyByteString.fromStrict+ . Sodium.encode+++decode :: (CryptoNumber a, Monad m) => ByteString.ByteString -> m (Key a)+decode bytes =+ case Sodium.decode bytes of+ Just key -> return $ Key key+ Nothing -> fail "unable to decode ByteString to Key"+++instance CryptoNumber a => Binary (Key a) where+ put (Key key) =+ Binary.putByteString $ Sodium.encode key++ get = do+ bytes <- Binary.getByteString $ encodedByteSize (Proxy :: Proxy a)+ decode bytes+++instance CryptoNumber a => Show (Key a) where+ show (Key key) = show $ Base16.encode $ Sodium.encode key++instance CryptoNumber a => Read (Key a) where+ readPrec = fst . Base16.decode <$> readPrec >>= decode++instance CryptoNumber a => MessagePack (Key a) where+ toObject = toObject . Sodium.encode+ fromObject = fromObject >=> decode+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance CryptoNumber a => Arbitrary (Key a) where+ arbitrary = do+ bytes <- fmap ByteString.pack $ Arbitrary.vector $ encodedByteSize (Proxy :: Proxy a)+ decode bytes+\end{code}
+ src/Network/Tox/Crypto/KeyPair.lhs view
@@ -0,0 +1,94 @@+\subsection{Key Pair}++A Key Pair is a pair of Secret Key and Public Key. A new key pair is generated+using the \texttt{crypto\_box\_keypair} function of the NaCl crypto library. Two+separate calls to the key pair generation function must return distinct key+pairs. See the \href{https://nacl.cr.yp.to/box.html}{NaCl documentation} for+details.++A Public Key can be computed from a Secret Key using the NaCl function+\texttt{crypto\_scalarmult\_base}, which computes the scalar product of a+standard group element and the Secret Key. See the+\href{https://nacl.cr.yp.to/scalarmult.html}{NaCl documentation} for details.++\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.Crypto.KeyPair where++import Control.Applicative ((<$>))+import qualified Crypto.Saltine.Class as Sodium (decode, encode)+import qualified Crypto.Saltine.Core.Box as Sodium (newKeypair)+import qualified Crypto.Saltine.Core.ScalarMult as Sodium (multBase)+import Data.Binary (Binary)+import Data.MessagePack (MessagePack (..))+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Network.MessagePack.Rpc (Doc (..))+import qualified Network.MessagePack.Rpc as Rpc+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)++import Network.Tox.Crypto.Key (Key (..))+import qualified Network.Tox.Crypto.Key as Key+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}+++data KeyPair = KeyPair+ { secretKey :: Key.SecretKey+ , publicKey :: Key.PublicKey+ }+ deriving (Eq, Show, Read, Generic, Typeable)++instance Binary KeyPair+instance MessagePack KeyPair+++newKeyPair :: IO KeyPair+newKeyPair = do+ (sk, pk) <- Sodium.newKeypair+ return $ KeyPair (Key sk) (Key pk)++newKeyPairR :: Rpc.Rpc (Rpc.ReturnsM IO KeyPair)+newKeyPairR =+ Rpc.stubs "KeyPair.newKeyPair"+ (RetM "keyPair")+ newKeyPair+++fromSecretKey :: Key.SecretKey -> KeyPair+fromSecretKey sk =+ let+ skBytes = Sodium.encode sk+ Just skScalar = Sodium.decode skBytes+ pkGroupElement = Sodium.multBase skScalar+ pkBytes = Sodium.encode pkGroupElement+ Just pk = Sodium.decode pkBytes+ in+ KeyPair sk pk++fromSecretKeyR :: Rpc.Rpc (Key.SecretKey -> Rpc.Returns KeyPair)+fromSecretKeyR =+ Rpc.stubs "KeyPair.fromSecretKey"+ (Arg "key" $ Ret "keyPair")+ fromSecretKey+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary KeyPair where+ arbitrary =+ fromSecretKey <$> arbitrary+\end{code}
+ src/Network/Tox/Crypto/Keyed.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE Safe #-}+{-# LANGUAGE UndecidableInstances #-}++-- | Monad class for caching of combined keys+module Network.Tox.Crypto.Keyed where++import Control.Applicative (Applicative, pure, (<*>))+import Control.Monad (Monad)+import Control.Monad.Random (RandT)+import Control.Monad.Reader (ReaderT)+import Control.Monad.RWS (RWST)+import Control.Monad.State (StateT)+import Control.Monad.Trans (lift)+import Control.Monad.Writer (WriterT)+import Data.Monoid (Monoid)++import qualified Network.Tox.Crypto.CombinedKey as CombinedKey+import Network.Tox.Crypto.Key (CombinedKey, PublicKey,+ SecretKey)++class (Monad m, Applicative m) => Keyed m where+ getCombinedKey :: SecretKey -> PublicKey -> m CombinedKey++instance Keyed m => Keyed (ReaderT r m) where+ getCombinedKey = (lift .) . getCombinedKey+instance (Monoid w, Keyed m) => Keyed (WriterT w m) where+ getCombinedKey = (lift .) . getCombinedKey+instance Keyed m => Keyed (StateT s m) where+ getCombinedKey = (lift .) . getCombinedKey+instance (Monoid w, Keyed m) => Keyed (RWST r w s m) where+ getCombinedKey = (lift .) . getCombinedKey+instance Keyed m => Keyed (RandT s m) where+ getCombinedKey = (lift .) . getCombinedKey++-- | trivial instance: the trivial monad, with no caching of keys+newtype NullKeyed a = NullKeyed { runNullKeyed :: a }+instance Functor NullKeyed where+ fmap f (NullKeyed x) = NullKeyed (f x)+instance Applicative NullKeyed where+ pure = NullKeyed+ (NullKeyed f) <*> (NullKeyed x) = NullKeyed (f x)+instance Monad NullKeyed where+ return = NullKeyed+ NullKeyed x >>= f = f x+instance Keyed NullKeyed where+ getCombinedKey = (NullKeyed .) . CombinedKey.precompute
+ src/Network/Tox/Crypto/KeyedT.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE Trustworthy #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE UndecidableInstances #-}+module Network.Tox.Crypto.KeyedT where++import Control.Applicative (Applicative, (<$>))+import Control.Monad (Monad)+import Control.Monad.IO.Class (MonadIO)+import Control.Monad.State (MonadState, StateT,+ StateT (..), evalStateT,+ gets, modify, runStateT,+ state)+import Control.Monad.Trans (MonadTrans)+import Control.Monad.Writer (MonadWriter)++import Data.Map (Map)+import qualified Data.Map as Map+import qualified Network.Tox.Crypto.CombinedKey as CombinedKey+import Network.Tox.Crypto.Key (CombinedKey, PublicKey,+ SecretKey)+import Network.Tox.Crypto.Keyed (Keyed (..))+import Network.Tox.Network.MonadRandomBytes (MonadRandomBytes)+import Network.Tox.Network.Networked (Networked)+import Network.Tox.Timed (Timed)++type KeyRing = Map (SecretKey, PublicKey) CombinedKey++-- | caches computations of combined keys. Makes no attempt to delete old keys.+newtype KeyedT m a = KeyedT (StateT KeyRing m a)+ deriving (Monad, Applicative, Functor, MonadWriter w+ , MonadRandomBytes, MonadTrans, MonadIO, Networked, Timed)++runKeyedT :: Monad m => KeyedT m a -> KeyRing -> m (a, KeyRing)+runKeyedT (KeyedT m) = runStateT m++evalKeyedT :: Monad m => KeyedT m a -> KeyRing -> m a+evalKeyedT (KeyedT m) = evalStateT m++instance (MonadState s m, Applicative m) => MonadState s (KeyedT m) where+ state f = KeyedT . StateT $ \s -> (, s) <$> state f++instance (Monad m, Applicative m) => Keyed (KeyedT m) where+ getCombinedKey secretKey publicKey =+ let keys = (secretKey, publicKey)+ in KeyedT $ gets (Map.lookup keys) >>= \case+ Nothing ->+ let shared = CombinedKey.precompute secretKey publicKey+ in modify (Map.insert keys shared) >> return shared+ Just shared -> return shared
+ src/Network/Tox/Crypto/Nonce.lhs view
@@ -0,0 +1,62 @@+\subsection{Nonce}++A random nonce is generated using the cryptographically secure random number+generator from the NaCl library \texttt{randombytes}.++A nonce is incremented by interpreting it as a Big Endian number and adding 1.+If the nonce has the maximum value, the value after the increment is 0.++Most parts of the protocol use random nonces. This prevents new nonces from+being associated with previous nonces. If many different packets could be tied+together due to how the nonces were generated, it might for example lead to+tying DHT and onion announce packets together. This would introduce a flaw in+the system as non friends could tie some people's DHT keys and long term keys+together.++\begin{code}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.Crypto.Nonce where++import Control.Applicative ((<$>))+import qualified Crypto.Saltine.Class as Sodium (decode, encode, nudge)+import qualified Crypto.Saltine.Core.Box as Sodium (newNonce)+import qualified Data.ByteString as ByteString+import Network.MessagePack.Rpc (Doc (..))+import qualified Network.MessagePack.Rpc as Rpc++import Network.Tox.Crypto.Key+++newNonce :: IO Nonce+newNonce = Key <$> Sodium.newNonce++newNonceR :: Rpc.Rpc (Rpc.ReturnsM IO Nonce)+newNonceR =+ Rpc.stubs "Nonce.newNonce"+ (RetM "nonce")+ newNonce+++reverseNonce :: Nonce -> Nonce+reverseNonce (Key nonce) =+ let Just reversed = Sodium.decode $ ByteString.reverse $ Sodium.encode nonce in+ Key reversed+++nudge :: Nonce -> Nonce+nudge =+ Key . Sodium.nudge . unKey+++increment :: Nonce -> Nonce+increment =+ reverseNonce . nudge . reverseNonce++incrementR :: Rpc.Rpc (Nonce -> Rpc.Returns Nonce)+incrementR =+ Rpc.stubs "Nonce.increment"+ (Arg "nonce" $ Ret "incremented")+ increment++\end{code}
+ src/Network/Tox/DHT.lhs view
@@ -0,0 +1,179 @@+\chapter{DHT}++\begin{code}+{-# LANGUAGE Safe #-}+module Network.Tox.DHT where+\end{code}++The DHT is a self-organizing swarm of all nodes in the Tox network. A node in+the Tox network is also called a "Tox node". When we talk about "peers", we mean+any node that is not the local node (the subject). This module takes care of+finding the IP and port of nodes and establishing a route to them directly via+UDP using \href{#hole-punching}{hole punching} if necessary. The DHT only runs+on UDP and so is only used if UDP works.++Every node in the Tox DHT has an ephemeral Key Pair called the DHT Key Pair,+consisting of the DHT Secret Key and the DHT Public Key. The DHT Public Key+acts as the node address. The DHT Key Pair is renewed every time the Tox+instance is closed or restarted. An implementation may choose to renew the key+more often, but doing so will disconnect all peers.++The DHT public key of a friend is found using the \href{#onion}{onion} module.+Once the DHT public key of a friend is known, the DHT is used to find them and+connect directly to them via UDP.++\input{src/Network/Tox/DHT/Distance.lhs}+\input{src/Network/Tox/DHT/ClientList.lhs}+\input{src/Network/Tox/DHT/KBuckets.lhs}+\input{src/Network/Tox/DHT/DhtState.lhs}++\section{Self-organisation}++Self-organising in the DHT occurs through each DHT peer connecting to an+arbitrary number of peers closest to their own DHT public key and some that are+further away.++If each peer in the network knows the peers with the DHT public key closest to+its DHT public key, then to find a specific peer with public key X a peer just+needs to recursively ask peers in the DHT for known peers that have the DHT+public keys closest to X. Eventually the peer will find the peers in the DHT+that are the closest to that peer and, if that peer is online, they will find+them.++\input{src/Network/Tox/DHT/DhtPacket.lhs}++\section{RPC Services}++\input{src/Network/Tox/DHT/RpcPacket.lhs}+\input{src/Network/Tox/DHT/PingPacket.lhs}++\subsection{Nodes Service}++The Nodes Service is used to query another DHT node for up to 4 nodes they know+that are the closest to a requested node.++The DHT Nodes RPC service uses the Packed Node Format.++Only the UDP Protocol (IP Type \texttt{2} and \texttt{10}) is used in the DHT+module when sending nodes with the packed node format. This is because the TCP+Protocol is used to send TCP relay information and the DHT is UDP only.++\input{src/Network/Tox/DHT/NodesRequest.lhs}+\input{src/Network/Tox/DHT/NodesResponse.lhs}++\input{src/Network/Tox/DHT/Operation.lhs}++\section{NATs}++We assume that peers are either directly accessible or are behind one of 3+types of NAT:++Cone NATs: Assign one whole port to each UDP socket behind the NAT; any packet+from any IP/port sent to that assigned port from the internet will be forwarded+to the socket behind it.++Restricted Cone NATs: Assign one whole port to each UDP socket behind the NAT.+However, it will only forward packets from IPs that the UDP socket has sent a+packet to.++Symmetric NATs: The worst kind of NAT, they assign a new port for each IP/port+a packet is sent to. They treat each new peer you send a UDP packet to as a+\texttt{'connection'} and will only forward packets from the IP/port of that+\texttt{'connection'}.+++\section{Hole punching}++Holepunching on normal cone NATs is achieved simply through the way in which+the DHT functions.++If more than half of the 8 peers closest to the friend in the DHT return an+IP/port for the friend and we send a ping request to each of the returned+IP/ports but get no response. If we have sent 4 ping requests to 4 IP/ports+that supposedly belong to the friend and get no response, then this is enough+for toxcore to start the hole punching. The numbers 8 and 4 are used in+toxcore and were chosen based on feel alone and so may not be the best numbers.++Before starting the hole punching, the peer will send a NAT ping packet to the+friend via the peers that say they know the friend. If a NAT ping response+with the same random number is received the hole punching will start.++If a NAT ping request is received, we will first check if it is from a friend.+If it is not from a friend it will be dropped. If it is from a friend, a+response with the same 8 byte number as in the request will be sent back via+the nodes that know the friend sending the request. If no nodes from the+friend are known, the packet will be dropped.++Receiving a NAT ping response therefore means that the friend is both online+and actively searching for us, as that is the only way they would know nodes+that know us. This is important because hole punching will work only if the+friend is actively trying to connect to us.++NAT ping requests are sent every 3 seconds in toxcore, if no response is+received for 6 seconds, the hole punching will stop. Sending them in longer+intervals might increase the possibility of the other node going offline and+ping packets sent in the hole punching being sent to a dead peer but decrease+bandwidth usage. Decreasing the intervals will have the opposite effect.++There are 2 cases that toxcore handles for the hole punching. The first case+is if each 4+ peers returned the same IP and port. The second is if the 4++peers returned same IPs but different ports.++A third case that may occur is the peers returning different IPs and ports.+This can only happen if the friend is behind a very restrictive NAT that cannot+be hole punched or if the peer recently connected to another internet+connection and some peers still have the old one stored. Since there is+nothing we can do for the first option it is recommended to just use the most+common IP returned by the peers and to ignore the other IP/ports.++In the case where the peers return the same IP and port it means that the other+friend is on a restricted cone NAT. These kinds of NATs can be hole punched by+getting the friend to send a packet to our public IP/port. This means that+hole punching can be achieved easily and that we should just continue sending+DHT ping packets regularly to that IP/port until we get a ping response. This+will work because the friend is searching for us in the DHT and will find us+and will send us a packet to our public IP/port (or try to with the hole+punching), thereby establishing a connection.++For the case where peers do not return the same ports, this means that the+other peer is on a symmetric NAT. Some symmetric NATs open ports in sequences+so the ports returned by the other peers might be something like: 1345, 1347,+1389, 1395. The method to hole punch these NATs is to try to guess which ports+are more likely to be used by the other peer when they try sending us ping+requests and send some ping requests to these ports. Toxcore just tries all+the ports beside each returned port (ex: for the 4 ports previously it would+try: 1345, 1347, 1389, 1395, 1346, 1348, 1390, 1396, 1344, 1346...) getting+gradually further and further away and, although this works, the method could+be improved. When using this method toxcore will try up to 48 ports every 3+seconds until both connect. After 5 tries toxcore doubles this and starts+trying ports from 1024 (48 each time) along with the previous port guessing.+This is because I have noticed that this seemed to fix it for some symmetric+NATs, most likely because a lot of them restart their count at 1024.++Increasing the amount of ports tried per second would make the hole punching go+faster but might DoS NATs due to the large number of packets being sent to+different IPs in a short amount of time. Decreasing it would make the hole+punching slower.++This works in cases where both peers have different NATs. For example, if A+and B are trying to connect to each other: A has a symmetric NAT and B a+restricted cone NAT. A will detect that B has a restricted cone NAT and keep+sending ping packets to his one IP/port. B will detect that A has a symmetric+NAT and will send packets to it to try guessing his ports. If B manages to+guess the port A is sending packets from they will connect together.++\section{DHT Bootstrap Info (0xf0)}++Bootstrap nodes are regular Tox nodes with a stable DHT public key. This means+the DHT public key does not change across restarts. DHT bootstrap nodes have one+additional request kind: Bootstrap Info. The request is simply a packet of+length 78 bytes where the first byte is 0xf0. The other bytes are ignored.++The response format is as follows:++\begin{tabular}{l|l|l}+ Length & Type & \href{#protocol-packet}{Contents} \\+ \hline+ \texttt{4} & Word32 & Bootstrap node version \\+ \texttt{256} & Bytes & Message of the day \\+\end{tabular}
+ src/Network/Tox/DHT/ClientList.lhs view
@@ -0,0 +1,156 @@+\section{Client Lists}++\begin{code}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE Safe #-}+module Network.Tox.DHT.ClientList where++import Control.Applicative ((<$>), (<*>))+import Control.Monad (join)+import Data.List (sort)+import Data.Map (Map)+import qualified Data.Map as Map+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary,+ arbitrarySizedNatural)+import Test.QuickCheck.Gen (Gen)+import qualified Test.QuickCheck.Gen as Gen++import Network.Tox.Crypto.Key (PublicKey)+import Network.Tox.DHT.ClientNode (ClientNode)+import qualified Network.Tox.DHT.ClientNode as ClientNode+import Network.Tox.DHT.Distance (Distance)+import qualified Network.Tox.DHT.Distance as Distance+import Network.Tox.NodeInfo.NodeInfo (NodeInfo)+import qualified Network.Tox.NodeInfo.NodeInfo as NodeInfo+import Network.Tox.Time (Timestamp)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}++\end{code}++A Client List of \textit{maximum size} \texttt{k} with a given public key as+\textit{base key} is an ordered set of at most \texttt{k} nodes close to the+base key. The elements are sorted by \href{#distance}{distance} from the base+key. Thus, the first (smallest) element of the set is the closest one to the+base key in that set, the last (greatest) element is the furthest away. The+maximum size and base key are constant throughout the lifetime of a Client+List.+++\begin{code}++data ClientList = ClientList+ { baseKey :: PublicKey+ , maxSize :: Int+ , nodes :: ClientNodes+ }+ deriving (Eq, Read, Show)++type ClientNodes = Map Distance ClientNode++nodeInfos :: ClientList -> [NodeInfo]+nodeInfos = map ClientNode.nodeInfo . Map.elems . nodes++empty :: PublicKey -> Int -> ClientList+empty publicKey size = ClientList+ { baseKey = publicKey+ , maxSize = size+ , nodes = Map.empty+ }++isEmpty :: ClientList -> Bool+isEmpty = Map.null . nodes++updateClientNodes :: (ClientNodes -> ClientNodes) -> ClientList -> ClientList+updateClientNodes f clientList@ClientList{ nodes } =+ clientList{nodes = f nodes}++lookup :: PublicKey -> ClientList -> Maybe NodeInfo+lookup publicKey _cl@ClientList{ baseKey, nodes } =+ ClientNode.nodeInfo <$> Distance.xorDistance publicKey baseKey `Map.lookup` nodes++\end{code}+++A Client List is \textit{full} when the number of nodes it contains is the+maximum size of the list.++A node is \textit{viable} for entry if the Client List is not \textit{full} or the+node's public key has a lower distance from the base key than the current entry+with the greatest distance.++If a node is \textit{viable} and the Client List is \textit{full}, the entry+with the greatest distance from the base key is removed to keep the size below+the maximum configured size.++Adding a node whose key already exists will result in an update of the Node+Info in the Client List. Removing a node for which no Node Info exists in the+Client List has no effect. Thus, removing a node twice is permitted and has the+same effect as removing it once.++\begin{code}++full :: ClientList -> Bool+full ClientList{ nodes, maxSize } =+ Map.size nodes >= maxSize++addNode :: Timestamp -> NodeInfo -> ClientList -> ClientList+addNode time nodeInfo clientList@ClientList{ baseKey, maxSize } =+ (`updateClientNodes` clientList) $+ mapTake maxSize+ . Map.insert+ (Distance.xorDistance (NodeInfo.publicKey nodeInfo) baseKey)+ (ClientNode.newNode time nodeInfo)+ where+ -- | 'mapTake' is 'Data.Map.take' in >=containers-0.5.8, but we define it+ -- for compatibility with older versions.+ mapTake :: Int -> Map k a -> Map k a+ mapTake n = Map.fromDistinctAscList . take n . Map.toAscList+++removeNode :: PublicKey -> ClientList -> ClientList+removeNode publicKey clientList =+ (`updateClientNodes` clientList) .+ Map.delete . Distance.xorDistance publicKey $ baseKey clientList++viable :: NodeInfo -> ClientList -> Bool+viable nodeInfo ClientList{ baseKey, maxSize, nodes } =+ let key = Distance.xorDistance (NodeInfo.publicKey nodeInfo) baseKey+ in (key `elem`) . take maxSize . sort $ key : Map.keys nodes++\end{code}++The iteration order of a Client List is in order of distance from the base+key. I.e. the first node seen in iteration is the closest, and the last node+is the furthest away in terms of the distance metric.++\begin{code}++foldNodes :: (a -> NodeInfo -> a) -> a -> ClientList -> a+foldNodes f x = foldl f x . nodeInfos++closeNodes :: PublicKey -> ClientList -> [ (Distance, NodeInfo) ]+closeNodes publicKey ClientList{ baseKey, nodes } =+ Map.toAscList . fmap ClientNode.nodeInfo $+ Map.mapKeys (Distance.rebaseDistance baseKey publicKey) nodes++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++genClientList :: PublicKey -> Int -> Gen ClientList+genClientList publicKey size =+ foldl (flip $ uncurry addNode) (empty publicKey size) <$> Gen.listOf arbitrary+++instance Arbitrary ClientList where+ arbitrary = join $ genClientList <$> arbitrary <*> arbitrarySizedNatural+\end{code}
+ src/Network/Tox/DHT/ClientNode.lhs view
@@ -0,0 +1,37 @@+\begin{code}+{-# LANGUAGE Safe #-}+module Network.Tox.DHT.ClientNode where++import Control.Applicative ((<$>), (<*>))+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)++import Network.Tox.NodeInfo.NodeInfo (NodeInfo)+import Network.Tox.Time (Timestamp)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}++data ClientNode = ClientNode+ { nodeInfo :: NodeInfo+ , lastCheck :: Timestamp+ , checkCount :: Int+ }+ deriving (Eq, Read, Show)++newNode :: Timestamp -> NodeInfo -> ClientNode+newNode time node = ClientNode node time 0++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}++instance Arbitrary ClientNode where+ arbitrary = ClientNode <$> arbitrary <*> arbitrary <*> arbitrary++\end{code}
+ src/Network/Tox/DHT/DhtPacket.lhs view
@@ -0,0 +1,124 @@+\section{DHT Packet}++The DHT Packet contains the sender's DHT Public Key, an encryption Nonce, and+an encrypted payload. The payload is encrypted with the DHT secret key of the+sender, the DHT public key of the receiver, and the nonce that is sent along+with the packet. DHT Packets are sent inside Protocol Packets with a varying+Packet Kind.++\begin{tabular}{l|l|l}+ Length & Type & \href{#protocol-packet}{Contents} \\+ \hline+ \texttt{32} & Public Key & Sender DHT Public Key \\+ \texttt{24} & Nonce & Random nonce \\+ \texttt{[16,]} & Bytes & Encrypted payload \\+\end{tabular}++The encrypted payload is at least 16 bytes long, because the encryption+includes a \href{https://en.wikipedia.org/wiki/Message_authentication_code}{MAC}+of 16 bytes. A 16 byte payload would thus be the empty message. The DHT+protocol never actually sends empty messages, so in reality the minimum size is+27 bytes for the \href{#ping-service}{Ping Packet}.++\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE Safe #-}+module Network.Tox.DHT.DhtPacket where++import Control.Applicative ((<$>), (<*>))+import Data.Binary (Binary, get, put)+import Data.Binary.Get (getRemainingLazyByteString)+import Data.Binary.Put (putByteString, runPut)+import qualified Data.ByteString.Lazy as LazyByteString+import Data.MessagePack (MessagePack)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Network.Tox.Crypto.Box (CipherText, PlainText (..),+ unCipherText)+import qualified Network.Tox.Crypto.Box as Box+import Network.Tox.Crypto.Key (Nonce, PublicKey)+import Network.Tox.Crypto.Keyed (Keyed)+import qualified Network.Tox.Crypto.Keyed as Keyed+import Network.Tox.Crypto.KeyPair (KeyPair (..))+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)++++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}+++data DhtPacket = DhtPacket+ { senderPublicKey :: PublicKey+ , encryptionNonce :: Nonce+ , encryptedPayload :: CipherText+ }+ deriving (Eq, Read, Show, Generic, Typeable)++instance MessagePack DhtPacket+++instance Binary DhtPacket where+ put packet = do+ put $ senderPublicKey packet+ put $ encryptionNonce packet+ putByteString . unCipherText . encryptedPayload $ packet++ get =+ DhtPacket <$> get <*> get <*> (LazyByteString.toStrict <$> getRemainingLazyByteString >>= Box.cipherText)+++encrypt :: KeyPair -> PublicKey -> Nonce -> PlainText -> DhtPacket+encrypt = (((Keyed.runNullKeyed .) .) .) . encryptKeyed++encryptKeyed :: Keyed m => KeyPair -> PublicKey -> Nonce -> PlainText -> m DhtPacket+encryptKeyed (KeyPair senderSecretKey senderPublicKey') receiverPublicKey nonce plainText =+ (\combinedKey -> DhtPacket senderPublicKey' nonce $+ Box.encrypt combinedKey nonce plainText) <$>+ Keyed.getCombinedKey senderSecretKey receiverPublicKey+++encode :: Binary payload => KeyPair -> PublicKey -> Nonce -> payload -> DhtPacket+encode = (((Keyed.runNullKeyed .) .) .) . encodeKeyed++encodeKeyed :: (Binary payload, Keyed m) => KeyPair -> PublicKey -> Nonce -> payload -> m DhtPacket+encodeKeyed keyPair receiverPublicKey nonce =+ encryptKeyed keyPair receiverPublicKey nonce+ . PlainText+ . LazyByteString.toStrict+ . runPut+ . put+++decrypt :: KeyPair -> DhtPacket -> Maybe PlainText+decrypt = (Keyed.runNullKeyed .) . decryptKeyed++decryptKeyed :: Keyed m => KeyPair -> DhtPacket -> m (Maybe PlainText)+decryptKeyed (KeyPair receiverSecretKey _) DhtPacket { senderPublicKey, encryptionNonce, encryptedPayload } =+ (\combinedKey -> Box.decrypt combinedKey encryptionNonce encryptedPayload) <$>+ Keyed.getCombinedKey receiverSecretKey senderPublicKey+++decode :: Binary payload => KeyPair -> DhtPacket -> Maybe payload+decode = (Keyed.runNullKeyed .) . decodeKeyed++decodeKeyed :: (Binary payload, Keyed m) => KeyPair -> DhtPacket -> m (Maybe payload)+decodeKeyed keyPair packet = (>>= Box.decode) <$> decryptKeyed keyPair packet+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary DhtPacket where+ arbitrary =+ DhtPacket <$> arbitrary <*> arbitrary <*> arbitrary+\end{code}
+ src/Network/Tox/DHT/DhtRequestPacket.lhs view
@@ -0,0 +1,69 @@+\section{DHT Request Packets}+DHT Request packets are used to route encrypted data from a sender to another+node, referred to as the addressee of the packet, via a third node.++A DHT Request Packet is sent as the payload of a Protocol Packet with the+corresponding Packet Kind. It contains the DHT Public Key of an addressee, and a+DHT Packet which is to be received by the addressee.++\begin{tabular}{l|l|l}+ Length & Type & \href{#protocol-packet}{Contents} \\+ \hline+ \texttt{32} & Public Key & Addressee DHT Public Key \\+ \texttt{[72,]} & DHT Packet & DHT Packet \\+\end{tabular}++\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE Safe #-}+module Network.Tox.DHT.DhtRequestPacket where++import Control.Applicative ((<$>), (<*>))+import Data.Binary (Binary, get, put)+import Data.MessagePack (MessagePack)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)++import Network.Tox.Crypto.Key (PublicKey)+import Network.Tox.DHT.DhtPacket (DhtPacket)++import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)++++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}+++data DhtRequestPacket = DhtRequestPacket+ { addresseePublicKey :: PublicKey+ , dhtPacket :: DhtPacket+ }+ deriving (Eq, Read, Show, Generic, Typeable)++instance MessagePack DhtRequestPacket+++instance Binary DhtRequestPacket where+ put packet = do+ put $ addresseePublicKey packet+ put $ dhtPacket packet++ get =+ DhtRequestPacket <$> get <*> get++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary DhtRequestPacket where+ arbitrary =+ DhtRequestPacket <$> arbitrary <*> arbitrary+\end{code}
+ src/Network/Tox/DHT/DhtState.lhs view
@@ -0,0 +1,366 @@+\section{DHT node state}++\begin{code}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE Safe #-}+module Network.Tox.DHT.DhtState where++import Control.Applicative (Applicative, Const (..),+ getConst, (<$>), (<*>), (<|>))+import Data.Functor.Identity (Identity (..))+import Data.List (nub, sortBy)+import Data.Map (Map)+import qualified Data.Map as Map+import qualified Data.Maybe as Maybe+import Data.Monoid (All (..), Monoid, getAll)+import Data.Ord (comparing)+import Data.Traversable (traverse)+import Lens.Family2 (Lens')+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary, shrink)++import Network.Tox.Crypto.Key (PublicKey)+import Network.Tox.Crypto.KeyPair (KeyPair)+import qualified Network.Tox.Crypto.KeyPair as KeyPair+import Network.Tox.DHT.ClientList (ClientList)+import qualified Network.Tox.DHT.ClientList as ClientList+import Network.Tox.DHT.Distance (Distance)+import Network.Tox.DHT.KBuckets (KBuckets)+import qualified Network.Tox.DHT.KBuckets as KBuckets+import Network.Tox.DHT.NodeList (NodeList)+import qualified Network.Tox.DHT.NodeList as NodeList+import Network.Tox.DHT.PendingReplies (PendingReplies)+import qualified Network.Tox.DHT.Stamped as Stamped+import Network.Tox.NodeInfo.NodeInfo (NodeInfo)+import qualified Network.Tox.NodeInfo.NodeInfo as NodeInfo+import Network.Tox.Time (Timestamp)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}+++\end{code}++Every DHT node contains the following state:++\begin{itemize}+ \item DHT Key Pair: The Key Pair used to communicate with other DHT nodes. It+ is immutable throughout the lifetime of the DHT node.+ \item DHT Close List: A set of Node Infos of nodes that are close to the+ DHT Public Key (public part of the DHT Key Pair). The Close List is+ represented as a \href{#k-buckets}{k-buckets} data structure, with the DHT+ Public Key as the Base Key.+ \item DHT Search List: A list of Public Keys of nodes that the DHT node is+ searching for, associated with a DHT Search Entry.+\end{itemize}++\begin{code}++data ListStamp = ListStamp { listTime :: Timestamp, listBootstrappedTimes :: Int }+ deriving (Eq, Read, Show)+newListStamp :: Timestamp -> ListStamp+newListStamp t = ListStamp t 0++data DhtState = DhtState+ { dhtKeyPair :: KeyPair+ , dhtCloseList :: KBuckets+ , dhtSearchList :: Map PublicKey DhtSearchEntry++ , dhtCloseListStamp :: ListStamp+ , dhtPendingReplies :: PendingReplies+ }+ deriving (Eq, Read, Show)++_dhtKeyPair :: Lens' DhtState KeyPair+_dhtKeyPair f d@DhtState{ dhtKeyPair = a } =+ (\a' -> d{ dhtKeyPair = a' }) <$> f a++_dhtCloseListStamp :: Lens' DhtState ListStamp+_dhtCloseListStamp f d@DhtState{ dhtCloseListStamp = a } =+ (\a' -> d{ dhtCloseListStamp = a' }) <$> f a++_dhtCloseList :: Lens' DhtState KBuckets+_dhtCloseList f d@DhtState{ dhtCloseList = a } =+ (\a' -> d{ dhtCloseList = a' }) <$> f a++_dhtSearchList :: Lens' DhtState (Map PublicKey DhtSearchEntry)+_dhtSearchList f d@DhtState{ dhtSearchList = a } =+ (\a' -> d{ dhtSearchList = a' }) <$> f a++_dhtPendingReplies :: Lens' DhtState PendingReplies+_dhtPendingReplies f d@DhtState{ dhtPendingReplies = a } =+ (\a' -> d{ dhtPendingReplies = a' }) <$> f a++\end{code}++A DHT node state is initialised using a Key Pair, which is stored in the state+as DHT Key Pair and as base key for the Close List. Both the Close and Search+Lists are initialised to be empty.++\begin{code}++empty :: Timestamp -> KeyPair -> DhtState+empty time keyPair =+ DhtState keyPair (KBuckets.empty $ KeyPair.publicKey keyPair)+ Map.empty (newListStamp time) Stamped.empty++\end{code}++\subsection{DHT Search Entry}++A DHT Search Entry contains a Client List with base key the searched node's+Public Key. Once the searched node is found, it is also stored in the Search+Entry.++The maximum size of the Client List is set to 8.+(Must be the same or smaller than the bucket size of the close list to make+sure all the closest peers found will know the node being searched+(TODO(zugz): this argument is unclear.)).++A DHT node state therefore contains one Client List for each bucket index in+the Close List, and one Client List for each DHT Search Entry.+These lists are not required to be disjoint - a node may be in multiple Client+Lists simultaneously.++\begin{code}++data DhtSearchEntry = DhtSearchEntry+ { searchNode :: Maybe NodeInfo+ , searchStamp :: ListStamp+ , searchClientList :: ClientList+ }+ deriving (Eq, Read, Show)++_searchNode :: Lens' DhtSearchEntry (Maybe NodeInfo)+_searchNode f d@DhtSearchEntry{ searchNode = a } =+ (\a' -> d{ searchNode = a' }) <$> f a++_searchStamp :: Lens' DhtSearchEntry ListStamp+_searchStamp f d@DhtSearchEntry{ searchStamp = a } =+ (\a' -> d{ searchStamp = a' }) <$> f a++_searchClientList :: Lens' DhtSearchEntry ClientList+_searchClientList f d@DhtSearchEntry{ searchClientList = a } =+ (\a' -> d{ searchClientList = a' }) <$> f a++searchEntryClientListSize :: Int+searchEntryClientListSize = 8++\end{code}++A Search Entry is initialised with the searched-for Public Key. The contained+Client List is initialised to be empty.++\begin{code}++emptySearchEntry :: Timestamp -> PublicKey -> DhtSearchEntry+emptySearchEntry time publicKey =+ DhtSearchEntry Nothing (newListStamp time) $+ ClientList.empty publicKey searchEntryClientListSize++\end{code}++\subsection{Manipulating the DHT node state}++Adding a search key to the DHT node state creates an empty entry in the Search+Nodes list. If a search entry for the public key already existed, the "add"+operation has no effect.++\begin{code}++addSearchKey :: Timestamp -> PublicKey -> DhtState -> DhtState+addSearchKey time searchKey dhtState@DhtState { dhtSearchList } =+ dhtState { dhtSearchList = updatedSearchList }+ where+ searchEntry =+ Map.findWithDefault (emptySearchEntry time searchKey) searchKey dhtSearchList+ updatedSearchList =+ Map.insert searchKey searchEntry dhtSearchList++\end{code}++Removing a search key removes its search entry and all associated data+structures from memory.++\begin{code}++removeSearchKey :: PublicKey -> DhtState -> DhtState+removeSearchKey searchKey dhtState@DhtState { dhtSearchList } =+ dhtState { dhtSearchList = Map.delete searchKey dhtSearchList }+++containsSearchKey :: PublicKey -> DhtState -> Bool+containsSearchKey searchKey =+ Map.member searchKey . dhtSearchList++\end{code}++\input{src/Network/Tox/DHT/NodeList.lhs}++The iteration order over the DHT state is to first process the Close List+k-buckets, then the Search List entry Client Lists. Each of these follows the+iteration order in the corresponding specification.++\begin{code}++traverseNodeLists :: Applicative f =>+ (forall l. NodeList l => l -> f l) -> DhtState -> f DhtState+traverseNodeLists f dhtState@DhtState{ dhtCloseList, dhtSearchList } =+ (\close' search' ->+ dhtState{ dhtCloseList = close', dhtSearchList = search' }) <$>+ f dhtCloseList <*>+ traverse traverseEntry dhtSearchList+ where+ traverseEntry entry =+ (\x -> entry{ searchClientList = x }) <$> f (searchClientList entry)++foldMapNodeLists :: Monoid m =>+ (forall l. NodeList l => l -> m) -> DhtState -> m+foldMapNodeLists f = getConst . traverseNodeLists (Const . f)++mapNodeLists :: (forall l. NodeList l => l -> l) -> DhtState -> DhtState+mapNodeLists f = runIdentity . traverseNodeLists (Identity . f)++\end{code}++A node info is considered to be contained in the DHT State if it is contained+in the Close List or in at least one of the Search Entries.++The size of the DHT state is defined to be the number of node infos it+contains, counted with multiplicity: node infos contained multiple times, e.g.+in the close list and in various search entries, are counted as many times as+they appear. Search keys do not directly count towards the state size. So+the size of the state is the sum of the sizes of the Close List and the Search+Entries.++The state size is relevant to later pruning algorithms that decide when to+remove a node info and when to request a ping from stale nodes. Search keys,+once added, are never automatically pruned.++\begin{code}++size :: DhtState -> Int+size = NodeList.foldNodes (flip $ const (1 +)) 0++\end{code}++Adding a Node Info to the state is done by adding the node to each Node List+in the state.++When adding a node info to the state, the search entry for the node's public+key, if it exists, is updated to contain the new node info. All k-buckets and+Client Lists that already contain the node info will also be updated. See the+corresponding specifications for the update algorithms. However, a node info+will not be added to a search entry when it is the node to which the search+entry is associated (i.e. the node being search for).++\begin{code}++addNode :: Timestamp -> NodeInfo -> DhtState -> DhtState+addNode time nodeInfo =+ updateSearchNode (NodeInfo.publicKey nodeInfo) (Just nodeInfo)+ . mapNodeLists addUnlessBase+ where+ addUnlessBase nodeList+ | NodeInfo.publicKey nodeInfo == NodeList.baseKey nodeList = nodeList+ addUnlessBase nodeList = NodeList.addNode time nodeInfo nodeList++removeNode :: PublicKey -> DhtState -> DhtState+removeNode publicKey =+ updateSearchNode publicKey Nothing+ . mapNodeLists (NodeList.removeNode publicKey)++viable :: NodeInfo -> DhtState -> Bool+viable nodeInfo = getAll . foldMapNodeLists (All . NodeList.viable nodeInfo)++traverseClientLists ::+ Applicative f => (ClientList -> f ClientList) -> DhtState -> f DhtState+traverseClientLists f = traverseNodeLists $ NodeList.traverseClientLists f++closeNodes :: PublicKey -> DhtState -> [ (Distance, NodeInfo) ]+closeNodes publicKey =+ nub . sortBy (comparing fst) . foldMapNodeLists (NodeList.closeNodes publicKey)++-- | although it is not referred to as a Node List in the spec, we make DhtState+-- an instance of NodeList so we can use the traversal and folding functions.+instance NodeList DhtState where+ addNode = addNode+ removeNode = removeNode+ viable = viable+ baseKey = KeyPair.publicKey . dhtKeyPair+ traverseClientLists = traverseClientLists+ closeNodes = closeNodes++takeClosestNodesTo :: Int -> PublicKey -> DhtState -> [ NodeInfo ]+takeClosestNodesTo n publicKey = map snd . take n . closeNodes publicKey++mapBuckets :: (KBuckets -> KBuckets) -> DhtState -> DhtState+mapBuckets f dhtState@DhtState { dhtCloseList } =+ dhtState+ { dhtCloseList = f dhtCloseList+ }++mapSearchEntry :: (DhtSearchEntry -> DhtSearchEntry) -> DhtState -> DhtState+mapSearchEntry f dhtState@DhtState { dhtSearchList } =+ dhtState+ { dhtSearchList = Map.map f dhtSearchList+ }++mapSearchClientLists :: (ClientList -> ClientList) -> DhtState -> DhtState+mapSearchClientLists f =+ mapSearchEntry $ \entry@DhtSearchEntry{ searchClientList } ->+ entry { searchClientList = f searchClientList }++updateSearchNode :: PublicKey -> Maybe NodeInfo -> DhtState -> DhtState+updateSearchNode publicKey nodeInfo dhtState@DhtState { dhtSearchList } =+ dhtState+ { dhtSearchList = Map.adjust update publicKey dhtSearchList+ }+ where+ update entry = entry { searchNode = nodeInfo }++\end{code}++Removing a node info from the state removes it from all k-buckets. If a search+entry for the removed node's public key existed, the node info in that search+entry is unset. The search entry itself is not removed.++\begin{code}++containsNode :: PublicKey -> DhtState -> Bool+containsNode publicKey =+ NodeList.foldNodes (\a x -> a || NodeInfo.publicKey x == publicKey) False+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary DhtState where+ arbitrary =+ initialise <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary+ where+ initialise :: Timestamp -> KeyPair -> [(Timestamp, NodeInfo)] -> [(Timestamp, PublicKey)] -> DhtState+ initialise time kp nis =+ foldl (flip $ uncurry addSearchKey) (foldl (flip $ uncurry NodeList.addNode) (empty time kp) nis)++ shrink dhtState =+ Maybe.maybeToList shrunkNode ++ Maybe.maybeToList shrunkSearchKey+ where+ -- Remove the first node we can find in the state.+ shrunkNode = do+ firstPK <- NodeInfo.publicKey <$> NodeList.foldNodes (\a x -> a <|> Just x) Nothing dhtState+ return $ NodeList.removeNode firstPK dhtState++ shrunkSearchKey = Nothing++\end{code}
+ src/Network/Tox/DHT/Distance.lhs view
@@ -0,0 +1,103 @@+\section{Distance}++\begin{code}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.Distance where++import Control.Applicative ((<$>))+import Control.Arrow (first)+import Data.Bits (xor)+import Data.Monoid (Monoid, mappend, mempty)+import Data.Semigroup (Semigroup, (<>))+import GHC.Exts (Int (I#))+import GHC.Integer.Logarithms (integerLog2#)+import Network.Tox.Crypto.Key (PublicKey)+import qualified Network.Tox.Crypto.Key as Key (keyToInteger)+import Numeric (readHex, showHex)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}++\end{code}++A Distance is a positive integer. Its human-readable representation is a+base-16 number. Distance (type) is an+\href{https://en.wikipedia.org/wiki/Ordered_semigroup}{ordered monoid} with the+associative binary operator \texttt{+} and the identity element \texttt{0}.++\begin{code}++newtype Distance = Distance Integer+ deriving (Eq, Ord)+++instance Semigroup Distance where+ (Distance x) <> (Distance y) = Distance (x + y)++instance Monoid Distance where+ mempty = Distance 0+ mappend (Distance x) (Distance y) = Distance (x + y)+++instance Show Distance where+ show (Distance distance) = showHex distance ""++instance Read Distance where+ readsPrec _ string = map (first Distance) $ readHex string+++log2 :: Distance -> Maybe Int+log2 (Distance 0) = Nothing+log2 (Distance x) = Just $ I# (integerLog2# x)+++\end{code}++The DHT uses a+\href{https://en.wikipedia.org/wiki/Metric_(mathematics)}{metric} to determine+the distance between two nodes. The Distance type is the co-domain of this+metric. The metric currently used by the Tox DHT is the \texttt{XOR} of the+nodes' public keys: \texttt{distance(x, y) = x XOR y}. For this computation,+public keys are interpreted as Big Endian integers (see \href{#key-1}{Crypto+Numbers}).++When we speak of a "close node", we mean that its Distance to the node under+consideration is small compared to the Distance to other nodes.++\begin{code}++xorDistance :: PublicKey -> PublicKey -> Distance+xorDistance a b =+ Distance $ Key.keyToInteger a `xor` Key.keyToInteger b++-- | rebaseDistance a b (xorDistance a c) == xorDistance b c+rebaseDistance :: PublicKey -> PublicKey -> Distance -> Distance+rebaseDistance a b (Distance d) =+ Distance $ d `xor` Key.keyToInteger a `xor` Key.keyToInteger b++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary Distance where+ arbitrary = Distance . abs <$> arbitrary+\end{code}++An implementation is not required to provide a Distance type, so it has no+specified binary representation. For example, instead of computing a distance+and comparing it against another distance, the implementation can choose to+implement Distance as a pair of public keys and define an ordering on Distance+without computing the complete integral value. This works, because as soon as+an ordering decision can be made in the most significant bits, further bits+won't influence that decision.++\input{test/Network/Tox/DHT/DistanceSpec.lhs}
+ src/Network/Tox/DHT/KBuckets.lhs view
@@ -0,0 +1,235 @@+\section{K-buckets}++K-buckets is a data structure for efficiently storing a set of nodes close to a+certain key called the base key. The base key is constant throughout the+lifetime of a k-buckets instance.++\begin{code}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.KBuckets where++import Control.Applicative (Applicative, (<$>))+import Data.Binary (Binary)+import Data.Foldable (toList)+import Data.List (sortBy)+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Maybe (isJust)+import Data.Ord (comparing)+import Data.Traversable (Traversable, mapAccumR,+ traverse)+import Data.Word (Word8)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)+import Test.QuickCheck.Gen (Gen)+import qualified Test.QuickCheck.Gen as Gen++import Network.Tox.Crypto.Key (PublicKey)+import Network.Tox.DHT.ClientList (ClientList)+import qualified Network.Tox.DHT.ClientList as ClientList+import Network.Tox.DHT.Distance (Distance)+import qualified Network.Tox.DHT.Distance as Distance+import Network.Tox.NodeInfo.NodeInfo (NodeInfo)+import qualified Network.Tox.NodeInfo.NodeInfo as NodeInfo+import Network.Tox.Time (Timestamp)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}++\end{code}++A k-buckets is a map from small integers \texttt{0 <= n < 256} to Client Lists+of maximum size $k$. Each Client List is called a (k-)bucket. A k-buckets is+equipped with a base key, and each bucket has this key as its base key.+\texttt{k} is called the bucket size. The default bucket size is 8.+A large bucket size was chosen to increase the speed at which peers are found. ++\begin{code}++data KBuckets = KBuckets+ { bucketSize :: Int+ , buckets :: Map KBucketIndex ClientList+ , baseKey :: PublicKey+ }+ deriving (Eq, Read, Show)+++defaultBucketSize :: Int+defaultBucketSize = 8+++empty :: PublicKey -> KBuckets+empty = KBuckets defaultBucketSize Map.empty++\end{code}++The above number \texttt{n} is the bucket index. It is a non-negative integer+with the range \texttt{[0, 255]}, i.e. the range of an 8 bit unsigned integer.++\begin{code}+++newtype KBucketIndex = KBucketIndex Word8+ deriving (Eq, Ord, Read, Show, Num, Binary, Enum)+++\end{code}++\subsection{Bucket Index}++The index of the bucket can be computed using the following function:+\texttt{bucketIndex(baseKey, nodeKey) = 255 - log\_2(distance(baseKey,+nodeKey))}. This function is not defined when \texttt{baseKey == nodeKey},+meaning k-buckets will never contain a Node Info about the base node.++Thus, each k-bucket contains only Node Infos for whose keys the following+holds: if node with key \texttt{nodeKey} is in k-bucket with index \texttt{n},+then \texttt{bucketIndex(baseKey, nodeKey) == n}. Thus, n'th k-bucket consists+of nodes for which distance to the base node lies in range+\verb![2^n, 2^(n+1) - 1]!.++The bucket index can be efficiently computed by determining the first bit at+which the two keys differ, starting from the most significant bit. So, if the+local DHT key starts with e.g. \texttt{0x80} and the bucketed node key starts+with \texttt{0x40}, then the bucket index for that node is 0. If the second+bit differs, the bucket index is 1. If the keys are almost exactly equal and+only the last bit differs, the bucket index is 255.++\begin{code}+++bucketIndex :: PublicKey -> PublicKey -> Maybe KBucketIndex+bucketIndex pk1 pk2 =+ fmap (\index -> 255 - fromIntegral index) $ Distance.log2 $ Distance.xorDistance pk1 pk2+++\end{code}++\subsection{Manipulating k-buckets}++TODO: this is different from kademlia's least-recently-seen eviction policy; why+the existing solution was chosen, how does it affect security, performance and+resistance to poisoning? original paper claims that preference of old live nodes+results in better persistence and resistance to basic DDoS attacks;++Any update or lookup operation on a k-buckets instance that involves a single+node requires us to first compute the bucket index for that node. An update+involving a Node Info with \texttt{nodeKey == baseKey} has no effect. If the+update results in an empty bucket, that bucket is removed from the map.++\begin{code}+++updateBucketForKey :: KBuckets -> PublicKey -> (ClientList -> ClientList) -> KBuckets+updateBucketForKey kBuckets key f =+ case bucketIndex (baseKey kBuckets) key of+ Nothing -> kBuckets+ Just index -> updateBucketForIndex kBuckets index f+++updateBucketForIndex :: KBuckets -> KBucketIndex -> (ClientList -> ClientList) -> KBuckets+updateBucketForIndex kBuckets@KBuckets { buckets, baseKey, bucketSize } index f =+ let+ -- Find the old bucket or create a new empty one.+ updatedBucket = f $ Map.findWithDefault (ClientList.empty baseKey bucketSize) index buckets+ -- Replace old bucket with updated bucket or delete if empty.+ updatedBuckets =+ if ClientList.isEmpty updatedBucket+ then Map.delete index buckets+ else Map.insert index updatedBucket buckets+ in+ kBuckets { buckets = updatedBuckets }+++\end{code}++Adding a node to, or removing a node from, a k-buckets consists of performing+the corresponding operation on the Client List bucket whose index is that of+the node's public key, except that adding a new node to a full bucket has no+effect. A node is considered \textit{viable} for entry if the corresponding+bucket is not full.++\begin{code}++addNode :: Timestamp -> NodeInfo -> KBuckets -> KBuckets+addNode time nodeInfo kBuckets =+ updateBucketForKey kBuckets publicKey $ \clientList ->+ let+ full = ClientList.full clientList+ alreadyIn = isJust $ ClientList.lookup publicKey clientList+ in+ if not full || alreadyIn+ then ClientList.addNode time nodeInfo clientList+ else clientList+ where+ publicKey = NodeInfo.publicKey nodeInfo++removeNode :: PublicKey -> KBuckets -> KBuckets+removeNode publicKey kBuckets =+ updateBucketForKey kBuckets publicKey $ ClientList.removeNode publicKey++viable :: NodeInfo -> KBuckets -> Bool+viable nodeInfo KBuckets{ baseKey, buckets } =+ case bucketIndex baseKey $ NodeInfo.publicKey nodeInfo of+ Nothing -> False+ Just index -> case Map.lookup index buckets of+ Nothing -> True+ Just bucket -> not $ ClientList.full bucket++\end{code}++Iteration order of a k-buckets instance is in order of distance from the base+key. I.e. the first node seen in iteration is the closest, and the last node+is the furthest away in terms of the distance metric.++\begin{code}++traverseClientLists ::+ Applicative f => (ClientList -> f ClientList) -> KBuckets -> f KBuckets+traverseClientLists f kBuckets@KBuckets{ buckets } =+ (\x -> kBuckets{ buckets = x }) <$> traverse f (reverseT buckets)+ where+ reverseT :: (Traversable t) => t a -> t a+ reverseT t = snd (mapAccumR (\ (x:xs) _ -> (xs, x)) (toList t) t)++closeNodes :: PublicKey -> KBuckets -> [ (Distance, NodeInfo) ]+closeNodes publicKey KBuckets{ baseKey, buckets } =+ let+ (further, at, nearer) = case bucketIndex baseKey publicKey of+ Nothing -> (buckets, Nothing, Map.empty)+ Just index -> Map.splitLookup index buckets+ clientClose = ClientList.closeNodes publicKey+ bucketsClose = sortBy (comparing fst) . concatMap clientClose+ in+ concat+ [ maybe [] clientClose at+ , bucketsClose $ Map.elems nearer+ , bucketsClose $ Map.elems further+ ]+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++getAllNodes :: KBuckets -> [NodeInfo]+getAllNodes =+ concatMap ClientList.nodeInfos . Map.elems . buckets+++genKBuckets :: PublicKey -> Gen KBuckets+genKBuckets publicKey =+ foldl (flip $ uncurry addNode) (empty publicKey) <$> Gen.listOf arbitrary+++instance Arbitrary KBuckets where+ arbitrary = arbitrary >>= genKBuckets+\end{code}
+ src/Network/Tox/DHT/NodeList.lhs view
@@ -0,0 +1,75 @@+The Close List and the Search Entries are termed the \texttt{Node Lists} of+the DHT State.++\begin{code}+module Network.Tox.DHT.NodeList where++import Control.Applicative (Applicative, Const (..),+ getConst)+import Control.Monad (guard)+import Data.Maybe (listToMaybe)+import Data.Monoid (Dual (..), Endo (..), Monoid,+ appEndo, getDual, mempty)++import Network.Tox.Crypto.Key (PublicKey)+import Network.Tox.DHT.ClientList (ClientList)+import qualified Network.Tox.DHT.ClientList as ClientList+import Network.Tox.DHT.Distance (Distance)+import Network.Tox.DHT.KBuckets (KBuckets)+import qualified Network.Tox.DHT.KBuckets as KBuckets+import Network.Tox.NodeInfo.NodeInfo (NodeInfo)+import Network.Tox.Time (Timestamp)++class NodeList l where+ addNode :: Timestamp -> NodeInfo -> l -> l++ removeNode :: PublicKey -> l -> l++ viable :: NodeInfo -> l -> Bool++ baseKey :: l -> PublicKey++ traverseClientLists ::+ Applicative f => (ClientList -> f ClientList) -> l -> f l++ -- | 'closeNodes pub' returns the (pub',node) pairs of the Node List in+ -- increasing order of distance of pub' from pub.+ closeNodes :: PublicKey -> l -> [(Distance, NodeInfo)]++ -- | copied from Data.Traversable.foldMapDefault+ foldMapClientLists :: Monoid m => (ClientList -> m) -> l -> m+ foldMapClientLists f = getConst . traverseClientLists (Const . f)++ -- | copied from Data.Foldable.foldl+ foldlClientLists :: (a -> ClientList -> a) -> a -> l -> a+ foldlClientLists f z t =+ appEndo (getDual (foldMapClientLists (Dual . Endo . flip f) t)) z++ nodeListList :: l -> [NodeInfo]+ nodeListList = foldMapClientLists ClientList.nodeInfos++ foldNodes :: (a -> NodeInfo -> a) -> a -> l -> a+ foldNodes = foldlClientLists . ClientList.foldNodes++ lookupPublicKey :: PublicKey -> l -> Maybe NodeInfo+ lookupPublicKey publicKey list = do+ (dist,node) <- listToMaybe $ closeNodes publicKey list+ guard (dist == mempty)+ Just node++instance NodeList ClientList where+ addNode = ClientList.addNode+ removeNode = ClientList.removeNode+ viable = ClientList.viable+ baseKey = ClientList.baseKey+ traverseClientLists = id+ closeNodes = ClientList.closeNodes++instance NodeList KBuckets where+ addNode = KBuckets.addNode+ removeNode = KBuckets.removeNode+ viable = KBuckets.viable+ baseKey = KBuckets.baseKey+ traverseClientLists = KBuckets.traverseClientLists+ closeNodes = KBuckets.closeNodes+\end{code}
+ src/Network/Tox/DHT/NodesRequest.lhs view
@@ -0,0 +1,51 @@+\subsubsection{Nodes Request (0x02)}++\begin{tabular}{l|l|l}+ Length & Type & \href{#rpc-services}{Contents} \\+ \hline+ \texttt{32} & Public Key & Requested DHT Public Key \\+\end{tabular}++The DHT Public Key sent in the request is the one the sender is searching for.++\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE Safe #-}+module Network.Tox.DHT.NodesRequest where++import Control.Applicative ((<$>))+import Data.Binary (Binary)+import Data.MessagePack (MessagePack)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Network.Tox.Crypto.Key (PublicKey)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}+++newtype NodesRequest = NodesRequest+ { requestedKey :: PublicKey+ }+ deriving (Eq, Read, Show, Generic, Typeable)++instance Binary NodesRequest+instance MessagePack NodesRequest+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary NodesRequest where+ arbitrary = NodesRequest <$> arbitrary+\end{code}
+ src/Network/Tox/DHT/NodesResponse.lhs view
@@ -0,0 +1,67 @@+\subsubsection{Nodes Response (0x04)}++\begin{tabular}{l|l|l}+ Length & Type & \href{#rpc-services}{Contents} \\+ \hline+ \texttt{1} & Int & Number of nodes in the response (maximum 4) \\+ \texttt{[39, 204]} & Node Infos & Nodes in Packed Node Format \\+\end{tabular}++An IPv4 node is 39 bytes, an IPv6 node is 51 bytes, so the maximum size of the+packed Node Infos is \texttt{51 * 4 = 204} bytes.++Nodes responses should contain the 4 closest nodes that the sender of the+response has in their lists of known nodes.++\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE Safe #-}+module Network.Tox.DHT.NodesResponse where++import Control.Applicative ((<$>))+import Data.Binary (Binary, get, put)+import qualified Data.Binary.Get as Binary (getWord8)+import qualified Data.Binary.Put as Binary (putWord8)+import Data.MessagePack (MessagePack)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Network.Tox.NodeInfo.NodeInfo (NodeInfo)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}+++newtype NodesResponse = NodesResponse+ { foundNodes :: [NodeInfo]+ }+ deriving (Eq, Read, Show, Generic, Typeable)++instance MessagePack NodesResponse+++instance Binary NodesResponse where+ put res = do+ Binary.putWord8 . fromInteger . toInteger . length . foundNodes $ res+ mapM_ put (foundNodes res)++ get = do+ count <- Binary.getWord8+ NodesResponse <$> mapM (const get) [1..count]+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary NodesResponse where+ arbitrary = NodesResponse <$> arbitrary+\end{code}
+ src/Network/Tox/DHT/Operation.lhs view
@@ -0,0 +1,589 @@+\section{DHT Operation}++\begin{code}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE Safe #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Network.Tox.DHT.Operation where++import Control.Applicative (Applicative, pure, (<$>),+ (<*>))+import Control.Monad (guard, msum, replicateM,+ unless, void, when)+import Control.Monad.Identity (Identity, runIdentity)+import Control.Monad.Random (RandT, evalRandT)+import Control.Monad.State (MonadState, StateT,+ execStateT, get, gets,+ modify, put, runStateT)+import Control.Monad.Trans (lift)+import Control.Monad.Trans.Maybe (MaybeT (..), runMaybeT)+import Control.Monad.Writer (MonadWriter, WriterT,+ execWriterT, tell)+import Data.Binary (Binary)+import Data.Foldable (for_)+import Data.Functor (($>))+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Maybe (isNothing)+import Data.Traversable (traverse)+import Lens.Family2 (Lens')+import Lens.Family2.State (zoom, (%%=), (%=))+import System.Random (StdGen, mkStdGen)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)++import Network.Tox.Crypto.Key (PublicKey)+import Network.Tox.Crypto.Keyed (Keyed)+import Network.Tox.Crypto.KeyedT (KeyedT)+import qualified Network.Tox.Crypto.KeyedT as KeyedT+import qualified Network.Tox.Crypto.KeyPair as KeyPair+import Network.Tox.DHT.ClientList (ClientList)+import qualified Network.Tox.DHT.ClientList as ClientList+import Network.Tox.DHT.ClientNode (ClientNode)+import qualified Network.Tox.DHT.ClientNode as ClientNode+import qualified Network.Tox.DHT.DhtPacket as DhtPacket+import Network.Tox.DHT.DhtRequestPacket (DhtRequestPacket (..))+import Network.Tox.DHT.DhtState (DhtState)+import qualified Network.Tox.DHT.DhtState as DhtState+import Network.Tox.DHT.NodeList (NodeList)+import qualified Network.Tox.DHT.NodeList as NodeList+import Network.Tox.DHT.NodesRequest (NodesRequest (..))+import Network.Tox.DHT.NodesResponse (NodesResponse (..))+import qualified Network.Tox.DHT.PendingReplies as PendingReplies+import Network.Tox.DHT.PingPacket (PingPacket (..))+import Network.Tox.DHT.RpcPacket (RpcPacket (..))+import qualified Network.Tox.DHT.RpcPacket as RpcPacket+import qualified Network.Tox.DHT.Stamped as Stamped+import Network.Tox.Network.MonadRandomBytes (MonadRandomBytes)+import qualified Network.Tox.Network.MonadRandomBytes as MonadRandomBytes+import Network.Tox.Network.Networked (Networked)+import qualified Network.Tox.Network.Networked as Networked+import Network.Tox.NodeInfo.NodeInfo (NodeInfo)+import qualified Network.Tox.NodeInfo.NodeInfo as NodeInfo+import Network.Tox.Protocol.Packet (Packet (..))+import Network.Tox.Protocol.PacketKind (PacketKind)+import qualified Network.Tox.Protocol.PacketKind as PacketKind+import Network.Tox.Time (TimeDiff, Timestamp)+import qualified Network.Tox.Time as Time+import Network.Tox.Timed (Timed)+import qualified Network.Tox.Timed as Timed+import Network.Tox.TimedT (TimedT)+import qualified Network.Tox.TimedT as TimedT+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}++class+ ( Networked m+ , Timed m+ , MonadRandomBytes m+ , MonadState DhtState m+ , Keyed m+ ) => DhtNodeMonad m where {}++data RequestInfo = RequestInfo+ { requestTo :: NodeInfo+ , requestSearch :: PublicKey+ }+ deriving (Eq, Read, Show)++sendDhtPacket :: (DhtNodeMonad m, Binary payload) =>+ NodeInfo -> PacketKind -> payload -> m ()+sendDhtPacket to kind payload = do+ keyPair <- gets DhtState.dhtKeyPair+ nonce <- MonadRandomBytes.randomNonce+ Networked.sendPacket to . Packet kind =<<+ DhtPacket.encodeKeyed keyPair (NodeInfo.publicKey to) nonce payload++sendRpcRequest :: (DhtNodeMonad m, Binary payload) =>+ NodeInfo -> PacketKind -> payload -> m ()+sendRpcRequest to packetKind payload = do+ requestId <- RpcPacket.RequestId <$> MonadRandomBytes.randomWord64+ time <- Timed.askTime+ DhtState._dhtPendingReplies %= PendingReplies.expectReply time to requestId+ sendDhtPacket to packetKind $+ RpcPacket payload requestId++sendNodesRequest :: DhtNodeMonad m => RequestInfo -> m ()+sendNodesRequest (RequestInfo to key) =+ sendRpcRequest to PacketKind.NodesRequest $ NodesRequest key++sendNodesResponse ::+ DhtNodeMonad m => NodeInfo -> RpcPacket.RequestId -> [NodeInfo] -> m ()+sendNodesResponse to requestId nodes =+ sendDhtPacket to PacketKind.NodesResponse $+ RpcPacket (NodesResponse nodes) requestId++sendPingRequest :: DhtNodeMonad m => NodeInfo -> m ()+sendPingRequest to =+ sendRpcRequest to PacketKind.PingRequest PingRequest++sendPingResponse ::+ DhtNodeMonad m => NodeInfo -> RpcPacket.RequestId -> m ()+sendPingResponse to requestId =+ sendDhtPacket to PacketKind.PingResponse $+ RpcPacket PingResponse requestId++modifyM :: MonadState s m => (s -> m s) -> m ()+modifyM = (put =<<) . (get >>=)++-- | adapted from michaelt's lens-simple:+-- zoom_ is like zoom but for convenience returns an mtl style+-- abstracted MonadState state, rather than a concrete StateT, recapturing+-- a bit more of the abstractness of Control.Lens.zoom+zoom_ :: MonadState s' m => Lens' s' s -> StateT s m a -> m a+-- full signature:+-- zoom_ :: MonadState s' m =>+-- LensLike' (Zooming m a) s' s -> StateT s m a -> m a+zoom_ l f = abstract $ zoom l f+ where+ abstract :: MonadState s m => StateT s m a -> m a+ abstract st = do+ (a,s') <- runStateT st =<< get+ put s'+ return a++\end{code}++\subsection{DHT Initialisation}+A new DHT node is initialised with a DHT State with a fresh random key pair, an+empty close list, and a search list containing 2 empty search entries searching+for the public keys of fresh random key pairs.++\begin{code}++initRandomSearches :: Int+initRandomSearches = 2++initDht :: (MonadRandomBytes m, Timed m) => m DhtState+initDht = do+ dhtState <- DhtState.empty <$> Timed.askTime <*> MonadRandomBytes.newKeyPair+ time <- Timed.askTime+ (`execStateT` dhtState) $ replicateM initRandomSearches $ do+ publicKey <- KeyPair.publicKey <$> MonadRandomBytes.newKeyPair+ DhtState._dhtSearchList %=+ Map.insert publicKey (DhtState.emptySearchEntry time publicKey)++bootstrapNode :: DhtNodeMonad m => NodeInfo -> m ()+bootstrapNode nodeInfo =+ sendNodesRequest . RequestInfo nodeInfo =<<+ KeyPair.publicKey <$> gets DhtState.dhtKeyPair++-- TODO+--loadDHT :: ??++\end{code}++\subsection{Periodic sending of Nodes Requests}+For each Nodes List in the DHT State, every 20 seconds we send a Nodes Request+to a random node on the list, searching for the base key of the list.++When a Nodes List first becomes populated with nodes, we send 5 such random+Nodes Requests in quick succession.++Random nodes are chosen since being able to predict which node a node will+send a request to next could make some attacks that disrupt the network+easier, as it adds a possible attack vector.++\begin{code}++randomRequestPeriod :: TimeDiff+randomRequestPeriod = Time.seconds 20++maxBootstrapTimes :: Int+maxBootstrapTimes = 5++randomRequests :: DhtNodeMonad m => WriterT [RequestInfo] m ()+randomRequests = do+ closeList <- gets DhtState.dhtCloseList+ zoom_ DhtState._dhtCloseListStamp $ doList closeList+ zoom_ DhtState._dhtSearchList .+ modifyM . traverse . execStateT $ do+ searchList <- gets DhtState.searchClientList+ zoom_ DhtState._searchStamp $ doList searchList+ where+ doList ::+ ( NodeList l+ , Timed m+ , MonadRandomBytes m+ , MonadState DhtState.ListStamp m+ , MonadWriter [RequestInfo] m+ ) => l -> m ()+ doList nodeList =+ case NodeList.nodeListList nodeList of+ [] -> return ()+ nodes -> do+ time <- Timed.askTime+ DhtState.ListStamp lastTime bootstrapped <- get+ when (time Time.- lastTime >= randomRequestPeriod+ || bootstrapped < maxBootstrapTimes) $ do+ node <- MonadRandomBytes.uniform nodes+ tell [RequestInfo node $ NodeList.baseKey nodeList]+ put $ DhtState.ListStamp time (bootstrapped + 1)++\end{code}++Furthermore, we periodically check every node for responsiveness by sending it a+Nodes Request: for each Nodes List in the DHT State, we send each node on the+list a Nodes Request every 60 seconds, searching for the base key of the list.+We remove from the DHT State any node from which we persistently fail to receive+Nodes Responses.++c-toxcore's implementation of checking and timeouts:+A Last Checked time is maintained for each node in each list. When a node is+added to a list, if doing so evicts a node from the list then the Last Checked+time is set to that of the evicted node, and otherwise it is set to 0. This+includes updating an already present node. Nodes from which we have not+received a Nodes Response for 122 seconds are considered Bad; they remain in the+DHT State, but are preferentially overwritten when adding to the DHT State, and+are ignored for all operations except the once-per-60s checking described above.+If we have not received a Nodes Response for 182 seconds, the node is not even+checked. So one check is sent after the node becomes Bad. In the special case+that every node in the Close List is Bad, they are all checked once more.)++hs-toxcore implementation of checking and timeouts:+We maintain a Last Checked timestamp and a Checks Counter on each node on each+Nodes List in the Dht State. When a node is added to a list, these are set+respectively to the current time and to 0. This includes updating an already+present node. We periodically pass through the nodes on the lists, and for each+which is due a check, we: check it, update the timestamp, increment the counter,+and, if the counter is then 2, remove the node from the list. This is pretty+close to the behaviour of c-toxcore, but much simpler. TODO: currently hs-toxcore+doesn't do anything to try to recover if the Close List becomes empty. We could+maintain a separate list of the most recently heard from nodes, and repopulate+the Close List with that if the Close List becomes empty.++\begin{code}++checkPeriod :: TimeDiff+checkPeriod = Time.seconds 60++maxChecks :: Int+maxChecks = 2++checkNodes :: forall m. DhtNodeMonad m => WriterT [RequestInfo] m ()+checkNodes = modifyM $ DhtState.traverseClientLists checkNodes'+ where+ checkNodes' :: ClientList -> WriterT [RequestInfo] m ClientList+ checkNodes' clientList =+ (\x -> clientList{ ClientList.nodes = x }) <$>+ traverseMaybe checkNode (ClientList.nodes clientList)+ where+ traverseMaybe :: Applicative f =>+ (a -> f (Maybe b)) -> Map k a -> f (Map k b)+ traverseMaybe f = (Map.mapMaybe id <$>) . traverse f++ checkNode :: ClientNode -> WriterT [RequestInfo] m (Maybe ClientNode)+ checkNode clientNode = Timed.askTime >>= \time ->+ if time Time.- lastCheck < checkPeriod+ then pure $ Just clientNode+ else (tell [requestInfo] $>) $+ if checkCount + 1 < maxChecks+ then Just $ clientNode+ { ClientNode.lastCheck = time+ , ClientNode.checkCount = checkCount + 1+ }+ else Nothing+ where+ nodeInfo = ClientNode.nodeInfo clientNode+ lastCheck = ClientNode.lastCheck clientNode+ checkCount = ClientNode.checkCount clientNode+ requestInfo = RequestInfo nodeInfo $ NodeList.baseKey clientList++doDHT :: DhtNodeMonad m => m ()+doDHT =+ execWriterT (randomRequests >> checkNodes) >>= mapM_ sendNodesRequest+++\end{code}++\subsection{Handling Nodes Response packets}+When we receive a valid Nodes Response packet, we first check that it is a reply+to a Nodes Request which we sent within the last 60 seconds to the node from+which we received the response, and that no previous reply has been received. If+this check fails, the packet is ignored. If the check succeeds, first we add to+the DHT State the node from which the response was sent. Then, for each node+listed in the response and for each Nodes List in the DHT State which does not+currently contain the node and to which the node is viable for entry, we send a+Nodes Request to the node with the requested public key being the base key of+the Nodes List.++An implementation may choose not to send every such Nodes Request.+(c-toxcore only sends so many per list (8 for the Close List, 4 for a Search+Entry) per 50ms, prioritising the closest to the base key).++\begin{code}++requireNodesResponseWithin :: TimeDiff+requireNodesResponseWithin = Time.seconds 60++handleNodesResponse ::+ DhtNodeMonad m => NodeInfo -> RpcPacket NodesResponse -> m ()+handleNodesResponse from (RpcPacket (NodesResponse nodes) requestId) = do+ isReply <- checkPending requireNodesResponseWithin from requestId+ when isReply $ do+ time <- Timed.askTime+ modify $ DhtState.addNode time from+ for_ nodes $ \node ->+ (>>= mapM_ sendNodesRequest) $ (<$> get) $ DhtState.foldMapNodeLists $+ \nodeList ->+ guard (isNothing (NodeList.lookupPublicKey+ (NodeInfo.publicKey node) nodeList)+ && NodeList.viable node nodeList) >>+ [ RequestInfo node $ NodeList.baseKey nodeList ]++\end{code}++\subsection{Handling Nodes Request packets}+When we receive a Nodes Request packet from another node, we reply with a Nodes+Response packet containing the 4 nodes in the DHT State which are the closest to+the public key in the packet. If there are fewer than 4 nodes in the state, we+reply with all the nodes in the state. If there are no nodes in the state, no+reply is sent.++We also send a Ping Request when this is appropriate; see below.++\begin{code}++responseMaxNodes :: Int+responseMaxNodes = 4++handleNodesRequest ::+ DhtNodeMonad m => NodeInfo -> RpcPacket NodesRequest -> m ()+handleNodesRequest from (RpcPacket (NodesRequest key) requestId) = do+ ourPublicKey <- gets $ KeyPair.publicKey . DhtState.dhtKeyPair+ when (ourPublicKey /= NodeInfo.publicKey from) $ do+ nodes <- gets (DhtState.takeClosestNodesTo responseMaxNodes key)+ unless (null nodes) $ sendNodesResponse from requestId nodes+ sendPingRequestIfAppropriate from++\end{code}++\subsection{Handling Ping Request packets}+When a valid Ping Request packet is received, we reply with a Ping Response.++We also send a Ping Request when this is appropriate; see below.++\begin{code}++handlePingRequest ::+ DhtNodeMonad m => NodeInfo -> RpcPacket PingPacket -> m ()+handlePingRequest from (RpcPacket PingRequest requestId) = do+ sendPingResponse from requestId+ sendPingRequestIfAppropriate from+handlePingRequest _ _ = return ()++\end{code}++\subsection{Handling Ping Response packets}+When we receive a valid Ping Response packet, we first check that it is a reply+to a Ping Request which we sent within the last 5 seconds to the node from+which we received the response, and that no previous reply has been received. If+this check fails, the packet is ignored. If the check succeeds, we add to the+DHT State the node from which the response was sent.++\begin{code}++requirePingResponseWithin :: TimeDiff+requirePingResponseWithin = Time.seconds 5++maxPendingTime :: TimeDiff+maxPendingTime = maximum+ [ requireNodesResponseWithin+ , requirePingResponseWithin+ ]++checkPending :: DhtNodeMonad m =>+ TimeDiff -> NodeInfo -> RpcPacket.RequestId -> m Bool+checkPending timeLimit from requestId = do+ oldTime <- (Time.+ negate maxPendingTime) <$> Timed.askTime+ DhtState._dhtPendingReplies %= Stamped.dropOlder oldTime+ recentCutoff <- (Time.+ negate timeLimit) <$> Timed.askTime+ DhtState._dhtPendingReplies %%=+ PendingReplies.checkExpectedReply recentCutoff from requestId++handlePingResponse ::+ DhtNodeMonad m => NodeInfo -> RpcPacket PingPacket -> m ()+handlePingResponse from (RpcPacket PingResponse requestId) = do+ isReply <- checkPending requirePingResponseWithin from requestId+ ourPublicKey <- gets $ KeyPair.publicKey . DhtState.dhtKeyPair+ when (isReply && ourPublicKey /= NodeInfo.publicKey from) $ do+ time <- Timed.askTime+ modify $ DhtState.addNode time from+handlePingResponse _ _ = return ()++\end{code}++\subsection{Sending Ping Requests}+When we receive a Nodes Request or a Ping Request, in addition to the handling+described above, we sometimes send a Ping Request.+Namely, we send a Ping Request to the node which sent the packet if the node is+viable for entry to the Close List and is not already in the Close List.+An implementation may (TODO: should?) choose not to send every such Ping+Request.+(c-toxcore sends at most 32 every 2 seconds, preferring closer nodes.)++\begin{code}++sendPingRequestIfAppropriate :: DhtNodeMonad m => NodeInfo -> m ()+sendPingRequestIfAppropriate from = do+ closeList <- gets DhtState.dhtCloseList+ when+ (isNothing (NodeList.lookupPublicKey (NodeInfo.publicKey from) closeList)+ && NodeList.viable from closeList) $+ sendPingRequest from++\end{code}++\input{src/Network/Tox/DHT/DhtRequestPacket.lhs}+\subsection{Handling DHT Request packets}++A DHT node that receives a DHT request packet checks whether the addressee+public key is their DHT public key. If it is, they will decrypt and handle+the packet. Otherwise, they will check whether the addressee DHT public key+is the DHT public key of one of the nodes in their Close List. If it isn't,+they will drop the packet. If it is they will resend the packet, unaltered, to+that DHT node.++DHT request packets are used for DHT public key packets (see+\href{#onion}{onion}) and NAT ping packets.++\begin{code}++handleDhtRequestPacket :: DhtNodeMonad m => NodeInfo -> DhtRequestPacket -> m ()+handleDhtRequestPacket _from packet@DhtRequestPacket{ addresseePublicKey, dhtPacket } = do+ keyPair <- gets DhtState.dhtKeyPair+ if addresseePublicKey == KeyPair.publicKey keyPair+ then void . runMaybeT $ msum+ [ MaybeT (DhtPacket.decodeKeyed keyPair dhtPacket) >>= lift . handleNatPingPacket+ , MaybeT (DhtPacket.decodeKeyed keyPair dhtPacket) >>= lift . handleDhtPKPacket+ ]+ else void . runMaybeT $ do+ node :: NodeInfo <- MaybeT $+ NodeList.lookupPublicKey addresseePublicKey <$> gets DhtState.dhtCloseList+ lift . Networked.sendPacket node . Packet PacketKind.Crypto $ packet++\end{code}++\subsection{NAT ping packets}++A NAT ping packet is sent as the payload of a DHT request packet.++We use NAT ping packets to see if a friend we are not connected to directly is+online and ready to do the hole punching.++\subsubsection{NAT ping request}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0xfe) \\+ \texttt{1} & \texttt{uint8\_t} (0x00) \\+ \texttt{8} & \texttt{uint64\_t} random number \\+\end{tabular}++\subsubsection{NAT ping response}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} (0xfe) \\+ \texttt{1} & \texttt{uint8\_t} (0x01) \\+ \texttt{8} & \texttt{uint64\_t} random number (the same that was received in request) \\+\end{tabular}++TODO: handling these packets.++\begin{code}++-- | TODO+type NatPingPacket = ()+handleNatPingPacket :: DhtNodeMonad m => NatPingPacket -> m ()+handleNatPingPacket _ = return ()++-- | TODO+type DhtPKPacket = ()+handleDhtPKPacket :: DhtNodeMonad m => DhtPKPacket -> m ()+handleDhtPKPacket _ = return ()++\end{code}++\subsection{Effects of chosen constants on performance}+If the bucket size of the k-buckets were increased, it would increase the+amount of packets needed to check if each node is still alive, which would+increase the bandwidth usage, but reliability would go up. If the number of+nodes were decreased, reliability would go down along with bandwidth usage.+The reason for this relationship between reliability and number of nodes is+that if we assume that not every node has its UDP ports open or is behind a+cone NAT it means that each of these nodes must be able to store a certain+number of nodes behind restrictive NATs in order for others to be able to find+those nodes behind restrictive NATs. For example if 7/8 nodes were behind+restrictive NATs, using 8 nodes would not be enough because the chances of+some of these nodes being impossible to find in the network would be too high.++TODO(zugz): this seems a rather wasteful solution to this problem.++If the ping timeouts and delays between pings were higher it would decrease the+bandwidth usage but increase the amount of disconnected nodes that are still+being stored in the lists. Decreasing these delays would do the opposite.++If the maximum size 8 of the DHT Search Entry Client Lists were increased+would increase the bandwidth usage, might increase hole punching efficiency on+symmetric NATs (more ports to guess from, see Hole punching) and might increase+the reliability. Lowering this number would have the opposite effect.++The timeouts and number of nodes in lists for toxcore were picked by feeling+alone and are probably not the best values. This also applies to the behavior+which is simple and should be improved in order to make the network resist+better to sybil attacks.++TODO: consider giving min and max values for the constants.++\begin{code}++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}++type TestDhtNodeMonad = KeyedT (TimedT (RandT StdGen (StateT DhtState (Networked.NetworkLogged Identity))))+instance DhtNodeMonad TestDhtNodeMonad++runTestDhtNode :: ArbStdGen -> Timestamp -> DhtState -> TestDhtNodeMonad a -> (a, DhtState)+runTestDhtNode seed time s =+ runIdentity+ . Networked.evalNetworkLogged+ . (`runStateT` s)+ . (`evalRandT` unwrapArbStdGen seed)+ . (`TimedT.runTimedT` time)+ . (`KeyedT.evalKeyedT` Map.empty)++evalTestDhtNode :: ArbStdGen -> Timestamp -> DhtState -> TestDhtNodeMonad a -> a+evalTestDhtNode seed time s = fst . runTestDhtNode seed time s+execTestDhtNode :: ArbStdGen -> Timestamp -> DhtState -> TestDhtNodeMonad a -> DhtState+execTestDhtNode seed time s = snd . runTestDhtNode seed time s++initTestDhtState :: ArbStdGen -> Timestamp -> DhtState+initTestDhtState seed time =+ runIdentity+ . (`evalRandT` unwrapArbStdGen seed)+ . (`TimedT.runTimedT` time)+ $ initDht++-- | wrap StdGen so the Arbitrary instance isn't an orphan+newtype ArbStdGen = ArbStdGen { unwrapArbStdGen :: StdGen }+ deriving (Read, Show)++instance Arbitrary ArbStdGen+ where arbitrary = ArbStdGen . mkStdGen <$> arbitrary++\end{code}+
+ src/Network/Tox/DHT/PendingReplies.lhs view
@@ -0,0 +1,45 @@+\subsection{Replies to RPC requests}+A \textit{reply} to a Request packet is a Response packet with the Request ID in+the Response packet set equal to the Request ID in the Request packet. A+response is accepted if and only if it is the first received reply to a request+which was sent sufficiently recently, according to a time limit which depends on+the service.++\begin{code}+{-# LANGUAGE Safe #-}+module Network.Tox.DHT.PendingReplies where++import qualified Network.Tox.DHT.RpcPacket as RpcPacket+import Network.Tox.DHT.Stamped (Stamped)+import qualified Network.Tox.DHT.Stamped as Stamped+import Network.Tox.NodeInfo.NodeInfo (NodeInfo)+import Network.Tox.Time (Timestamp)++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}++type PendingReplies = Stamped (NodeInfo, RpcPacket.RequestId)++expectReply :: Timestamp -> NodeInfo -> RpcPacket.RequestId ->+ PendingReplies -> PendingReplies+expectReply time node requestId = Stamped.add time (node, requestId)++checkExpectedReply :: Timestamp -> NodeInfo -> RpcPacket.RequestId ->+ PendingReplies -> (Bool, PendingReplies)+checkExpectedReply cutoff node requestId pendingReplies =+ case filter (>= cutoff) $+ Stamped.findStamps (== (node, requestId)) pendingReplies+ of+ [] -> (False, pendingReplies)+ time:_ -> (True, Stamped.delete time (node, requestId) pendingReplies)++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}++\end{code}
+ src/Network/Tox/DHT/PingPacket.lhs view
@@ -0,0 +1,78 @@+\subsection{Ping Service}++The Ping Service is used to check if a node is responsive.++\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE Safe #-}+module Network.Tox.DHT.PingPacket where++import Data.Binary (Binary)+import Data.MessagePack (MessagePack)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)+import qualified Test.QuickCheck.Gen as Gen+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}+++\end{code}++A Ping Packet payload consists of just a boolean value saying whether it is a+request or a response.++The one byte boolean inside the encrypted payload is added to prevent peers+from creating a valid Ping Response from a Ping Request without decrypting the+packet and encrypting a new one. Since symmetric encryption is used, the+encrypted Ping Response would be byte-wise equal to the Ping Request without+the discriminator byte.++\begin{tabular}{l|l|l}+ Length & Type & \href{#rpc-services}{Contents} \\+ \hline+ \texttt{1} & Bool & Response flag: 0x00 for Request, 0x01 for Response \\+\end{tabular}++\subsubsection{Ping Request (0x00)}++A Ping Request is a Ping Packet with the response flag set to False. When a+Ping Request is received and successfully decrypted, a Ping Response packet is+created and sent back to the requestor.++\subsubsection{Ping Response (0x01)}++A Ping Response is a Ping Packet with the response flag set to True.++\begin{code}+++data PingPacket+ = PingRequest+ | PingResponse+ deriving (Eq, Read, Show, Generic, Typeable)++instance Binary PingPacket+instance MessagePack PingPacket+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary PingPacket where+ arbitrary =+ Gen.elements+ [ PingRequest+ , PingResponse+ ]+\end{code}
+ src/Network/Tox/DHT/RpcPacket.lhs view
@@ -0,0 +1,85 @@+\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.RpcPacket where++import Control.Applicative ((<$>), (<*>))+import Data.Binary (Binary)+import Data.MessagePack (MessagePack)+import Data.Typeable (Typeable)+import Data.Word (Word64)+import GHC.Generics (Generic)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}++\end{code}++A DHT RPC Service consists of a Request packet and a Response packet. A DHT+RPC Packet contains a payload and a Request ID. This ID is a 64 bit unsigned+integer that helps identify the response for a given request.++\begin{code}++newtype RequestId = RequestId Word64+ deriving (Eq, Read, Show, Binary, Arbitrary, Generic)++instance MessagePack RequestId++\end{code}++\input{src/Network/Tox/DHT/PendingReplies.lhs}++DHT RPC Packets are encrypted and transported within DHT Packets.++\begin{tabular}{l|l|l}+ Length & Type & \href{#dht-packet}{Contents} \\+ \hline+ \texttt{[0,]} & Bytes & Payload \\+ \texttt{8} & \texttt{uint64\_t} & Request ID \\+\end{tabular}++The minimum payload size is 0, but in reality the smallest sensible payload+size is 1. Since the same symmetric key is used in both communication+directions, an encrypted Request would be a valid encrypted Response if they+contained the same plaintext.++\begin{code}++data RpcPacket payload = RpcPacket+ { rpcPayload :: payload+ , requestId :: RequestId+ }+ deriving (Eq, Read, Show, Generic, Typeable)++instance Binary payload => Binary (RpcPacket payload)+instance MessagePack payload => MessagePack (RpcPacket payload)+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary payload => Arbitrary (RpcPacket payload) where+ arbitrary =+ RpcPacket <$> arbitrary <*> arbitrary+\end{code}++Parts of the protocol using RPC packets must take care to make Request payloads+not be valid Response payloads. For instance, \href{#ping-service}{Ping+Packets} carry a boolean flag that indicate whether the payload corresponds to+a Request or a Response.++The Request ID provides some resistance against replay attacks. If there were+no Request ID, it would be easy for an attacker to replay old responses and+thus provide nodes with out-of-date information. A Request ID should be+randomly generated for each Request which is sent.
+ src/Network/Tox/DHT/Stamped.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE Safe #-}+module Network.Tox.DHT.Stamped where++import qualified Data.Foldable as F+import Data.List ((\\))+import Data.Map (Map)+import qualified Data.Map as Map++import Network.Tox.Time (Timestamp)++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}++-- | a collection of objects associated with a timestamp.+type Stamped a = Map Timestamp [a]++empty :: Stamped a+empty = Map.empty++-- | add a timestamped object. There is no requirement that the stamp be later+-- than that of previously added objects.+add :: Timestamp -> a -> Stamped a -> Stamped a+add time x = Map.insertWith (++) time [x]++delete :: Eq a => Timestamp -> a -> Stamped a -> Stamped a+delete time x = Map.adjust (\\ [x]) time++findStamps :: (a -> Bool) -> Stamped a -> [Timestamp]+findStamps p = Map.keys . Map.filter (any p)++dropOlder :: Timestamp -> Stamped a -> Stamped a+dropOlder time = Map.mapMaybeWithKey $+ \t x -> if t < time then Nothing else Just x++getList :: Stamped a -> [a]+getList = F.concat++popFirst :: Stamped a -> (Maybe (Timestamp, a), Stamped a)+popFirst stamped =+ case Map.toAscList stamped of+ [] -> (Nothing, stamped)+ assoc:assocs -> case assoc of+ (_, []) -> popFirst $ Map.fromAscList assocs+ (stamp, [a]) -> (Just (stamp, a), Map.fromAscList assocs)+ (stamp, a:as) -> (Just (stamp, a), Map.fromAscList $ (stamp, as):assocs)++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}
+ src/Network/Tox/Encoding.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.Encoding where++import Data.Binary (Binary, get, put)+import Data.Binary.Bits.Get (BitGet)+import Data.Binary.Bits.Put (BitPut)+import Data.Binary.Get (Decoder (..), pushChunk,+ runGetIncremental)+import Data.Binary.Put (runPut)+import Data.ByteString (ByteString)+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Lazy as LazyByteString+import Network.Tox.Crypto.Box (PlainText (..))+++class BitEncoding a where+ bitGet :: BitGet a+ bitPut :: a -> BitPut ()+++encode :: Binary a => a -> ByteString+encode =+ LazyByteString.toStrict . runPut . put+++decode :: (Monad m, Binary a) => ByteString -> m a+decode bytes =+ finish $ pushChunk (runGetIncremental get) bytes+ where+ finish = \case+ Done unconsumed _ output ->+ if ByteString.null unconsumed+ then return output+ else fail $ "unconsumed input: " ++ show (PlainText unconsumed)+ Fail _ _ msg -> fail msg+ Partial f -> finish $ f Nothing
+ src/Network/Tox/Network/MonadRandomBytes.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE Trustworthy #-}++module Network.Tox.Network.MonadRandomBytes where++import Control.Applicative (Applicative, (<$>))+import Control.Monad.Random (RandT, getRandoms)+import Control.Monad.Reader (ReaderT)+import Control.Monad.RWS (RWST)+import Control.Monad.State (StateT)+import Control.Monad.Trans.Class (lift)+import Control.Monad.Writer (WriterT)+import Data.Binary (get)+import Data.Binary.Get (Get, getWord16be, getWord32be,+ getWord64be, getWord8, runGet)+import Data.ByteString (ByteString, pack, unpack)+import Data.ByteString.Lazy (fromStrict)+import Data.Monoid (Monoid)+import Data.Proxy (Proxy (..))+import Data.Word (Word16, Word32, Word64, Word8)+import System.Entropy (getEntropy)+import System.Random (RandomGen)+++import Network.Tox.Crypto.Key (Key)+import qualified Network.Tox.Crypto.Key as Key+import Network.Tox.Crypto.KeyPair (KeyPair)+import qualified Network.Tox.Crypto.KeyPair as KeyPair++class (Monad m, Applicative m) => MonadRandomBytes m where+ randomBytes :: Int -> m ByteString++ newKeyPair :: m KeyPair+ newKeyPair = KeyPair.fromSecretKey <$> randomKey++instance (Monad m, Applicative m, RandomGen s) => MonadRandomBytes (RandT s m) where+ randomBytes n = pack . take n <$> getRandoms++-- | cryptographically secure random bytes from system source+instance MonadRandomBytes IO where+ randomBytes = getEntropy+ newKeyPair = KeyPair.newKeyPair++instance MonadRandomBytes m => MonadRandomBytes (ReaderT r m) where+ randomBytes = lift . randomBytes+ newKeyPair = lift newKeyPair+instance (Monoid w, MonadRandomBytes m) => MonadRandomBytes (WriterT w m) where+ randomBytes = lift . randomBytes+ newKeyPair = lift newKeyPair+instance MonadRandomBytes m => MonadRandomBytes (StateT s m) where+ randomBytes = lift . randomBytes+ newKeyPair = lift newKeyPair+instance (Monoid w, MonadRandomBytes m) => MonadRandomBytes (RWST r w s m) where+ randomBytes = lift . randomBytes+ newKeyPair = lift newKeyPair++randomBinary :: MonadRandomBytes m => Get a -> Int -> m a+randomBinary g len = runGet g . fromStrict <$> randomBytes len++randomKey :: forall m a. (MonadRandomBytes m, Key.CryptoNumber a) => m (Key a)+randomKey = randomBinary get $ Key.encodedByteSize (Proxy :: Proxy a)++randomNonce :: MonadRandomBytes m => m Key.Nonce+randomNonce = randomKey++randomWord64 :: MonadRandomBytes m => m Word64+randomWord64 = randomBinary getWord64be 8+randomWord32 :: MonadRandomBytes m => m Word32+randomWord32 = randomBinary getWord32be 4+randomWord16 :: MonadRandomBytes m => m Word16+randomWord16 = randomBinary getWord16be 2+randomWord8 :: MonadRandomBytes m => m Word8+randomWord8 = randomBinary getWord8 1++-- produces Int uniformly distributed in range [0,bound)+randomInt :: MonadRandomBytes m => Int -> m Int+randomInt bound | bound <= 1 = return 0+randomInt bound =+ let+ numBits = log2 bound+ numBytes = 1 + (numBits - 1 `div` 8)+ in do+ r <- (`mod` 2^numBits) . makeInt . unpack <$> randomBytes numBytes+ if r >= bound+ then randomInt bound+ else return r+ where+ log2 :: Int -> Int+ log2 = ceiling . logBase 2 . (fromIntegral :: Int -> Double)+ makeInt :: [Word8] -> Int+ makeInt = foldr (\w -> (fromIntegral w +) . (256*)) 0++-- produces Int uniformly distributed in range [low,high]+randomIntR :: MonadRandomBytes m => (Int,Int) -> m Int+randomIntR (low,high) = (low +) <$> randomInt (1 + high - low)++-- | produces uniformly random element of a list+uniform :: MonadRandomBytes m => [a] -> m a+uniform [] = error "empty list in uniform"+uniform as = (as!!) <$> randomInt (length as)++uniformSafe :: MonadRandomBytes m => [a] -> m (Maybe a)+uniformSafe [] = return Nothing+uniformSafe as = Just <$> uniform as+
+ src/Network/Tox/Network/Networked.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE Trustworthy #-}++-- | Abstraction layer for network functionality.+--+-- The intention is to+-- (i) separate the logic of the protocol from its binary encoding, and+-- (ii) allow a simulated network in place of actual network IO.+module Network.Tox.Network.Networked where++import Control.Applicative (Applicative, (<$>))+import Control.Monad.Random (RandT)+import Control.Monad.Reader (ReaderT)+import Control.Monad.State (MonadState, StateT)+import Control.Monad.Trans.Class (lift)+import Control.Monad.Writer (WriterT, execWriterT,+ runWriterT, tell)+import Data.Binary (Binary)+import Data.Monoid (Monoid)++import Network.Tox.Network.MonadRandomBytes (MonadRandomBytes)+import Network.Tox.NodeInfo.NodeInfo (NodeInfo)+import Network.Tox.Protocol.Packet (Packet (..))+import Network.Tox.Timed (Timed)++class Monad m => Networked m where+ sendPacket :: (Binary payload, Show payload) => NodeInfo -> Packet payload -> m ()++-- | actual network IO+instance Networked (StateT NetworkState IO) where+ -- | TODO+ sendPacket _ _ = return ()++-- | TODO: sockets etc+type NetworkState = ()++type NetworkEvent = String+newtype NetworkLogged m a = NetworkLogged (WriterT [NetworkEvent] m a)+ deriving (Monad, Applicative, Functor, MonadState s, MonadRandomBytes, Timed)++runNetworkLogged :: Monad m => NetworkLogged m a -> m (a, [NetworkEvent])+runNetworkLogged (NetworkLogged m) = runWriterT m+evalNetworkLogged :: (Monad m, Applicative m) => NetworkLogged m a -> m a+evalNetworkLogged = (fst <$>) . runNetworkLogged+execNetworkLogged :: Monad m => NetworkLogged m a -> m [NetworkEvent]+execNetworkLogged (NetworkLogged m) = execWriterT m++-- | just log network events+instance Monad m => Networked (NetworkLogged m) where+ sendPacket to packet = NetworkLogged $+ tell [">>> " ++ show to ++ " : " ++ show packet]++instance Networked m => Networked (ReaderT r m) where+ sendPacket = (lift .) . sendPacket+instance (Monoid w, Networked m) => Networked (WriterT w m) where+ sendPacket = (lift .) . sendPacket+instance Networked m => Networked (RandT s m) where+ sendPacket = (lift .) . sendPacket+instance Networked m => Networked (StateT s m) where+ sendPacket = (lift .) . sendPacket
+ src/Network/Tox/NodeInfo.lhs view
@@ -0,0 +1,12 @@+\chapter{Node Info}++\begin{code}+{-# LANGUAGE Safe #-}+module Network.Tox.NodeInfo where+\end{code}++\input{src/Network/Tox/NodeInfo/TransportProtocol.lhs}+\input{src/Network/Tox/NodeInfo/HostAddress.lhs}+\input{src/Network/Tox/NodeInfo/PortNumber.lhs}+\input{src/Network/Tox/NodeInfo/SocketAddress.lhs}+\input{src/Network/Tox/NodeInfo/NodeInfo.lhs}
+ src/Network/Tox/NodeInfo/HostAddress.lhs view
@@ -0,0 +1,102 @@+\section{Host Address}++A Host Address is either an IPv4 or an IPv6 address. The binary representation+of an IPv4 address is a Big Endian 32 bit unsigned integer (4 bytes). For an+IPv6 address, it is a Big Endian 128 bit unsigned integer (16 bytes). The+binary representation of a Host Address is a 7 bit unsigned integer specifying+the address family (2 for IPv4, 10 for IPv6), followed by the address itself.++Thus, when packed together with the Transport Protocol, the first bit of the+packed byte is the protocol and the next 7 bits are the address family.++\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.NodeInfo.HostAddress where++import Control.Applicative ((<$>))+import Control.Arrow ((&&&))+import Data.Binary (Binary)+import qualified Data.Binary as Binary (get, put)+import qualified Data.Binary.Bits.Get as Bits+import qualified Data.Binary.Bits.Put as Bits+import qualified Data.Binary.Get as Bytes+import qualified Data.Binary.Put as Bytes+import qualified Data.IP as IP+import Data.MessagePack (MessagePack)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import qualified Network.Socket as Socket (HostAddress, HostAddress6)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)+import qualified Test.QuickCheck.Gen as Gen+import Text.Read (readMaybe, readPrec)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}+++data HostAddress+ = IPv4 Socket.HostAddress+ | IPv6 Socket.HostAddress6+ deriving (Eq, Ord, Generic, Typeable)++instance Binary HostAddress+instance MessagePack HostAddress+++instance Show HostAddress where+ show (IPv4 addr) = show . show . IP.fromHostAddress $ addr+ show (IPv6 addr) = show . show . IP.fromHostAddress6 $ addr+++instance Read HostAddress where+ readPrec = do+ str <- readPrec+ case readMaybe str of+ Nothing -> fail "HostAddress"+ Just (IP.IPv4 ipv4) -> return . IPv4 . IP.toHostAddress $ ipv4+ Just (IP.IPv6 ipv6) -> return . IPv6 . IP.toHostAddress6 $ ipv6+++getHostAddressGetter :: Bits.BitGet (Bytes.Get HostAddress)+getHostAddressGetter =+ Bits.getWord8 7 >>= \case+ 2 -> return $ IPv4 <$> Binary.get+ 10 -> return $ IPv6 <$> Binary.get+ n -> fail $ "Invalid address family: " ++ show n+++putAddressFamily :: HostAddress -> Bits.BitPut ()+putAddressFamily (IPv4 _) = Bits.putWord8 7 2+putAddressFamily (IPv6 _) = Bits.putWord8 7 10+++putHostAddressValue :: HostAddress -> Bytes.Put+putHostAddressValue (IPv4 addr) = Binary.put addr+putHostAddressValue (IPv6 addr) = Binary.put addr+++putHostAddress :: HostAddress -> (Bits.BitPut (), Bytes.Put)+putHostAddress = putAddressFamily &&& putHostAddressValue+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary HostAddress where+ arbitrary =+ Gen.oneof+ [ IPv4 <$> arbitrary+ , IPv6 <$> arbitrary+ ]+\end{code}
+ src/Network/Tox/NodeInfo/NodeInfo.lhs view
@@ -0,0 +1,98 @@+\section{Node Info (packed node format)}++The Node Info data structure contains a Transport Protocol, a Socket Address,+and a Public Key. This is sufficient information to start communicating with+that node. The binary representation of a Node Info is called the "packed node+format".++\begin{tabular}{l|l|l}+ Length & Type & Contents \\+ \hline+ \texttt{1} bit & Transport Protocol & UDP = 0, TCP = 1 \\+ \texttt{7} bit & Address Family & 2 = IPv4, 10 = IPv6 \\+ \texttt{4 $|$ 16} & IP address & 4 bytes for IPv4, 16 bytes for IPv6 \\+ \texttt{2} & Port Number & Port number \\+ \texttt{32} & Public Key & Node ID \\+\end{tabular}++The packed node format is a way to store the node info in a small yet easy to+parse format. To store more than one node, simply append another one to the+previous one: \texttt{[packed node 1][packed node 2][...]}.++In the packed node format, the first byte (high bit protocol, lower 7 bits+address family) are called the IP Type. The following table is informative and+can be used to simplify the implementation.++\begin{tabular}{l|l|l}+ IP Type & Transport Protocol & Address Family \\+ \hline+ \texttt{2 (0x02)} & UDP & IPv4 \\+ \texttt{10 (0x0a)} & UDP & IPv6 \\+ \texttt{130 (0x82)} & TCP & IPv4 \\+ \texttt{138 (0x8a)} & TCP & IPv6 \\+\end{tabular}++The number \texttt{130} is used for an IPv4 TCP relay and \texttt{138} is used+to indicate an IPv6 TCP relay.++The reason for these numbers is that the numbers on Linux for IPv4 and IPv6+(the \texttt{AF\_INET} and \texttt{AF\_INET6} defines) are \texttt{2} and+\texttt{10}. The TCP numbers are just the UDP numbers \texttt{+ 128}.++\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE Safe #-}+module Network.Tox.NodeInfo.NodeInfo where++import Control.Applicative ((<$>), (<*>))+import Data.Binary (Binary)+import qualified Data.Binary as Binary (get, put)+import Data.MessagePack (MessagePack)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)++import Network.Tox.Crypto.Key (PublicKey)+import Network.Tox.NodeInfo.SocketAddress (SocketAddress)+import qualified Network.Tox.NodeInfo.SocketAddress as SocketAddress+import Network.Tox.NodeInfo.TransportProtocol (TransportProtocol)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}+++data NodeInfo = NodeInfo+ { protocol :: TransportProtocol+ , address :: SocketAddress+ , publicKey :: PublicKey+ }+ deriving (Eq, Ord, Show, Read, Generic, Typeable)++instance MessagePack NodeInfo+++instance Binary NodeInfo where+ get =+ uncurry NodeInfo <$> SocketAddress.getSocketAddress <*> Binary.get++ put ni = do+ SocketAddress.putSocketAddress (protocol ni) (address ni)+ Binary.put $ publicKey ni+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary NodeInfo where+ arbitrary =+ NodeInfo <$> arbitrary <*> arbitrary <*> arbitrary+\end{code}
+ src/Network/Tox/NodeInfo/PortNumber.lhs view
@@ -0,0 +1,50 @@+\section{Port Number}++A Port Number is a 16 bit number. Its binary representation is a Big Endian 16+bit unsigned integer (2 bytes).++\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.NodeInfo.PortNumber where++import Control.Applicative ((<$>))+import Data.Binary (Binary)+import Data.MessagePack (MessagePack)+import Data.Typeable (Typeable)+import Data.Word (Word16)+import GHC.Generics (Generic)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}+++newtype PortNumber = PortNumber Word16+ deriving+ ( Generic, Typeable+ , Eq, Ord+ , Show, Read+ , Binary+ , Num, Integral, Real, Bounded, Enum)++instance MessagePack PortNumber+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary PortNumber where+ arbitrary =+ PortNumber . fromInteger <$> arbitrary+\end{code}
+ src/Network/Tox/NodeInfo/SocketAddress.lhs view
@@ -0,0 +1,76 @@+\section{Socket Address}++A Socket Address is a pair of Host Address and Port Number. Together with a+Transport Protocol, it is sufficient information to address a network port on+any internet host.++\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.NodeInfo.SocketAddress where++import Control.Applicative ((<$>), (<*>))+import Data.Binary (Binary, get, put)+import qualified Data.Binary.Bits.Get as Bits (runBitGet)+import qualified Data.Binary.Bits.Put as Bits (runBitPut)+import qualified Data.Binary.Get as Binary (Get)+import qualified Data.Binary.Put as Binary (Put)+import Data.MessagePack (MessagePack)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Network.Tox.Encoding (bitGet, bitPut)+import Network.Tox.NodeInfo.HostAddress (HostAddress (..))+import qualified Network.Tox.NodeInfo.HostAddress as HostAddress+import Network.Tox.NodeInfo.PortNumber (PortNumber)+import Network.Tox.NodeInfo.TransportProtocol (TransportProtocol)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}+++data SocketAddress = SocketAddress HostAddress PortNumber+ deriving (Eq, Ord, Show, Read, Generic, Typeable)++instance Binary SocketAddress+instance MessagePack SocketAddress+++putSocketAddress :: TransportProtocol -> SocketAddress -> Binary.Put+putSocketAddress protocol (SocketAddress hostAddress portNumber) =+ let (putAddressFamily, putHostAddress) = HostAddress.putHostAddress hostAddress in+ do+ Bits.runBitPut $ do+ bitPut protocol -- first bit = protocol+ putAddressFamily -- 7 bits = address family+ putHostAddress+ put portNumber+++getSocketAddress :: Binary.Get (TransportProtocol, SocketAddress)+getSocketAddress = do+ (protocol, getHostAddress) <- Bits.runBitGet $ do+ protocol <- bitGet+ getHostAddress <- HostAddress.getHostAddressGetter+ return (protocol, getHostAddress)+ hostAddress <- getHostAddress+ portNumber <- get+ return (protocol, SocketAddress hostAddress portNumber)+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary SocketAddress where+ arbitrary =+ SocketAddress <$> arbitrary <*> arbitrary+\end{code}
+ src/Network/Tox/NodeInfo/TransportProtocol.lhs view
@@ -0,0 +1,65 @@+\section{Transport Protocol}++A Transport Protocol is a transport layer protocol directly below the Tox+protocol itself. Tox supports two transport protocols: UDP and TCP. The+binary representation of the Transport Protocol is a single bit: 0 for UDP, 1+for TCP. If encoded as standalone value, the bit is stored in the least+significant bit of a byte. If followed by other bit-packed data, it consumes+exactly one bit.++The human-readable representation for UDP is \texttt{UDP} and for TCP is+\texttt{TCP}.++\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.NodeInfo.TransportProtocol where++import Data.Binary (Binary)+import qualified Data.Binary.Bits.Get as Bits (getBool)+import qualified Data.Binary.Bits.Put as Bits (putBool)+import Data.MessagePack (MessagePack)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Network.Tox.Encoding (BitEncoding, bitGet, bitPut)+import Test.QuickCheck.Arbitrary (Arbitrary (..))+import qualified Test.QuickCheck.Gen as Gen+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}+++data TransportProtocol+ = UDP+ | TCP+ deriving (Eq, Ord, Show, Read, Generic, Typeable)++instance Binary TransportProtocol+instance MessagePack TransportProtocol++instance BitEncoding TransportProtocol where+ bitGet = fmap (\case+ False -> UDP+ True -> TCP+ ) Bits.getBool++ bitPut UDP = Bits.putBool False+ bitPut TCP = Bits.putBool True+++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary TransportProtocol where+ arbitrary = Gen.elements [UDP, TCP]+\end{code}
+ src/Network/Tox/Protocol.lhs view
@@ -0,0 +1,9 @@+\chapter{Protocol Packet}++\begin{code}+{-# LANGUAGE Safe #-}+module Network.Tox.Protocol where+\end{code}++\input{src/Network/Tox/Protocol/Packet.lhs}+\input{src/Network/Tox/Protocol/PacketKind.lhs}
+ src/Network/Tox/Protocol/Packet.lhs view
@@ -0,0 +1,72 @@+\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE Safe #-}+module Network.Tox.Protocol.Packet where++import Control.Applicative ((<$>), (<*>))+import Data.Binary (Binary)+import Data.MessagePack (MessagePack)+import Data.Typeable (Typeable)+import GHC.Generics (Generic)+import Network.Tox.Protocol.PacketKind (PacketKind)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}++\end{code}++A Protocol Packet is the top level Tox protocol element. All other packet+types are wrapped in Protocol Packets. It consists of a Packet Kind and a+payload. The binary representation of a Packet Kind is a single byte (8 bits).+The payload is an arbitrary sequence of bytes.++\begin{tabular}{l|l|l}+ Length & Type & Contents \\+ \hline+ \texttt{1} & Packet Kind & The packet kind identifier \\+ \texttt{[0,]} & Bytes & Payload \\+\end{tabular}++\begin{code}++data Packet payload = Packet+ { packetKind :: PacketKind+ , packetPayload :: payload+ }+ deriving (Eq, Read, Show, Generic, Typeable)++instance Binary payload => Binary (Packet payload)+instance MessagePack payload => MessagePack (Packet payload)++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary payload => Arbitrary (Packet payload) where+ arbitrary =+ Packet <$> arbitrary <*> arbitrary+\end{code}++These top level packets can be transported in a number of ways, the most common+way being over the network using UDP or TCP. The protocol itself does not+prescribe transport methods, and an implementation is free to implement+additional transports such as WebRTC, IRC, or pipes.++In the remainder of the document, different kinds of Protocol Packet are+specified with their packet kind and payload. The packet kind is not repeated+in the payload description (TODO: actually it mostly is, but later it won't).++Inside Protocol Packets payload, other packet types can specify additional+packet kinds. E.g. inside a Crypto Data packet (\texttt{0x1b}), the+\href{#messenger}{Messenger} module defines its protocols for messaging, file+transfers, etc. Top level Protocol Packets are themselves not encrypted,+though their payload may be.
+ src/Network/Tox/Protocol/PacketKind.lhs view
@@ -0,0 +1,163 @@+\section{Packet Kind}++The following is an exhaustive list of top level packet kind names and their+number. Their payload is specified in dedicated sections. Each section is+named after the Packet Kind it describes followed by the byte value in+parentheses, e.g. \href{#ping-request-0x00}{Ping Request (0x00)}.++\begin{tabular}{l|l}+ Byte value & Packet Kind \\+ \hline+ \texttt{0x00} & Ping Request \\+ \texttt{0x01} & Ping Response \\+ \texttt{0x02} & Nodes Request \\+ \texttt{0x04} & Nodes Response \\+ \texttt{0x18} & Cookie Request \\+ \texttt{0x19} & Cookie Response \\+ \texttt{0x1a} & Crypto Handshake \\+ \texttt{0x1b} & Crypto Data \\+ \texttt{0x20} & DHT Request \\+ \texttt{0x21} & LAN Discovery \\+ \texttt{0x80} & Onion Request 0 \\+ \texttt{0x81} & Onion Request 1 \\+ \texttt{0x82} & Onion Request 2 \\+ \texttt{0x83} & Announce Request \\+ \texttt{0x84} & Announce Response \\+ \texttt{0x85} & Onion Data Request \\+ \texttt{0x86} & Onion Data Response \\+ \texttt{0x8c} & Onion Response 3 \\+ \texttt{0x8d} & Onion Response 2 \\+ \texttt{0x8e} & Onion Response 1 \\+ \texttt{0xf0} & Bootstrap Info \\+\end{tabular}++\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE Safe #-}+module Network.Tox.Protocol.PacketKind where++import Control.Arrow ((&&&))+import Data.Binary (Binary, get, put)+import Data.MessagePack (MessagePack)+import Data.Typeable (Typeable)+import Data.Word (Word8)+import GHC.Generics (Generic)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary,+ arbitraryBoundedEnum)+++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}+++data PacketKind+ = PingRequest+ | PingResponse+ | NodesRequest+ | NodesResponse+ | CookieRequest+ | CookieResponse+ | CryptoHandshake+ | CryptoData+ | Crypto+ | LanDiscovery+ | OnionRequest0+ | OnionRequest1+ | OnionRequest2+ | AnnounceRequest+ | AnnounceResponse+ | OnionDataRequest+ | OnionDataResponse+ | OnionResponse3+ | OnionResponse2+ | OnionResponse1+ | BootstrapInfo+ deriving (Eq, Read, Show, Bounded, Enum, Generic, Typeable)+++instance MessagePack PacketKind+++kindDescription :: PacketKind -> String+kindDescription = \case+ PingRequest -> "Ping request"+ PingResponse -> "Ping response"+ NodesRequest -> "Nodes request"+ NodesResponse -> "Nodes response"+ CookieRequest -> "Cookie request"+ CookieResponse -> "Cookie response"+ CryptoHandshake -> "Crypto handshake"+ CryptoData -> "Crypto data"+ Crypto -> "Encrypted data"+ LanDiscovery -> "LAN discovery"+ OnionRequest0 -> "Initial onion request"+ OnionRequest1 -> "First level wrapped onion request"+ OnionRequest2 -> "Second level wrapped onion request"+ AnnounceRequest -> "Announce request"+ AnnounceResponse -> "Announce response"+ OnionDataRequest -> "Onion data request"+ OnionDataResponse -> "Onion data response"+ OnionResponse3 -> "Third level wrapped onion response"+ OnionResponse2 -> "Second level wrapped onion response"+ OnionResponse1 -> "First level wrapped onion response"+ BootstrapInfo -> "Bootstrap node info request and response"+++kindToByte :: PacketKind -> Word8+kindToByte = \case+ PingRequest -> 0x00+ PingResponse -> 0x01+ NodesRequest -> 0x02+ NodesResponse -> 0x04+ CookieRequest -> 0x18+ CookieResponse -> 0x19+ CryptoHandshake -> 0x1a+ CryptoData -> 0x1b+ Crypto -> 0x20+ LanDiscovery -> 0x21+ OnionRequest0 -> 0x80+ OnionRequest1 -> 0x81+ OnionRequest2 -> 0x82+ AnnounceRequest -> 0x83+ AnnounceResponse -> 0x84+ OnionDataRequest -> 0x85+ OnionDataResponse -> 0x86+ OnionResponse3 -> 0x8c+ OnionResponse2 -> 0x8d+ OnionResponse1 -> 0x8e+ BootstrapInfo -> 0xf0+++byteToKind :: Word8 -> Maybe PacketKind+byteToKind =+ flip lookup mapping+ where+ mapping = map (kindToByte &&& id) [minBound..maxBound]+++instance Binary PacketKind where+ put = put . kindToByte++ get = do+ byte <- get+ case byteToKind byte of+ Nothing -> fail $ "no binary mapping for packet kind " ++ show byte+ Just kind -> return kind++++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}+++instance Arbitrary PacketKind where+ arbitrary = arbitraryBoundedEnum+\end{code}
+ src/Network/Tox/SaveData.lhs view
@@ -0,0 +1,336 @@+\chapter{State Format}++\begin{code}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-}+module Network.Tox.SaveData+ ( SaveData (..)+ , Section (..)+ , NospamKeys (..)+ , Friends (..)+ , Bytes (..)+ ) where+\end{code}++The reference Tox implementation uses a custom binary format to save the state+of a Tox client between restarts. This format is far from perfect and will be+replaced eventually. For the sake of maintaining compatibility down the road,+it is documented here.++The binary encoding of all integer types in the state format is a fixed-width+byte sequence with the integer encoded in Little Endian unless stated otherwise.++\begin{code}++import Control.Arrow (second)+import Control.Monad (when)+import Data.Binary (Binary (..))+import Data.Binary.Get (Get)+import qualified Data.Binary.Get as Get+import Data.Binary.Put (Put)+import qualified Data.Binary.Put as Put+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LBS+import Data.Word (Word16, Word32, Word8)+import GHC.Generics (Generic)+import Network.Tox.Crypto.Key (PublicKey, SecretKey)+import Network.Tox.SaveData.Conferences (Conferences)+import Network.Tox.SaveData.DHT (DHT)+import Network.Tox.SaveData.Friend (Friend)+import Network.Tox.SaveData.Nodes (Nodes)+import qualified Network.Tox.SaveData.Util as Util+import Test.QuickCheck.Arbitrary (Arbitrary (..),+ genericShrink)+import qualified Test.QuickCheck.Gen as Gen++\end{code}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{4} & Zeroes \\+ \texttt{4} & \texttt{uint32\_t} (0x15ED1B1F) \\+ \texttt{?} & List of sections \\+\end{tabular}++\begin{code}++saveDataMagic :: Word32+saveDataMagic = 0x15ED1B1F++newtype SaveData = SaveData [Section]+ deriving (Eq, Show, Read, Generic)++instance Binary SaveData where+ get = do+ zeroes <- Get.getWord32le+ when (zeroes /= 0) $+ fail $ "savedata should start with 32 zero-bits, but got "+ ++ show zeroes++ magic <- Get.getWord32le+ when (magic /= saveDataMagic) $+ fail $ "wrong magic number for savedata: "+ ++ show magic ++ " != " ++ show saveDataMagic++ SaveData <$> getSections++ put (SaveData sections) = do+ Put.putWord32le 0+ Put.putWord32le saveDataMagic+ putSections sections++instance Arbitrary SaveData where+ arbitrary = SaveData . (++ [SectionEOF]) <$> arbitrary+ shrink = filter (\(SaveData ss) -> SectionEOF `elem` ss) . genericShrink++\end{code}++\section{Sections}++The core of the state format consists of a list of sections. Every section has+its type and length specified at the beginning. In some cases, a section only+contains one item and thus takes up the entire length of the section. This is+denoted with '?'.++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{4} & \texttt{uint32\_t} Length of this section \\+ \texttt{2} & \texttt{uint16\_t} Section type \\+ \texttt{2} & \texttt{uint16\_t} (0x01CE) \\+ \texttt{?} & Section \\+\end{tabular}++\begin{code}++sectionMagic :: Word16+sectionMagic = 0x01CE++\end{code}++Section types:++\begin{tabular}{l|l}+ Name & Value \\+ \hline+ NospamKeys & 0x01 \\+ DHT & 0x02 \\+ Friends & 0x03 \\+ Name & 0x04 \\+ StatusMessage & 0x05 \\+ Status & 0x06 \\+ TcpRelays & 0x0A \\+ PathNodes & 0x0B \\+ Conferences & 0x14 \\+ EOF & 0xFF \\+\end{tabular}++\begin{code}++getSections :: Get [Section]+getSections = go+ where+ go = do+ (len, ty) <- Util.getSectionHeader sectionMagic++ let load f = (:) <$> (f <$> Get.isolate (fromIntegral len) get) <*> go++ case ty of+ 0x01 -> load SectionNospamKeys+ 0x02 -> load SectionDHT+ 0x03 -> load SectionFriends+ 0x04 -> load SectionName+ 0x05 -> load SectionStatusMessage+ 0x06 -> load SectionStatus+ 0x0A -> load SectionTcpRelays+ 0x0B -> load SectionPathNodes+ 0x14 -> load SectionConferences+ 0xFF -> return [SectionEOF]+ _ -> fail $ show ty++putSections :: [Section] -> Put+putSections = mapM_ go+ where+ go section = do+ let (ty, bytes) = second Put.runPut $ putSection section++ Util.putSectionHeader sectionMagic (fromIntegral $ LBS.length bytes) ty+ Put.putLazyByteString bytes++ putSection = \case+ SectionNospamKeys x -> (0x01, put x)+ SectionDHT x -> (0x02, put x)+ SectionFriends x -> (0x03, put x)+ SectionName x -> (0x04, put x)+ SectionStatusMessage x -> (0x05, put x)+ SectionStatus x -> (0x06, put x)+ SectionTcpRelays x -> (0x0A, put x)+ SectionPathNodes x -> (0x0B, put x)+ SectionConferences x -> (0x14, put x)+ SectionEOF -> (0xFF, return ())++\end{code}++Not every section listed above is required to be present in order to restore+from a state file. Only NospamKeys is required.++\subsection{Nospam and Keys (0x01)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{4} & \texttt{uint32\_t} Nospam \\+ \texttt{32} & Long term public key \\+ \texttt{32} & Long term secret key \\+\end{tabular}++\input{src/Network/Tox/SaveData/DHT.lhs}++\subsection{Friends (0x03)}++This section contains a list of friends. A friend can either be a peer we've+sent a friend request to or a peer we've accepted a friend request from.++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{?} & List of friends \\+\end{tabular}++\input{src/Network/Tox/SaveData/Friend.lhs}++\subsection{Name (0x04)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{?} & Name as a UTF-8 encoded string \\+\end{tabular}++\subsection{Status Message (0x05)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{?} & Status message as a UTF-8 encoded string \\+\end{tabular}++\subsection{Status (0x06)}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} User status (see also: \texttt{USERSTATUS}) \\+\end{tabular}++\subsection{Tcp Relays (0x0A)}++This section contains a list of TCP relays.++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{?} & List of TCP relays \\+\end{tabular}++The structure of a TCP relay is the same as \texttt{Node Info}. Note: this+means that the integers stored in these nodes are stored in Big Endian as well.++\subsection{Path Nodes (0x0B)}++This section contains a list of path nodes used for onion routing.++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{?} & List of path nodes \\+\end{tabular}++The structure of a path node is the same as \texttt{Node Info}. Note: this+means that the integers stored in these nodes are stored in Big Endian as well.++\input{src/Network/Tox/SaveData/Conferences.lhs}++\subsection{EOF (0xFF)}++This section indicates the end of the state file. This section doesn't have any+content and thus its length is 0.++\begin{code}++data Section+ = SectionNospamKeys NospamKeys+ | SectionDHT DHT+ | SectionFriends Friends+ | SectionName Bytes+ | SectionStatusMessage Bytes+ | SectionStatus Word8+ | SectionTcpRelays Nodes+ | SectionPathNodes Nodes+ | SectionConferences Conferences+ | SectionEOF+ deriving (Eq, Show, Read, Generic)++instance Arbitrary Section where+ arbitrary = Gen.oneof+ [ SectionNospamKeys <$> arbitrary+ , SectionDHT <$> arbitrary+ , SectionFriends <$> arbitrary+ , SectionName <$> arbitrary+ , SectionStatusMessage <$> arbitrary+ , SectionStatus <$> arbitrary+ , SectionTcpRelays <$> arbitrary+ , SectionPathNodes <$> arbitrary+ , SectionConferences <$> arbitrary+ ]+ shrink = genericShrink++data NospamKeys = NospamKeys+ { nospam :: Word32+ , publicKey :: PublicKey+ , secretKey :: SecretKey+ }+ deriving (Eq, Show, Read, Generic)++instance Binary NospamKeys where+ get = NospamKeys+ <$> Get.getWord32le+ <*> get+ <*> get++ put NospamKeys{..} = do+ Put.putWord32le nospam+ put publicKey+ put secretKey++instance Arbitrary NospamKeys where+ arbitrary = NospamKeys+ <$> arbitrary+ <*> arbitrary+ <*> arbitrary+ shrink = genericShrink++newtype Friends = Friends [Friend]+ deriving (Eq, Show, Read, Generic)++instance Binary Friends where+ get = Friends <$> Util.getList+ put (Friends xs) = mapM_ put xs++instance Arbitrary Friends where+ arbitrary = Friends <$> arbitrary+ shrink = genericShrink++newtype Bytes = Bytes LBS.ByteString+ deriving (Eq, Show, Read, Generic)++instance Binary Bytes where+ get = Bytes <$> Get.getRemainingLazyByteString+ put (Bytes bs) = Put.putLazyByteString bs++instance Arbitrary Bytes where+ arbitrary = Bytes . LBS.pack <$> arbitrary++\end{code}
+ src/Network/Tox/SaveData/Conferences.lhs view
@@ -0,0 +1,169 @@+\subsection{Conferences (0x14)}++This section contains a list of saved conferences.++\begin{code}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE RecordWildCards #-}+module Network.Tox.SaveData.Conferences where++import Data.Binary (Binary (..))+import qualified Data.Binary.Get as Get+import qualified Data.Binary.Put as Put+import qualified Data.ByteString as BS+import Data.Word (Word16, Word32, Word64, Word8)+import GHC.Generics (Generic)+import Network.Tox.Crypto.Key (PublicKey)+import qualified Network.Tox.SaveData.Util as Util+import Test.QuickCheck.Arbitrary (Arbitrary (..), genericShrink)+import qualified Test.QuickCheck.Arbitrary as Arbitrary++\end{code}++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{?} & List of conferences \\+\end{tabular}++\begin{code}++newtype Conferences = Conferences [Conference]+ deriving (Eq, Show, Read, Generic)++instance Binary Conferences where+ get = Conferences <$> Util.getList+ put (Conferences xs) = mapM_ put xs++instance Arbitrary Conferences where+ arbitrary = Conferences <$> arbitrary+ shrink = genericShrink++\end{code}++Conference:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} Groupchat type \\+ \texttt{32} & Groupchat id \\+ \texttt{4} & \texttt{uint32\_t} Message number \\+ \texttt{2} & \texttt{uint16\_t} Lossy message number \\+ \texttt{2} & \texttt{uint16\_t} Peer number \\+ \texttt{4} & \texttt{uint32\_t} Number of peers \\+ \texttt{1} & \texttt{uint8\_t} Title length \\+ \texttt{?} & Title \\+ \texttt{?} & List of peers \\+\end{tabular}++All peers other than the saver are saved, including frozen peers. On reload,+they all start as frozen.++\begin{code}++maxTitleLen :: Int+maxTitleLen = 128++data Conference = Conference+ { conferenceType :: Word8+ , conferenceId :: BS.ByteString+ , messageNumber :: Word32+ , lossyMessageNumber :: Word16+ , selfPeerNumber :: Word16+ , title :: BS.ByteString+ , peers :: [Peer]+ }+ deriving (Eq, Show, Read)++instance Binary Conference where+ get = do+ conferenceType <- Get.getWord8+ conferenceId <- Get.getByteString 32+ messageNumber <- Get.getWord32le+ lossyMessageNumber <- Get.getWord16le+ selfPeerNumber <- Get.getWord16le+ peerCount <- Get.getWord32le+ titleLength <- Get.getWord8+ title <- Get.getByteString (fromIntegral titleLength)+ peers <- mapM (const get) [1..peerCount]+ return Conference{..}++ put Conference{..} = do+ Put.putWord8 conferenceType+ Put.putByteString conferenceId+ Put.putWord32le messageNumber+ Put.putWord16le lossyMessageNumber+ Put.putWord16le selfPeerNumber+ Put.putWord32le (fromIntegral $ length peers)+ Put.putWord8 (fromIntegral $ BS.length title)+ Put.putByteString title+ mapM_ put peers+++instance Arbitrary Conference where+ arbitrary = Conference+ <$> arbitrary+ <*> (BS.pack <$> Arbitrary.vector 32)+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> (BS.pack . take maxTitleLen <$> arbitrary)+ <*> arbitrary++\end{code}++Peer:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{32} & Long term public key \\+ \texttt{32} & DHT public key \\+ \texttt{2} & \texttt{uint16\_t} Peer number \\+ \texttt{8} & \texttt{uint64\_t} Last active timestamp \\+ \texttt{1} & \texttt{uint8\_t} Name length \\+ \texttt{?} & Name \\+\end{tabular}++\begin{code}++maxNameLen :: Int+maxNameLen = 128++data Peer = Peer+ { publicKey :: PublicKey+ , dhtPublicKey :: PublicKey+ , peerNumber :: Word16+ , lastActiveTime :: Word64+ , name :: BS.ByteString+ }+ deriving (Eq, Show, Read)++instance Binary Peer where+ get = do+ publicKey <- get+ dhtPublicKey <- get+ peerNumber <- Get.getWord16le+ lastActiveTime <- Get.getWord64le+ nameLength <- Get.getWord8+ name <- Get.getByteString (fromIntegral nameLength)+ return Peer{..}++ put Peer{..} = do+ put publicKey+ put dhtPublicKey+ Put.putWord16le peerNumber+ Put.putWord64le lastActiveTime+ Put.putWord8 (fromIntegral $ BS.length name)+ Put.putByteString name++instance Arbitrary Peer where+ arbitrary = Peer+ <$> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> (BS.pack . take maxNameLen <$> arbitrary)++\end{code}
+ src/Network/Tox/SaveData/DHT.lhs view
@@ -0,0 +1,120 @@+\subsection{DHT (0x02)}++\begin{code}+module Network.Tox.SaveData.DHT (DHT) where++import Control.Arrow (second)+import Control.Monad (when)+import Data.Binary (Binary (..))+import Data.Binary.Get (Get)+import qualified Data.Binary.Get as Get+import Data.Binary.Put (Put)+import qualified Data.Binary.Put as Put+import qualified Data.ByteString.Lazy as LBS+import Data.Word (Word16, Word32)+import Network.Tox.SaveData.Nodes (Nodes)+import qualified Network.Tox.SaveData.Util as Util+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)+import qualified Test.QuickCheck.Gen as Gen++\end{code}++This section contains a list of DHT-related sections.++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{4} & \texttt{uint32\_t} (0x159000D) \\+ \texttt{?} & List of DHT sections \\+\end{tabular}++\subsubsection{DHT Sections}++Every DHT section has the following structure:++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{4} & \texttt{uint32\_t} Length of this section \\+ \texttt{2} & \texttt{uint16\_t} DHT section type \\+ \texttt{2} & \texttt{uint16\_t} (0x11CE) \\+ \texttt{?} & DHT section \\+\end{tabular}++DHT section types:++\begin{tabular}{l|l}+ Name & Value \\+ \hline+ Nodes & 0x04 \\+\end{tabular}++\paragraph{Nodes (0x04)}++This section contains a list of nodes. These nodes are used to quickly reconnect+to the DHT after a Tox client is restarted.++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{?} & List of nodes \\+\end{tabular}++The structure of a node is the same as \texttt{Node Info}. Note: this means+that the integers stored in these nodes are stored in Big Endian as well.++\begin{code}++dhtMagic :: Word32+dhtMagic = 0x0159000D++sectionMagic :: Word16+sectionMagic = 0x11CE++newtype DHT = DHT [DhtSection]+ deriving (Eq, Show, Read)++instance Arbitrary DHT where+ arbitrary = DHT <$> arbitrary++instance Binary DHT where+ get = do+ magic <- Get.getWord32le+ when (magic /= dhtMagic) $+ fail $ "wrong magic number for DHT savedata: "+ ++ show magic ++ " != " ++ show dhtMagic++ DHT <$> Util.getList++ put (DHT sections) = do+ Put.putWord32le dhtMagic+ mapM_ put sections+++newtype DhtSection+ = DhtSectionNodes Nodes+ deriving (Eq, Show, Read)++instance Binary DhtSection where+ get = do+ (len, ty) <- Util.getSectionHeader sectionMagic+ Get.isolate len $ case ty of+ 0x04 -> DhtSectionNodes <$> get+ _ -> fail $ show ty++ put section = do+ let (ty, bytes) = second Put.runPut output++ Util.putSectionHeader sectionMagic (fromIntegral $ LBS.length bytes) ty+ Put.putLazyByteString bytes++ where+ output = case section of+ DhtSectionNodes x -> (0x04, put x)++instance Arbitrary DhtSection where+ arbitrary = Gen.oneof+ [ DhtSectionNodes <$> arbitrary+ ]++\end{code}
+ src/Network/Tox/SaveData/Friend.lhs view
@@ -0,0 +1,136 @@+\begin{code}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+module Network.Tox.SaveData.Friend where++import Data.Binary (Binary (..))+import qualified Data.Binary.Get as Get+import qualified Data.Binary.Put as Put+import qualified Data.ByteString as BS+import Data.Monoid ((<>))+import Data.Word (Word32, Word64, Word8)+import Network.Tox.Crypto.Key (PublicKey)+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)+\end{code}++Friend:++The integers in this structure are stored in Big Endian format.++\begin{tabular}{l|l}+ Length & Contents \\+ \hline+ \texttt{1} & \texttt{uint8\_t} Status \\+ \texttt{32} & Long term public key \\+ \texttt{1024} & Friend request message as a byte string \\+ \texttt{1} & PADDING \\+ \texttt{2} & \texttt{uint16\_t} Size of the friend request message \\+ \texttt{128} & Name as a byte string \\+ \texttt{2} & \texttt{uint16\_t} Size of the name \\+ \texttt{1007} & Status message as a byte string \\+ \texttt{1} & PADDING \\+ \texttt{2} & \texttt{uint16\_t} Size of the status message \\+ \texttt{1} & \texttt{uint8\_t} User status (see also: \texttt{USERSTATUS}) \\+ \texttt{3} & PADDING \\+ \texttt{4} & \texttt{uint32\_t} Nospam (only used for sending a friend request) \\+ \texttt{8} & \texttt{uint64\_t} Last seen time \\+\end{tabular}++Status can be one of:++\begin{tabular}{l|l}+ Status & Meaning \\+ \hline+ 0 & Not a friend \\+ 1 & Friend added \\+ 2 & Friend request sent \\+ 3 & Confirmed friend \\+ 4 & Friend online \\+\end{tabular}++\begin{code}++data Friend = Friend+ { status :: Word8+ , publicKey :: PublicKey+ , friendRequest :: BS.ByteString+ , name :: BS.ByteString+ , statusMessage :: BS.ByteString+ , userStatus :: Word8+ , nospam :: Word32+ , lastSeenTime :: Word64+ }+ deriving (Eq, Show, Read)++maxFriendRequestLen :: Int+maxFriendRequestLen = 1024++maxNameLen :: Int+maxNameLen = 128++maxStatusMessageLen :: Int+maxStatusMessageLen = 1007++instance Binary Friend where+ get = do+ status <- Get.getWord8+ publicKey <- get+ friendRequest' <- Get.getByteString maxFriendRequestLen+ _ <- Get.getWord8+ friendRequestLen <- Get.getWord16be+ name' <- Get.getByteString maxNameLen+ nameLen <- Get.getWord16be+ statusMessage' <- Get.getByteString maxStatusMessageLen+ _ <- Get.getWord8+ statusMessageLen <- Get.getWord16be+ userStatus <- Get.getWord8+ _ <- Get.getByteString 3+ nospam <- Get.getWord32be+ lastSeenTime <- Get.getWord64be++ let friendRequest = BS.take (fromIntegral friendRequestLen) friendRequest'+ let name = BS.take (fromIntegral nameLen) name'+ let statusMessage = BS.take (fromIntegral statusMessageLen) statusMessage'++ return Friend{..}++ put Friend {..} = do+ let friendRequestLen = BS.length friendRequest+ let friendRequest' = friendRequest+ <> BS.replicate (maxFriendRequestLen - friendRequestLen) 0++ let nameLen = BS.length name+ let name' = name+ <> BS.replicate (maxNameLen - nameLen) 0++ let statusMessageLen = BS.length statusMessage+ let statusMessage' = statusMessage+ <> BS.replicate (maxStatusMessageLen - statusMessageLen) 0++ Put.putWord8 status+ put publicKey+ Put.putByteString friendRequest'+ Put.putWord8 0+ Put.putWord16be (fromIntegral friendRequestLen)+ Put.putByteString name'+ Put.putWord16be (fromIntegral nameLen)+ Put.putByteString statusMessage'+ Put.putWord8 0+ Put.putWord16be (fromIntegral statusMessageLen)+ Put.putWord8 userStatus+ Put.putByteString "\0\0\0"+ Put.putWord32be nospam+ Put.putWord64be lastSeenTime++instance Arbitrary Friend where+ arbitrary = Friend+ <$> arbitrary+ <*> arbitrary+ <*> (BS.pack . take maxFriendRequestLen <$> arbitrary)+ <*> (BS.pack . take maxNameLen <$> arbitrary)+ <*> (BS.pack . take maxStatusMessageLen <$> arbitrary)+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary++\end{code}
+ src/Network/Tox/SaveData/Nodes.hs view
@@ -0,0 +1,18 @@+module Network.Tox.SaveData.Nodes+ ( Nodes (..)+ ) where++import Data.Binary (Binary (..))+import Network.Tox.NodeInfo.NodeInfo (NodeInfo)+import qualified Network.Tox.SaveData.Util as Util+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)++newtype Nodes = Nodes [NodeInfo]+ deriving (Eq, Show, Read)++instance Binary Nodes where+ get = Nodes <$> Util.getList+ put (Nodes xs) = mapM_ put xs++instance Arbitrary Nodes where+ arbitrary = Nodes <$> arbitrary
+ src/Network/Tox/SaveData/Util.hs view
@@ -0,0 +1,38 @@+module Network.Tox.SaveData.Util where++import Control.Monad (when)+import Data.Binary (Binary (get))+import Data.Binary.Get (Get)+import qualified Data.Binary.Get as Get+import Data.Binary.Put (Put)+import qualified Data.Binary.Put as Put+import Data.Word (Word16, Word32)+++-- | Consumes the entire stream and parses some Binary out of it in a loop.+getList :: (Binary a, Show a) => Get [a]+getList = go []+ where+ go xs = do+ isEmpty <- Get.isEmpty+ if isEmpty+ then return $ reverse xs+ else go =<< (: xs) <$> get++getSectionHeader :: Word16 -> Get (Int, Word16)+getSectionHeader sectionMagic = do+ len <- Get.getWord32le+ ty <- Get.getWord16le+ magic <- Get.getWord16le+ when (magic /= sectionMagic) $+ fail $ "wrong magic number for section: "+ ++ show magic ++ " != " ++ show sectionMagic++ return (fromIntegral len, ty)++putSectionHeader :: Word16 -> Word32 -> Word16 -> Put+putSectionHeader sectionMagic len ty = do+ Put.putWord32le len+ Put.putWord16le ty+ Put.putWord16le sectionMagic+
+ src/Network/Tox/Testing.lhs view
@@ -0,0 +1,55 @@+\chapter{Testing}++The final part of the architecture is the test protocol. We use a+\href{https://msgpack.org}{MessagePack} based RPC protocol to expose language+agnostic interfaces to internal functions. Using property based testing with+random inputs as well as specific edge case tests help ensure that an+implementation of the Tox protocol following the architecture specified in this+document is correct.++See the \href{https://github.com/msgpack/msgpack/blob/master/spec.md}{spec} of+msgpack for information on the binary representation.++\begin{code}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE Safe #-}+module Network.Tox.Testing (serve, defaultPort) where++import Control.Applicative ((<$>))+import qualified Network.MessagePack.Rpc as Rpc+import qualified Network.MessagePack.Server as Server+import System.Environment (getArgs)+import Text.Read (readMaybe)++import qualified Network.Tox.Binary as Binary+import qualified Network.Tox.Crypto.Box as Box+import qualified Network.Tox.Crypto.CombinedKey as CombinedKey+import qualified Network.Tox.Crypto.KeyPair as KeyPair+import qualified Network.Tox.Crypto.Nonce as Nonce+++defaultPort :: Int+defaultPort = 1234+++services :: [Server.Method IO]+services =+ [ Binary.decodeS+ , Binary.encodeS+ , Rpc.method Box.decryptR+ , Rpc.method Box.encryptR+ , Rpc.method CombinedKey.precomputeR+ , Rpc.method KeyPair.fromSecretKeyR+ , Rpc.method KeyPair.newKeyPairR+ , Rpc.method Nonce.incrementR+ , Rpc.method Nonce.newNonceR+ ]+++serve :: IO ()+serve = map readMaybe <$> getArgs >>= \case+ [Just port] -> Server.runServer port services+ _ -> Server.runServer defaultPort services+\end{code}++TODO(iphydf): Generate and add specifications of each test method here.
+ src/Network/Tox/Time.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE Safe #-}+module Network.Tox.Time where++import Control.Applicative ((<$>), (<*>))+import qualified System.Clock as Clock+import Test.QuickCheck.Arbitrary (Arbitrary, arbitrary)++{-------------------------------------------------------------------------------+ -+ - :: Implementation.+ -+ ------------------------------------------------------------------------------}++newtype Timestamp = Timestamp Clock.TimeSpec+ deriving (Eq, Ord, Show, Read)++newtype TimeDiff = TimeDiff Clock.TimeSpec+ deriving (Eq, Ord, Show, Read)++instance Num TimeDiff where+ TimeDiff t + TimeDiff t' = TimeDiff $ t Prelude.+ t'+ TimeDiff t - TimeDiff t' = TimeDiff $ t Prelude.- t'+ TimeDiff t * TimeDiff t' = TimeDiff $ t * t'+ negate (TimeDiff t) = TimeDiff $ negate t+ abs (TimeDiff t) = TimeDiff $ abs t+ signum (TimeDiff t) = TimeDiff $ signum t+ fromInteger = TimeDiff . fromInteger++seconds :: Integer -> TimeDiff+seconds s = TimeDiff $ Clock.TimeSpec (fromIntegral s) 0++milliseconds :: Integer -> TimeDiff+milliseconds = TimeDiff . Clock.TimeSpec 0 . (*10^(6::Integer)) . fromIntegral++getTime :: IO Timestamp+getTime = Timestamp <$> Clock.getTime Clock.Monotonic++(-) :: Timestamp -> Timestamp -> TimeDiff+Timestamp t - Timestamp t' = TimeDiff $ t Prelude.- t'++(+) :: Timestamp -> TimeDiff -> Timestamp+Timestamp t + TimeDiff t' = Timestamp $ t Prelude.+ t'++{-------------------------------------------------------------------------------+ -+ - :: Tests.+ -+ ------------------------------------------------------------------------------}++instance Arbitrary Timestamp+ where arbitrary = (Timestamp <$>) $ Clock.TimeSpec <$> arbitrary <*> arbitrary++instance Arbitrary TimeDiff+ where arbitrary = (TimeDiff <$>) $ Clock.TimeSpec <$> arbitrary <*> arbitrary
+ src/Network/Tox/Timed.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE Safe #-}+{-# LANGUAGE UndecidableInstances #-}++module Network.Tox.Timed where++import Control.Monad (Monad)+import Control.Monad.Random (RandT)+import Control.Monad.Reader (ReaderT)+import Control.Monad.RWS (RWST)+import Control.Monad.State (StateT)+import Control.Monad.Trans (lift)+import Control.Monad.Writer (WriterT)+import Data.Monoid (Monoid)++import Network.Tox.Time (Timestamp)++class Monad m => Timed m where+ askTime :: m Timestamp++instance Timed m => Timed (ReaderT r m) where+ askTime = lift askTime+instance (Monoid w, Timed m) => Timed (WriterT w m) where+ askTime = lift askTime+instance Timed m => Timed (StateT s m) where+ askTime = lift askTime+instance (Monoid w, Timed m) => Timed (RWST r w s m) where+ askTime = lift askTime+instance Timed m => Timed (RandT s m) where+ askTime = lift askTime
+ src/Network/Tox/TimedT.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE Trustworthy #-}++module Network.Tox.TimedT where++import Control.Applicative (Applicative)+import Control.Monad (Monad)+import Control.Monad.IO.Class (MonadIO)+import Control.Monad.Reader (ReaderT, ask, runReaderT)+import Control.Monad.State (MonadState)+import Control.Monad.Trans (MonadTrans)+import Control.Monad.Writer (MonadWriter)++import Network.Tox.Crypto.Keyed (Keyed)+import Network.Tox.Network.MonadRandomBytes (MonadRandomBytes)+import Network.Tox.Network.Networked (Networked)+import Network.Tox.Time (Timestamp)+import Network.Tox.Timed (Timed (..))++newtype TimedT m a = TimedT (ReaderT Timestamp m a)+ deriving (Monad, Applicative, Functor, MonadState s, MonadWriter w+ , MonadRandomBytes, MonadTrans, MonadIO, Networked, Keyed)++runTimedT :: TimedT m a -> Timestamp -> m a+runTimedT (TimedT m) = runReaderT m++instance Monad m => Timed (TimedT m) where+ askTime = TimedT ask
+ test/Data/Result.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE Safe #-}+module Data.Result+ ( Result (..)+ ) where++import Control.Applicative (Alternative (..), Applicative (..), (<$>),+ (<*>))+import Control.Monad.Fail (MonadFail (..))+import Data.Foldable (Foldable)+import Data.Traversable (Traversable)++data Result a+ = Success a+ | Failure String+ deriving (Read, Show, Eq, Functor, Foldable, Traversable)++instance Applicative Result where+ pure = Success++ Success f <*> x = fmap f x+ Failure msg <*> _ = Failure msg++instance Alternative Result where+ empty = Failure "empty alternative"++ s@Success {} <|> _ = s+ _ <|> r = r++instance Monad Result where+ return = Success+ fail = Failure++ Success x >>= f = f x+ Failure msg >>= _ = Failure msg++instance MonadFail Result where+ fail = Failure
− test/Network/Tox/C/ToxSpec.hs
@@ -1,119 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}-{-# LANGUAGE Trustworthy #-}-module Network.Tox.C.ToxSpec where--import Control.Applicative ((<$>), (<*>))-import qualified Crypto.Saltine.Internal.ByteSizes as Sodium (boxPK, boxSK)-import qualified Data.ByteString as BS-import Data.ByteString.Arbitrary (fromABS)-import Data.Default.Class (def)-import Test.Hspec-import Test.QuickCheck-import Test.QuickCheck.Arbitrary (arbitraryBoundedEnum,- genericShrink)--import qualified Network.Tox.C as C---instance Arbitrary C.ProxyType where- shrink = genericShrink- arbitrary = arbitraryBoundedEnum--instance Arbitrary C.SavedataType where- shrink = genericShrink- arbitrary = arbitraryBoundedEnum--instance Arbitrary BS.ByteString where- shrink bs = if BS.null bs then [] else BS.inits bs- arbitrary = fromABS <$> arbitrary---- | Ensure that the hostname has a chance of being valid.-filterHost :: C.Options -> C.Options-filterHost o@C.Options{C.proxyHost = h} = o{C.proxyHost = filter (`elem` hostChars) h}- where- hostChars = ".-_" ++ ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z']--instance Arbitrary C.Options where- shrink = map filterHost . genericShrink- arbitrary = fmap filterHost $ C.Options- <$> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary---getRight :: (Monad m, Show a) => Either a b -> m b-getRight (Left l) = fail $ show l-getRight (Right r) = return r---must :: Show a => IO (Either a b) -> IO b-must = (getRight =<<)---spec :: Spec-spec = do- describe "tox_version_is_compatible" $- it "is compatible with the major/minor/patch of the linked library" $- C.tox_version_is_compatible- C.tox_version_major- C.tox_version_minor- C.tox_version_patch- `shouldBe` True-- describe "Constants" $- it "has constants equal to the hstox expected key size" $ do- fromIntegral C.tox_public_key_size `shouldBe` Sodium.boxPK- fromIntegral C.tox_secret_key_size `shouldBe` Sodium.boxSK- C.tox_address_size `shouldBe` C.tox_public_key_size + 6- C.tox_max_name_length `shouldBe` 128- C.tox_max_status_message_length `shouldBe` 1007- C.tox_max_friend_request_length `shouldBe` 1016- C.tox_max_message_length `shouldBe` C.tox_max_custom_packet_size - 1- C.tox_max_custom_packet_size `shouldBe` 1373- C.tox_max_filename_length `shouldBe` 255- C.tox_hash_length `shouldBe` C.tox_file_id_length-- describe "Options" $ do- it "can be marshalled to C and back" $- property $ \options -> do- res <- C.withOptions options C.peekToxOptions- res `shouldBe` Right options-- it "is saved correctly by pokeToxOptions" $- property $ \options0 options1 -> do- res <- C.withOptions options0 $ \ptr -> do- C.pokeToxOptions options1 ptr (return ())- C.peekToxOptions ptr- res `shouldBe` Right options0-- it "has a 'def' that is equivalent to the C default options" $ do- res <- C.withToxOptions C.peekToxOptions- res `shouldBe` Right def-- describe "nospam" $- it "can be retrieved after being set" $- property $ \nospam ->- must $ C.withDefaultTox $ \tox -> do- C.tox_self_set_nospam tox nospam- nospam' <- C.tox_self_get_nospam tox- nospam' `shouldBe` nospam-- describe "public key" $ do- it "is a prefix of the address" $- must $ C.withDefaultTox $ \tox -> do- pk <- C.toxSelfGetPublicKey tox- addr <- C.toxSelfGetAddress tox- BS.unpack addr `shouldStartWith` BS.unpack pk-- it "is not equal to the secret key" $- must $ C.withDefaultTox $ \tox -> do- pk <- C.toxSelfGetPublicKey tox- sk <- C.toxSelfGetSecretKey tox- pk `shouldNotBe` sk
− test/Network/Tox/CSpec.hs
@@ -1,96 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE Trustworthy #-}-module Network.Tox.CSpec where--import Control.Applicative ((<$>))-import Control.Concurrent (threadDelay)-import Control.Concurrent.MVar (MVar, newMVar, readMVar)-import Control.Exception (bracket)-import Control.Monad (when)-import qualified Data.ByteString as BS-import qualified Data.ByteString.Base16 as Base16-import Data.String (fromString)-import Foreign.StablePtr (StablePtr, freeStablePtr,- newStablePtr)-import Foreign.Storable (Storable (..))-import Test.Hspec--import qualified Network.Tox.C as C---bootstrapKey :: BS.ByteString-bootstrapKey =- fst . Base16.decode . fromString $- "15E9C309CFCB79FDDF0EBA057DABB49FE15F3803B1BFF06536AE2E5BA5E4690E"--bootstrapHost :: String-bootstrapHost = "tox.ngc.zone"---options :: C.Options-options = C.Options- { C.ipv6Enabled = True- , C.udpEnabled = True- , C.proxyType = C.ProxyTypeNone- , C.proxyHost = ""- , C.proxyPort = 0- , C.startPort = 33445- , C.endPort = 33545- , C.tcpPort = 3128- , C.savedataType = C.SavedataTypeNone- , C.savedataData = BS.empty- }---while :: IO Bool -> IO () -> IO ()-while cond io = do- continue <- cond- when continue $ io >> while cond io---getRight :: (Monad m, Show a) => Either a b -> m b-getRight (Left l) = fail $ show l-getRight (Right r) = return r---must :: Show a => IO (Either a b) -> IO b-must = (getRight =<<)---newtype UserData = UserData Int- deriving (Eq, Storable, Read, Show)--instance C.CHandler UserData where- cSelfConnectionStatus _ conn ud = do- print conn- print ud- return $ UserData 4321---withStablePtr :: a -> (StablePtr a -> IO b) -> IO b-withStablePtr x = bracket (newStablePtr x) freeStablePtr---toxIterate :: MVar a -> C.Tox a -> IO ()-toxIterate ud tox =- withStablePtr ud (C.tox_iterate tox)---spec :: Spec-spec =- describe "toxcore" $- it "can bootstrap" $- must $ C.withOptions options $ \optPtr ->- must $ C.withTox optPtr $ \tox -> do- must $ C.toxBootstrap tox bootstrapHost 33445 bootstrapKey- must $ C.toxAddTcpRelay tox bootstrapHost 33445 bootstrapKey-- C.withCHandler tox $ do- ud <- newMVar (UserData 1234)- while ((/= UserData 4321) <$> readMVar ud) $ do- toxIterate ud tox- putStrLn "tox_iterate"- interval <- C.tox_iteration_interval tox- threadDelay $ fromIntegral $ interval * 10000- putStrLn "done"
+ test/Network/Tox/Crypto/BoxSpec.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.Crypto.BoxSpec where++import Control.Monad.IO.Class (liftIO)+import qualified Data.ByteString as ByteString+import Data.Proxy (Proxy (..))+import qualified Data.Result as R+import Network.MessagePack.Rpc (local)+import Test.Hspec+import Test.QuickCheck++import Network.Tox.Crypto.Box (CipherText, PlainText (..))+import qualified Network.Tox.Crypto.Box as Box+import qualified Network.Tox.Crypto.CombinedKey as CombinedKey+import Network.Tox.Crypto.KeyPair (KeyPair (..))+import qualified Network.Tox.Crypto.KeyPair as KeyPair+import Network.Tox.EncodingSpec+++spec :: Spec+spec = do+ describe "Text" $ do+ rpcSpec (Proxy :: Proxy CipherText)+ rpcSpec (Proxy :: Proxy PlainText)+ binarySpec (Proxy :: Proxy CipherText)+ binarySpec (Proxy :: Proxy PlainText)+ readShowSpec (Proxy :: Proxy CipherText)+ readShowSpec (Proxy :: Proxy PlainText)++ it "encodes/decodes arbitrary texts" $+ property $ \(bytes :: String) ->+ Box.decode (Box.encode bytes) `shouldBe` Just bytes++ it "should return an error message in a monad that supports fail" $+ case Box.decode (PlainText (ByteString.pack [0x00])) of+ R.Success success -> expectationFailure $ "Expected failure, but got success: " ++ success+ R.Failure failure -> failure `shouldContain` "not enough bytes"++ describe "encrypt" $+ it "encrypts data with a random keypair" $+ property $ \nonce plainText -> do+ KeyPair sk pk <- local KeyPair.newKeyPairR+ let combinedKey = local CombinedKey.precomputeR sk pk+ let cipherText = local Box.encryptR combinedKey nonce plainText+ let decryptedText = Box.decrypt combinedKey nonce cipherText+ decryptedText `shouldBe` Just plainText++ describe "decrypt" $ do+ it "decrypts data encrypted with 'encrypt'" $+ property $ \combinedKey nonce plainText -> do+ let cipherText = local Box.encryptR combinedKey nonce plainText+ let decryptedText = local Box.decryptR combinedKey nonce cipherText+ decryptedText `shouldBe` Just plainText++ it "decrypts encrypted data with a random keypair" $+ property $ \nonce plainText -> do+ KeyPair sk pk <- local KeyPair.newKeyPairR+ let combinedKey = local CombinedKey.precomputeR sk pk+ let cipherText = Box.encrypt combinedKey nonce plainText+ let decryptedText = local Box.decryptR combinedKey nonce cipherText+ decryptedText `shouldBe` Just plainText++ it "supports communication with asymmetric keys" $+ property $ \nonce plainText -> do+ KeyPair sk1 pk1 <- local KeyPair.newKeyPairR+ KeyPair sk2 pk2 <- local KeyPair.newKeyPairR++ let key1 = local CombinedKey.precomputeR sk1 pk2+ let key2 = CombinedKey.precompute sk2 pk1+ key1 `shouldBe` key2++ let cipherText = local Box.encryptR key1 nonce plainText+ let decryptedText = Box.decrypt key2 nonce cipherText+ decryptedText `shouldBe` Just plainText
+ test/Network/Tox/Crypto/CombinedKeySpec.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.Crypto.CombinedKeySpec where++import Control.Monad.IO.Class (liftIO)+import Network.MessagePack.Rpc (local)+import Test.Hspec+import Test.QuickCheck++import qualified Network.Tox.Crypto.CombinedKey as CombinedKey+import Network.Tox.Crypto.KeyPair (KeyPair (..))+++spec :: Spec+spec =+ describe "precompute" $ do+ it "always computes the same combined key for the same public/secret keys" $+ property $ \sk pk -> do+ let ck1 = local CombinedKey.precomputeR sk pk+ let ck2 = local CombinedKey.precomputeR sk pk+ ck1 `shouldBe` ck2++ it "computes the same combined key for pk1/sk2 and pk2/sk1" $+ property $ \(KeyPair sk1 pk1) (KeyPair sk2 pk2) -> do+ let ck1 = local CombinedKey.precomputeR sk1 pk2+ let ck2 = local CombinedKey.precomputeR sk2 pk1+ ck1 `shouldBe` ck2
+ test/Network/Tox/Crypto/KeyPairSpec.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.Crypto.KeyPairSpec where++import Control.Monad.IO.Class (liftIO)+import qualified Crypto.Saltine.Class as Sodium (encode)+import Data.Proxy (Proxy (..))+import Network.MessagePack.Rpc (local)+import Test.Hspec+import Test.QuickCheck++import qualified Network.Tox.Crypto.Box as Box+import qualified Network.Tox.Crypto.CombinedKey as CombinedKey+import qualified Network.Tox.Crypto.Key as Key+import Network.Tox.Crypto.KeyPair (KeyPair (..))+import qualified Network.Tox.Crypto.KeyPair as KeyPair+import Network.Tox.EncodingSpec+++spec :: Spec+spec = do+ rpcSpec (Proxy :: Proxy KeyPair)+ readShowSpec (Proxy :: Proxy KeyPair)++ describe "newKeyPair" $ do+ it "generates different key pairs on subsequent calls" $ do+ kp1 <- local KeyPair.newKeyPairR+ kp2 <- local KeyPair.newKeyPairR+ kp1 `shouldNotBe` kp2++ it "generates different secret keys on subsequent calls" $ do+ KeyPair sk1 _ <- local KeyPair.newKeyPairR+ KeyPair sk2 _ <- local KeyPair.newKeyPairR+ sk1 `shouldNotBe` sk2++ it "generates different public keys on subsequent calls" $ do+ KeyPair _ pk1 <- local KeyPair.newKeyPairR+ KeyPair _ pk2 <- local KeyPair.newKeyPairR+ pk1 `shouldNotBe` pk2++ it "generates a public key that is different from the secret key" $ do+ kp <- local KeyPair.newKeyPairR+ Sodium.encode (KeyPair.secretKey kp) `shouldNotBe` Sodium.encode (KeyPair.publicKey kp)++ describe "fromSecretKey" $ do+ it "doesn't modify the secret key" $+ property $ \sk -> do+ let KeyPair sk' _ = local KeyPair.fromSecretKeyR sk+ sk' `shouldBe` sk++ it "never computes a public key that is equal to the secret key" $+ property $ \sk -> do+ let KeyPair _ (Key.Key pk) = local KeyPair.fromSecretKeyR sk+ Sodium.encode pk `shouldNotBe` Sodium.encode sk++ it "computes a usable public key from an invalid secret key" $+ property $ \plainText nonce -> do+ let KeyPair sk pk = local KeyPair.fromSecretKeyR $ read "\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\""+ let ck = local CombinedKey.precomputeR sk pk+ let encrypted = local Box.encryptR ck nonce plainText+ let decrypted = local Box.decryptR ck nonce encrypted+ decrypted `shouldBe` Just plainText
+ test/Network/Tox/Crypto/KeySpec.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.Crypto.KeySpec where++import Test.Hspec+import Test.QuickCheck++import qualified Crypto.Saltine.Class as Sodium+import Data.Binary (Binary)+import Data.ByteString (ByteString)+import qualified Data.ByteString as ByteString+import Data.Proxy (Proxy (..))+import qualified Data.Result as R+import Data.Typeable (Typeable)+import qualified Network.Tox.Binary as Binary+import Network.Tox.Crypto.Key (Key (..))+import qualified Network.Tox.Crypto.Key as Key+import Network.Tox.EncodingSpec+import qualified Text.Read as Read+++readMaybe :: String -> Maybe Key.PublicKey+readMaybe = Read.readMaybe+++decodeM :: Monad m => ByteString -> m Key.PublicKey+decodeM = Key.decode+++keyToInteger :: String -> Integer+keyToInteger string =+ Key.keyToInteger (read string :: Key.PublicKey)+++encodeDecodePublicKey :: Key.PublicKey -> Expectation+encodeDecodePublicKey key =+ Sodium.decode (Sodium.encode key) `shouldBe` Just key+++localEncodingSpec+ :: (Typeable a, Read a, Show a, Binary a, Arbitrary a, Eq a)+ => Proxy a -> Spec+localEncodingSpec proxy =+ describe (Binary.typeName proxy) $ do+ binarySpec proxy+ readShowSpec proxy+++spec :: Spec+spec = do+ -- PublicKey for RPC tests.+ rpcSpec (Proxy :: Proxy Key.PublicKey)++ -- All others only local tests.+ localEncodingSpec (Proxy :: Proxy Key.CombinedKey)+ localEncodingSpec (Proxy :: Proxy Key.Nonce)+ localEncodingSpec (Proxy :: Proxy Key.PublicKey)+ localEncodingSpec (Proxy :: Proxy Key.SecretKey)++ describe "IsEncoding" $+ it "decodes encoded public keys correctly" $+ property encodeDecodePublicKey++ describe "read" $ do+ it "decodes valid hex string to PublicKey" $+ let+ actual = readMaybe "\"0100000000000000000000000000000000000000000000000000000000000010\""+ Just expected = Sodium.decode $ ByteString.pack [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x10]+ in+ actual `shouldBe` Just (Key expected)++ it "decodes empty string to Nothing" $ do+ let actual = readMaybe ""+ actual `shouldBe` Nothing+ case decodeM ByteString.empty of+ R.Failure msg -> msg `shouldStartWith` "unable to decode"+ R.Success val -> expectationFailure $ "unexpected success: " ++ show val++ it "decodes valid hex string of wrong length to Nothing" $+ let actual = readMaybe "\"0110\"" in+ actual `shouldBe` Nothing++ describe "keyToInteger" $ do+ it "converts keys to Integer in big endian" $ do+ keyToInteger "\"fe00000000000000000000000000000000000000000000000000000000000000\""+ `shouldBe` 0xfe00000000000000000000000000000000000000000000000000000000000000+ keyToInteger "\"00000000000000000000000000000000000000000000000000000000000000fe\""+ `shouldBe` 0x00000000000000000000000000000000000000000000000000000000000000fe++ it "encodes all keys to positive Integers" $+ property $ \key ->+ Key.keyToInteger (key :: Key.PublicKey) `shouldSatisfy` (0 <=)
+ test/Network/Tox/Crypto/NonceSpec.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.Crypto.NonceSpec where++import Control.Monad.IO.Class (liftIO)+import Network.MessagePack.Rpc (local)+import Test.Hspec+import Test.QuickCheck++import qualified Network.Tox.Crypto.Nonce as Nonce+++spec :: Spec+spec = do+ describe "newNonce" $+ it "generates a different nonce on subsequent calls to newNonce" $ do+ nonce1 <- local Nonce.newNonceR+ nonce2 <- local Nonce.newNonceR+ liftIO $ nonce1 `shouldNotBe` nonce2++ describe "nudge" $+ it "creates a nonce that is different from the passed nonce" $+ property $ \nonce ->+ Nonce.nudge nonce `shouldNotBe` nonce++ describe "increment" $ do+ it "generates a different nonce for arbitrary nonces" $+ property $ \nonce -> do+ let incremented = local Nonce.incrementR nonce+ incremented `shouldNotBe` nonce++ it "increments a 0 nonce to 1" $ do+ let nonce = read "\"000000000000000000000000000000000000000000000000\""+ let nonce' = read "\"000000000000000000000000000000000000000000000001\""+ let incremented = local Nonce.incrementR nonce+ incremented `shouldBe` nonce'++ it "increments a max nonce to 0" $ do+ let nonce = read "\"ffffffffffffffffffffffffffffffffffffffffffffffff\""+ let nonce' = read "\"000000000000000000000000000000000000000000000000\""+ let incremented = local Nonce.incrementR nonce+ incremented `shouldBe` nonce'++ it "increments a max-1 nonce to max" $ do+ let nonce = read "\"fffffffffffffffffffffffffffffffffffffffffffffffe\""+ let nonce' = read "\"ffffffffffffffffffffffffffffffffffffffffffffffff\""+ let incremented = local Nonce.incrementR nonce+ incremented `shouldBe` nonce'++ it "increments a little endian max-1 nonce to little endian 255" $ do+ let nonce = read "\"feffffffffffffffffffffffffffffffffffffffffffffff\""+ let nonce' = read "\"ff0000000000000000000000000000000000000000000000\""+ let incremented = local Nonce.incrementR nonce+ incremented `shouldBe` nonce'
+ test/Network/Tox/CryptoSpec.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.CryptoSpec where++import Test.Hspec++import qualified Network.Tox.Crypto as Crypto+++spec :: Spec+spec = return ()
+ test/Network/Tox/DHT/ClientListSpec.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.ClientListSpec where++import Test.Hspec+import Test.QuickCheck++import Control.Monad (unless, when)+import Data.List (sort, sortOn)+import qualified Data.Map as Map+import Data.Ord (comparing)+import Data.Proxy (Proxy (..))+import Network.Tox.Crypto.Key (PublicKey)+import Network.Tox.DHT.ClientList (ClientList)+import qualified Network.Tox.DHT.ClientList as ClientList+import qualified Network.Tox.DHT.Distance as Distance+import Network.Tox.EncodingSpec+import qualified Network.Tox.NodeInfo.NodeInfo as NodeInfo+++spec :: Spec+spec = do+ readShowSpec (Proxy :: Proxy ClientList)++ it "has no more than maxSize elements" $+ property $ \clientList ->+ Map.size (ClientList.nodes clientList) `shouldSatisfy` (<= ClientList.maxSize clientList)++ it "removing a node twice has no effect" $+ property $ \baseKey time nodeInfo size ->+ let+ empty = ClientList.empty baseKey+ afterAdd = ClientList.addNode time nodeInfo $ empty size+ afterRemove0 = ClientList.removeNode (NodeInfo.publicKey nodeInfo) afterAdd+ afterRemove1 = ClientList.removeNode (NodeInfo.publicKey nodeInfo) afterRemove0+ in+ afterRemove0 `shouldBe` afterRemove1++ it "adding a node twice has no effect" $+ property $ \baseKey time nodeInfo size ->+ let+ empty = ClientList.empty baseKey+ afterAdd0 = ClientList.addNode time nodeInfo $ empty size+ afterAdd1 = ClientList.addNode time nodeInfo afterAdd0+ in+ afterAdd0 `shouldBe` afterAdd1++ it "adding a non-viable node has no effect" $+ property $ \clientList time nodeInfo ->+ let+ viable = ClientList.viable nodeInfo clientList+ afterAdd = ClientList.addNode time nodeInfo clientList+ in+ unless viable $ afterAdd `shouldBe` clientList++ describe "addNode" $+ it "keeps the k nodes closest to the base key" $+ property $ \clientList time nodeInfo ->+ let+ allNodes = (nodeInfo:) $ ClientList.nodeInfos clientList+ keptNodes = ClientList.nodeInfos $ ClientList.addNode time nodeInfo clientList+ nodeDistance node = Distance.xorDistance (ClientList.baseKey clientList) (NodeInfo.publicKey node)+ sortNodes = sortOn nodeDistance+ in+ take (ClientList.maxSize clientList) (sortNodes allNodes) `shouldBe` sortNodes keptNodes++ describe "foldNodes" $+ it "iterates over nodes in order of distance from the base key" $+ property $ \clientList ->+ let+ nodes = reverse $ ClientList.foldNodes (flip (:)) [] clientList+ nodeDistance node = Distance.xorDistance (ClientList.baseKey clientList) (NodeInfo.publicKey node)+ sortNodes = sortOn nodeDistance+ in+ nodes `shouldBe` sortNodes nodes
+ test/Network/Tox/DHT/DhtPacketSpec.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.DhtPacketSpec where++import Test.Hspec+import Test.QuickCheck++import Data.Binary (Binary)+import qualified Data.Binary as Binary (get, put)+import qualified Data.Binary.Get as Binary (runGet)+import qualified Data.Binary.Put as Binary (runPut)+import Data.Proxy (Proxy (..))+import Network.Tox.Crypto.Key (Nonce)+import Network.Tox.Crypto.KeyPair (KeyPair (..))+import Network.Tox.DHT.DhtPacket (DhtPacket (..))+import qualified Network.Tox.DHT.DhtPacket as DhtPacket+import Network.Tox.EncodingSpec+import Network.Tox.NodeInfo.NodeInfo (NodeInfo)+++encodeAndDecode :: (Binary a, Binary b) => KeyPair -> KeyPair -> Nonce -> a -> Maybe b+encodeAndDecode senderKeyPair receiverKeyPair nonce payload =+ let+ KeyPair _ receiverPublicKey = receiverKeyPair+ packet = DhtPacket.encode senderKeyPair receiverPublicKey nonce payload+ packet' = Binary.runGet Binary.get $ Binary.runPut $ Binary.put packet+ in+ DhtPacket.decode receiverKeyPair packet'+++encodeAndDecodeString :: KeyPair -> KeyPair -> Nonce -> String -> Maybe String+encodeAndDecodeString = encodeAndDecode+++encodeCharAndDecodeString :: KeyPair -> KeyPair -> Nonce -> Char -> Maybe String+encodeCharAndDecodeString = encodeAndDecode+++encodeIntAndDecodeNodeInfo :: KeyPair -> KeyPair -> Nonce -> Int -> Maybe NodeInfo+encodeIntAndDecodeNodeInfo = encodeAndDecode+++spec :: Spec+spec = do+ rpcSpec (Proxy :: Proxy DhtPacket)+ binarySpec (Proxy :: Proxy DhtPacket)+ readShowSpec (Proxy :: Proxy DhtPacket)++ it "encodes and decodes packets" $+ property $ \senderKeyPair receiverKeyPair nonce payload ->+ encodeAndDecodeString senderKeyPair receiverKeyPair nonce payload `shouldBe` Just payload++ it "fails to decode packets with the wrong secret key" $+ property $ \senderKeyPair (KeyPair _ receiverPublicKey) badSecretKey nonce payload ->+ encodeAndDecodeString senderKeyPair (KeyPair badSecretKey receiverPublicKey) nonce payload `shouldBe` Nothing++ it "fails to decode packets with the wrong payload type (Partial)" $+ property $ \senderKeyPair receiverKeyPair nonce payload ->+ encodeCharAndDecodeString senderKeyPair receiverKeyPair nonce payload `shouldBe` Nothing++ it "fails to decode packets with the wrong payload type (Fail)" $+ property $ \senderKeyPair receiverKeyPair nonce payload ->+ encodeIntAndDecodeNodeInfo senderKeyPair receiverKeyPair nonce payload `shouldBe` Nothing++ it "should decode empty CipherText correctly" $+ expectDecoded+ [ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0+ , 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0+ , 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0+ ] $+ DhtPacket+ (read "\"0000000000000000000000000000000000000000000000000000000000000000\"")+ (read "\"000000000000000000000000000000000000000000000000\"")+ (read "\"00000000000000000000000000000000\"")
+ test/Network/Tox/DHT/DhtRequestPacketSpec.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.DhtRequestPacketSpec where++import Test.Hspec+import Test.QuickCheck++import Data.Proxy (Proxy (..))+import Network.Tox.DHT.DhtRequestPacket (DhtRequestPacket (..))+import Network.Tox.EncodingSpec+++spec :: Spec+spec = do+ rpcSpec (Proxy :: Proxy DhtRequestPacket)+ binarySpec (Proxy :: Proxy DhtRequestPacket)+ readShowSpec (Proxy :: Proxy DhtRequestPacket)
+ test/Network/Tox/DHT/DhtStateSpec.hs view
@@ -0,0 +1,119 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.DhtStateSpec where++import Test.Hspec+import Test.QuickCheck++import Control.Monad (unless, when)+import Data.List (nub, sort)+import Data.Proxy (Proxy (..))+import qualified Data.Set as Set++import qualified Network.Tox.Crypto.KeyPair as KeyPair+import Network.Tox.DHT.DhtState (DhtState)+import qualified Network.Tox.DHT.DhtState as DhtState+import qualified Network.Tox.DHT.Distance as Distance+import qualified Network.Tox.DHT.NodeList as NodeList+import Network.Tox.EncodingSpec+import qualified Network.Tox.NodeInfo.NodeInfo as NodeInfo+++spec :: Spec+spec = do+ readShowSpec (Proxy :: Proxy DhtState)++ it "the state can never contain itself" $+ property $ \keyPair time nodeInfo ->+ let+ dhtState = DhtState.empty time keyPair+ afterAdd = DhtState.addNode time+ nodeInfo { NodeInfo.publicKey = KeyPair.publicKey keyPair }+ dhtState+ in+ afterAdd `shouldBe` dhtState++ describe "adding a node that was not yet contained" $ do+ it "should result in a different state" $+ property $ \keyPair time nodeInfo ->+ let+ dhtState = DhtState.empty time keyPair+ afterAdd = DhtState.addNode time nodeInfo dhtState+ in+ unless (DhtState.containsNode (NodeInfo.publicKey nodeInfo) dhtState) $+ afterAdd `shouldNotBe` dhtState++ it "and removing it yields the same state" $+ property $ \keyPair time nodeInfo ->+ let+ dhtState = DhtState.empty time keyPair+ afterAdd = DhtState.addNode time nodeInfo dhtState+ afterRemove = DhtState.removeNode (NodeInfo.publicKey nodeInfo) afterAdd+ in+ unless (DhtState.containsNode (NodeInfo.publicKey nodeInfo) dhtState) $+ afterRemove `shouldBe` dhtState++ describe "adding a node" $+ it "and adding it again does not change the state twice" $+ property $ \keyPair time nodeInfo ->+ let+ dhtState = DhtState.empty time keyPair+ afterAdd1 = DhtState.addNode time nodeInfo dhtState+ afterAdd2 = DhtState.addNode time nodeInfo afterAdd1+ in+ afterAdd1 `shouldBe` afterAdd2++ describe "adding a search node" $ do+ it "should result in a different state" $+ property $ \keyPair time publicKey ->+ let+ dhtState = DhtState.empty time keyPair+ afterAdd = DhtState.addSearchKey time publicKey dhtState+ in+ afterAdd `shouldNotBe` dhtState++ it "and removing it yields the same state" $+ property $ \keyPair time publicKey ->+ let+ dhtState = DhtState.empty time keyPair+ afterAdd = DhtState.addSearchKey time publicKey dhtState+ afterRemove = DhtState.removeSearchKey publicKey afterAdd+ in+ afterRemove `shouldBe` dhtState++ it "and adding it again does not change the state twice" $+ property $ \keyPair time publicKey ->+ let+ dhtState = DhtState.empty time keyPair+ afterAdd1 = DhtState.addSearchKey time publicKey dhtState+ afterAdd2 = DhtState.addSearchKey time publicKey afterAdd1+ in+ afterAdd1 `shouldBe` afterAdd2++ it "and adding a node info for it will not add it to the search entry's Client List" $+ property $ \keyPair time nodeInfo ->+ let+ dhtState = DhtState.empty time keyPair+ afterAddSearchKey = DhtState.addSearchKey time+ (NodeInfo.publicKey nodeInfo)+ dhtState+ in+ DhtState.size (DhtState.addNode time nodeInfo afterAddSearchKey)+ `shouldBe`+ DhtState.size (DhtState.addNode time nodeInfo dhtState)++ describe "takeClosestNodesTo" $ do+ it "returns the requested number of nodes if there are enough nodes in the state" $+ property $ \dhtState n publicKey -> n >= 0 ==>+ let+ taken = DhtState.takeClosestNodesTo n publicKey dhtState+ nodes = NodeList.foldNodes (flip Set.insert) Set.empty dhtState+ in+ when (Set.size nodes >= n) $ length taken `shouldBe` n++ it "returns distinct nodes sorted by distance from the given key" $+ property $ \dhtState n publicKey -> n >= 0 ==>+ let+ taken = DhtState.takeClosestNodesTo n publicKey dhtState+ dists = map (Distance.xorDistance publicKey . NodeInfo.publicKey) taken+ in+ dists `shouldBe` (nub . sort) dists
+ test/Network/Tox/DHT/DistanceSpec.lhs view
@@ -0,0 +1,169 @@+\begin{code}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.DistanceSpec where++import Test.Hspec+import Test.QuickCheck++import Data.Monoid (Monoid, mappend, mempty)+import Data.Proxy (Proxy (..))+import qualified Network.Tox.Crypto.Key as Key+import Network.Tox.DHT.Distance+import Network.Tox.EncodingSpec++\end{code}++XOR is a valid metric, i.e. it satisfies the required conditions:++\begin{enumerate}+ \item Non-negativity \texttt{distance(x, y) >= 0}: Since public keys are+ Crypto Numbers, which are by definition non-negative, their XOR is necessarily+ non-negative.+ \item Identity of indiscernibles \texttt{distance(x, y) == 0} iff \texttt{x ==+ y}: The XOR of two integers is zero iff they are equal.+ \item Symmetry \texttt{distance(x, y) == distance(y, x)}: XOR is a symmetric+ operation.+ \item Subadditivity \texttt{distance(x, z) <= distance(x, y) + distance(y,+ z)}: follows from associativity, since \texttt{x XOR z = x XOR (y XOR y) XOR+ z = distance(x, y) XOR distance(y, z)} which is not greater than+ \texttt{distance(x, y) + distance(y, z)}.+\end{enumerate}++In addition, XOR has other useful properties:++\begin{itemize}+ \item Unidirectionality: given the key \texttt{x} and the distance \texttt{d}+ there exist one and only one key \texttt{y} such that \texttt{distance(x,+ y) = d}.++ The implication is that repeated lookups are likely to pass along the same+ way and thus caching makes sense.++ Source:+ \href{http://pdos.csail.mit.edu/~petar/papers/maymounkov-kademlia-lncs.pdf}{maymounkov-kademlia}+\end{itemize}++\begin{code}++metricSpec :: ( Eq a, Arbitrary a, Show a+ , Eq b, Ord b, Monoid b, Show b)+ => (a -> a -> b) -> Spec+metricSpec d = do+ it "satisfies non-negativity" $+ property $ \x y ->+ d x y > mempty++ it "satisfies identity of indiscernibles" $+ property $ \x y ->+ d x y == mempty `shouldBe` x == y++ it "satisfies symmetry" $+ property $ \x y ->+ d x y `shouldBe` d y x++ it "satisfies triangle inequality" $+ property $ \x y z ->+ d x z <= d x y `mappend` d y z+++zeroKey :: Key.PublicKey+zeroKey = read "\"0000000000000000000000000000000000000000000000000000000000000000\""+++spec :: Spec+spec = do+ readShowSpec (Proxy :: Proxy Distance)++ describe "xorDistance" $ do+ metricSpec xorDistance++ it "should not partition the network at 0x7f/0x80" $+ let+ o = zeroKey+ x = read "\"8000000000000000000000000000000000000000000000000000000000000000\""+ y = read "\"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\""++ ox = xorDistance o x+ oy = xorDistance o y+ in++ oy < ox+\end{code}++Example: Given three nodes with keys 2, 5, and 6:++\begin{itemize}+ \item \texttt{2 XOR 5 = 7}+ \item \texttt{2 XOR 6 = 4}+ \item \texttt{5 XOR 2 = 7}+ \item \texttt{5 XOR 6 = 3}+ \item \texttt{6 XOR 2 = 4}+ \item \texttt{6 XOR 5 = 3}+\end{itemize}++The closest node from both 2 and 5 is 6. The closest node from 6 is 5 with+distance 3. This example shows that a key that is close in terms of integer+addition may not necessarily be close in terms of XOR.++\begin{code}++ it "should yield the values from the example from the spec" $+ let+ k1 = read "\"0000000000000000000000000000000000000000000000000000000000000002\""+ k2 = read "\"0000000000000000000000000000000000000000000000000000000000000005\""+ k3 = read "\"0000000000000000000000000000000000000000000000000000000000000006\""+ in do++ xorDistance k1 k2 `shouldBe` Distance 7+ xorDistance k1 k3 `shouldBe` Distance 4+ xorDistance k2 k1 `shouldBe` Distance 7+ xorDistance k2 k3 `shouldBe` Distance 3+ xorDistance k3 k1 `shouldBe` Distance 4+ xorDistance k3 k2 `shouldBe` Distance 3++ describe "log2" $ do+ it "should result in 0 <= value for any Distance" $+ property $ \distance ->+ log2 distance `shouldSatisfy` \case+ Nothing -> True+ Just value -> 0 <= value++ it "should result in 0 <= value < 256 for public key distances" $+ property $ \pk1 pk2 ->+ log2 (xorDistance pk1 pk2) `shouldSatisfy` \case+ Nothing -> True+ Just value -> 0 <= value && value < 256++ it "should result in 255 for maximum distance" $+ let+ k1 = read "\"0000000000000000000000000000000000000000000000000000000000000000\""+ k2 = read "\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\""+ in+ log2 (xorDistance k1 k2) `shouldBe` Just 255++ it "should result in 255 for the highest bit set" $+ let+ k1 = read "\"0000000000000000000000000000000000000000000000000000000000000000\""+ k2 = read "\"8000000000000000000000000000000000000000000000000000000000000000\""+ in+ log2 (xorDistance k1 k2) `shouldBe` Just 255++ it "should result in 254 for the highest-but-one bit set" $+ let+ k1 = read "\"0000000000000000000000000000000000000000000000000000000000000000\""+ k2 = read "\"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\""+ in+ log2 (xorDistance k1 k2) `shouldBe` Just 254++ it "should result in Nothing for distance 0" $+ let+ k = read "\"0000000000000000000000000000000000000000000000000000000000000000\""+ in+ log2 (xorDistance k k) `shouldBe` Nothing++ describe "rebaseDistance" $+ it "should satisfy: rebaseDistance a b (xorDistance a c) == xorDistance b c" $+ property $ \a b c ->+ rebaseDistance a b (xorDistance a c) `shouldBe` xorDistance b c+\end{code}
+ test/Network/Tox/DHT/KBucketsSpec.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.KBucketsSpec where++import Test.Hspec+import Test.QuickCheck++import Control.Monad (unless, when)+import Data.List (sort, sortOn)+import qualified Data.Map as Map+import Data.Ord (comparing)+import Data.Proxy (Proxy (..))+import Network.Tox.Crypto.Key (PublicKey)+import Network.Tox.DHT.ClientList (ClientList)+import qualified Network.Tox.DHT.ClientList as ClientList+import qualified Network.Tox.DHT.Distance as Distance+import Network.Tox.DHT.KBuckets (KBuckets)+import qualified Network.Tox.DHT.KBuckets as KBuckets+import qualified Network.Tox.DHT.NodeList as NodeList+import Network.Tox.EncodingSpec+import Network.Tox.NodeInfo.NodeInfo (NodeInfo)+import qualified Network.Tox.NodeInfo.NodeInfo as NodeInfo+++makeInputKey :: Int -> Char -> PublicKey+makeInputKey pos digit =+ read $ "\"" ++ map (const '0') [0 .. pos - 1] ++ digit : map (const '0') [pos .. 63] ++ "\""+++getAllBuckets :: KBuckets -> [[NodeInfo]]+getAllBuckets kBuckets =+ map ClientList.nodeInfos (Map.elems (KBuckets.buckets kBuckets))+++spec :: Spec+spec = do+ readShowSpec (Proxy :: Proxy KBuckets)++ it "does not accept adding a NodeInfo with the baseKey as publicKey" $+ property $ \kBuckets time nodeInfo ->+ KBuckets.addNode time nodeInfo { NodeInfo.publicKey = KBuckets.baseKey kBuckets } kBuckets+ `shouldBe`+ kBuckets++ it "adding a node to an empty k-buckets always succeeds if baseKey <> nodeKey" $+ property $ \baseKey time nodeInfo ->+ let+ empty = KBuckets.empty baseKey+ kBuckets = KBuckets.addNode time nodeInfo empty+ in+ if baseKey == NodeInfo.publicKey nodeInfo+ then kBuckets `shouldBe` empty+ else kBuckets `shouldNotBe` empty++ it "removing a node twice has no effect" $+ property $ \baseKey time nodeInfo ->+ let+ empty = KBuckets.empty baseKey+ afterAdd = KBuckets.addNode time nodeInfo empty+ afterRemove0 = KBuckets.removeNode (NodeInfo.publicKey nodeInfo) afterAdd+ afterRemove1 = KBuckets.removeNode (NodeInfo.publicKey nodeInfo) afterRemove0+ in+ afterRemove0 `shouldBe` afterRemove1++ it "adding a node twice has no effect" $+ property $ \baseKey time nodeInfo ->+ let+ empty = KBuckets.empty baseKey+ afterAdd0 = KBuckets.addNode time nodeInfo empty+ afterAdd1 = KBuckets.addNode time nodeInfo afterAdd0+ in+ afterAdd0 `shouldBe` afterAdd1++ it "adding a non-viable node has no effect" $+ property $ \(kBuckets::KBuckets) time nodeInfo ->+ let+ viable = KBuckets.viable nodeInfo kBuckets+ afterAdd = KBuckets.addNode time nodeInfo kBuckets+ in+ unless viable $ afterAdd `shouldBe` kBuckets++ it "never contains a NodeInfo with the public key equal to the base key" $+ property $ \kBuckets ->+ notElem (KBuckets.baseKey kBuckets) $ concatMap (map NodeInfo.publicKey) $ getAllBuckets kBuckets++ describe "each bucket list" $ do+ it "has maximum size bucketSize" $+ property $ \kBuckets ->+ mapM_+ (`shouldSatisfy` (== KBuckets.bucketSize kBuckets) . ClientList.maxSize)+ . Map.elems $ KBuckets.buckets kBuckets+ it "has base key baseKey" $+ property $ \kBuckets ->+ mapM_+ (`shouldSatisfy` (== KBuckets.baseKey kBuckets) . ClientList.baseKey)+ . Map.elems $ KBuckets.buckets kBuckets++ describe "bucketIndex" $ do+ it "returns an integer between 0 and 255 for any two non-equal keys" $+ property $ \k1 k2 ->+ when (k1 /= k2) $+ -- In our implementation, this is guaranteed by the type system, as+ -- we're using Word8, which can only represent values in this range.+ KBuckets.bucketIndex k1 k2 `shouldSatisfy` \case+ Nothing -> False+ Just index -> index >= 0 && index <= 255++ it "is undefined for two equal keys" $+ property $ \k ->+ KBuckets.bucketIndex k k `shouldBe` Nothing++ it "returns a larger index for smaller distances and smaller index for larger distances" $+ property $ \k1 k2 k3 ->+ let+ d = Distance.xorDistance k1+ i = KBuckets.bucketIndex k1+ in+ if d k2 <= d k3+ then i k2 >= i k3+ else i k2 <= i k3++ it "produces indices 0..255 for each bit set in the key" $+ let+ zeroKey = read "\"0000000000000000000000000000000000000000000000000000000000000000\""+ inputs = zeroKey : concatMap (\pos -> map (makeInputKey pos) ['8', '4', '2', '1']) [0 .. 63]+ outputs = Nothing : map Just [0 .. 255]+ in+ map (KBuckets.bucketIndex zeroKey) inputs `shouldBe` outputs++ describe "foldNodes" $+ it "iterates over nodes in order of distance from the base key" $+ property $ \kBuckets ->+ let+ nodes = reverse $ NodeList.foldNodes (flip (:)) [] kBuckets+ nodeDistance node = Distance.xorDistance (KBuckets.baseKey kBuckets) (NodeInfo.publicKey node)+ in+ nodes `shouldBe` sortOn nodeDistance nodes
+ test/Network/Tox/DHT/NodesRequestSpec.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.NodesRequestSpec where++import Test.Hspec++import Data.Proxy (Proxy (..))+import qualified Network.Tox.Crypto.KeyPair as KeyPair+import Network.Tox.DHT.NodesRequest (NodesRequest (..))+import Network.Tox.EncodingSpec+++spec :: Spec+spec = do+ rpcSpec (Proxy :: Proxy NodesRequest)+ binarySpec (Proxy :: Proxy NodesRequest)+ readShowSpec (Proxy :: Proxy NodesRequest)++ it "has a public key" $ do+ kp <- KeyPair.newKeyPair+ let req = NodesRequest (KeyPair.publicKey kp)+ requestedKey req `shouldBe` KeyPair.publicKey kp
+ test/Network/Tox/DHT/NodesResponseSpec.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.NodesResponseSpec where++import Test.Hspec++import Data.Proxy (Proxy (..))+import Network.Tox.DHT.NodesResponse (NodesResponse)+import Network.Tox.EncodingSpec+++spec :: Spec+spec = do+ rpcSpec (Proxy :: Proxy NodesResponse)+ binarySpec (Proxy :: Proxy NodesResponse)+ readShowSpec (Proxy :: Proxy NodesResponse)
+ test/Network/Tox/DHT/OperationSpec.hs view
@@ -0,0 +1,89 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.OperationSpec where++import Test.Hspec+import Test.QuickCheck++import Control.Monad (mzero, when)+import Control.Monad.Writer (execWriterT)+import qualified Data.Map as Map+import Data.Proxy (Proxy (..))++import Network.Tox.Crypto.Key (PublicKey)+import qualified Network.Tox.Crypto.KeyPair as KeyPair+import Network.Tox.DHT.DhtState (DhtState)+import qualified Network.Tox.DHT.DhtState as DhtState+import qualified Network.Tox.DHT.Operation as Operation+import Network.Tox.EncodingSpec+import Network.Tox.NodeInfo.NodeInfo (NodeInfo)+import qualified Network.Tox.NodeInfo.NodeInfo as NodeInfo+import qualified Network.Tox.Time as Time++spec :: Spec+spec = do+ describe "a newly initialised DHT node" $ do+ it "contains no nodes" $+ property $ \time seed ->+ DhtState.size (Operation.initTestDhtState seed time) `shouldBe` 0++ it "has a search list containing initRandomSearches search entries" $+ property $ \time seed ->+ (Map.size . DhtState.dhtSearchList $ Operation.initTestDhtState seed time)+ `shouldBe` Operation.initRandomSearches++ describe "periodic nodes requests" $+ it "are not generated for an empty DHT State" $+ property $ \keyPair time time' seed ->+ let+ dhtState = DhtState.empty time keyPair+ requests = Operation.evalTestDhtNode seed time' dhtState . execWriterT $+ Operation.randomRequests >> Operation.checkNodes+ in+ requests `shouldBe` []++ describe "randomRequests" $ do+ it "generates a single Nodes Request to a node in the close list after randomRequestPeriod" $+ property $ \keyPair time (nodeInfos::[NodeInfo]) seed ->+ let+ dhtState = DhtState.empty time keyPair+ afterAdd = foldr (DhtState.addNode time) dhtState nodeInfos+ time' = time Time.+ Operation.randomRequestPeriod+ randomRequests = Operation.evalTestDhtNode seed time' afterAdd+ . execWriterT $ Operation.randomRequests+ in+ case randomRequests of+ [] -> DhtState.size dhtState `shouldBe` 0+ Operation.RequestInfo nodeInfo publicKey : rs -> do+ rs `shouldSatisfy` null+ nodeInfo `shouldSatisfy` (`elem` nodeInfos)+ publicKey `shouldBe` KeyPair.publicKey (DhtState.dhtKeyPair dhtState)++ it "generates a Nodes Request to a node in a new search list after randomRequestPeriod" $+ property $ \time publicKey dhtState (nodeInfos::[NodeInfo]) seed ->+ let+ afterSearch = DhtState.addSearchKey time publicKey dhtState+ afterAdd = foldr (DhtState.addNode time) afterSearch nodeInfos+ nodeAddedToSearch = not $ all ((== publicKey) . NodeInfo.publicKey) nodeInfos+ time' = time Time.+ Operation.randomRequestPeriod+ randomRequests = Operation.evalTestDhtNode seed time' afterAdd+ . execWriterT $ Operation.randomRequests++ requestIsForSearch (Operation.RequestInfo nodeInfo publicKey') =+ publicKey == publicKey' && nodeInfo `elem` nodeInfos &&+ NodeInfo.publicKey nodeInfo /= publicKey+ in+ when nodeAddedToSearch $+ randomRequests `shouldSatisfy` any requestIsForSearch++ describe "checkNodes" $+ it "generates a Nodes Request to a newly added node after checkPeriod" $+ property $ \time dhtState nodeInfo seed ->+ let+ viable = DhtState.viable nodeInfo dhtState+ afterAdd = DhtState.addNode time nodeInfo dhtState+ time' = time Time.+ Operation.checkPeriod+ checks = Operation.evalTestDhtNode seed time' afterAdd+ . execWriterT $ Operation.checkNodes+ in+ when viable $ map Operation.requestTo checks `shouldSatisfy` (nodeInfo `elem`)
+ test/Network/Tox/DHT/PendingRepliesSpec.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.PendingRepliesSpec where++import Test.Hspec+import Test.QuickCheck++import Network.Tox.DHT.PendingReplies (PendingReplies)+import Network.Tox.DHT.PendingReplies as PendingReplies+import Network.Tox.DHT.Stamped (Stamped)+import qualified Network.Tox.DHT.Stamped as Stamped++spec :: Spec+spec = do+ it "Accepts a response with the same RequestID iff sent since the cutoff" $+ property $ \time time' node requestID ->+ let+ expecting = PendingReplies.expectReply time node requestID Stamped.empty+ in+ fst (PendingReplies.checkExpectedReply time' node requestID expecting)+ `shouldBe` time' <= time++ it "Rejects a response with a different requestID" $+ property $ \time node requestID requestID' ->+ let+ expecting = PendingReplies.expectReply time node requestID Stamped.empty+ in+ fst (PendingReplies.checkExpectedReply time node requestID' expecting)+ `shouldBe` requestID == requestID'++ it "Doesn't accept the same response twice" $+ property $ \time node requestID ->+ let+ expecting = PendingReplies.expectReply time node requestID Stamped.empty+ accepted = snd $ PendingReplies.checkExpectedReply time node requestID expecting+ in+ fst (PendingReplies.checkExpectedReply time node requestID accepted)+ `shouldBe` False
+ test/Network/Tox/DHT/PingPacketSpec.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.PingPacketSpec where++import Test.Hspec++import Data.Proxy (Proxy (..))+import Network.Tox.DHT.PingPacket (PingPacket)+import Network.Tox.EncodingSpec+++spec :: Spec+spec = do+ rpcSpec (Proxy :: Proxy PingPacket)+ binarySpec (Proxy :: Proxy PingPacket)+ readShowSpec (Proxy :: Proxy PingPacket)
+ test/Network/Tox/DHT/RpcPacketSpec.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHT.RpcPacketSpec where++import Test.Hspec++import Data.Proxy (Proxy (..))+import Data.Word (Word64)+import Network.Tox.DHT.RpcPacket (RequestId (..), RpcPacket (..))+import Network.Tox.EncodingSpec+++spec :: Spec+spec = do+ rpcSpec (Proxy :: Proxy (RpcPacket Word64))+ binarySpec (Proxy :: Proxy (RpcPacket Word64))+ readShowSpec (Proxy :: Proxy (RpcPacket Word64))++ it "has a payload and a request ID" $ do+ let packet = RpcPacket ["heyo"] (RequestId 0x12345678)+ rpcPayload packet `shouldBe` ["heyo"]+ requestId packet `shouldBe` RequestId 0x12345678
+ test/Network/Tox/DHTSpec.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.DHTSpec where++import Test.Hspec++import qualified Network.Tox.DHT as DHT+++spec :: Spec+spec = return ()
+ test/Network/Tox/EncodingSpec.hs view
@@ -0,0 +1,146 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE Trustworthy #-}+module Network.Tox.EncodingSpec+ ( spec+ , binarySpec+ , binaryGetPutSpec+ , bitEncodingSpec+ , readShowSpec+ , rpcSpec+ , expectDecoded+ , expectDecoderFail+ ) where++import Control.Monad.IO.Class (liftIO)+import Data.MessagePack (MessagePack)+import Network.MessagePack.Client (Client)+import Test.Hspec+import Test.QuickCheck (Arbitrary)+import qualified Test.QuickCheck as QC++import Data.Binary (Binary)+import qualified Data.Binary as Binary (get, put)+import qualified Data.Binary.Bits.Get as Bits (BitGet, runBitGet)+import qualified Data.Binary.Bits.Put as Bits (BitPut, runBitPut)+import qualified Data.Binary.Get as Binary (Decoder (..), Get,+ pushChunk, runGet,+ runGetIncremental)+import qualified Data.Binary.Put as Binary (Put, runPut)+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Lazy as LazyByteString+import Data.Proxy (Proxy (..))+import Data.Typeable (Typeable)+import Data.Word (Word64, Word8)++import qualified Network.Tox.Binary as Binary+import Network.Tox.Encoding (BitEncoding, bitGet, bitPut)+++spec :: Spec+spec =+ rpcSpec (Proxy :: Proxy Word64)+++-- | Limit the number of tests we do with the encoders/decoders.+--+-- These are fairly expensive, and running very large tests for them is probably+-- not very valuable.+property :: QC.Testable prop => prop -> QC.Property+property = QC.withMaxSuccess 50 . QC.property+++expectDecoded :: (Binary a, Eq a, Show a) => [Word8] -> a -> Expectation+expectDecoded bytes expected =+ Binary.runGet Binary.get (LazyByteString.pack bytes) `shouldBe` expected+++expectDecoderFail :: Binary.Get a -> [Word8] -> String -> Expectation+expectDecoderFail getA bytes expectedMessage =+ let decoder = Binary.runGetIncremental getA in+ case Binary.pushChunk decoder $ ByteString.pack bytes of+ Binary.Fail _ _ msg -> msg `shouldContain` expectedMessage+ Binary.Partial _ -> expectationFailure "Not enough input to reach failure"+ Binary.Done {} -> expectationFailure "Input unexpectedly yielded a valid value"+++binaryEncodeAndDecode :: (Eq a, Show a) => Binary.Get a -> (a -> Binary.Put) -> a -> Expectation+binaryEncodeAndDecode getA putA expected =+ let bytes = LazyByteString.toStrict $ Binary.runPut $ putA expected in+ finish $ Binary.pushChunk (Binary.runGetIncremental getA) bytes++ where+ finish = \case+ Binary.Fail _ _ msg -> expectationFailure msg+ Binary.Partial next -> finish $ next Nothing+ Binary.Done remaining _ output -> do+ remaining `shouldBe` ByteString.empty+ output `shouldBe` expected+++binaryGetPutSpec :: (Arbitrary a, Eq a, Show a) => String -> Binary.Get a -> (a -> Binary.Put) -> Spec+binaryGetPutSpec name getA putA =+ describe name $ do+ it "decodes encoded protocols correctly" $+ property $ binaryEncodeAndDecode getA putA++ it "handles arbitrary input" $+ property $ \bytes ->+ let+ finish = \case+ Binary.Fail {} -> return ()+ Binary.Partial f -> finish $ f Nothing+ Binary.Done _ _ output -> binaryEncodeAndDecode getA putA output+ in+ finish $ Binary.pushChunk (Binary.runGetIncremental getA) $ ByteString.pack bytes++ it "should have a non-nullable packet grammar" $+ let+ bytes = []+ decoder = Binary.runGetIncremental getA+ in+ case Binary.pushChunk decoder $ ByteString.pack bytes of+ Binary.Fail _ _ msg -> expectationFailure msg+ Binary.Partial _ -> return ()+ Binary.Done {} -> expectationFailure "Done with empty input; packet grammar appears to be nullable"+++binarySpec :: (Arbitrary a, Eq a, Show a, Binary a) => Proxy a -> Spec+binarySpec (Proxy :: Proxy a) =+ binaryGetPutSpec "Binary.{get,put}" (Binary.get :: Binary.Get a) (Binary.put :: a -> Binary.Put)+++bitEncodingSpec :: (Arbitrary a, Eq a, Show a, BitEncoding a) => Proxy a -> Spec+bitEncodingSpec (Proxy :: Proxy a) =+ let+ bitGetA = (bitGet :: Bits.BitGet a)+ bitPutA = (bitPut :: a -> Bits.BitPut ())+ in+ binaryGetPutSpec "BitEncoding.bit{Get,Put}" (Bits.runBitGet bitGetA) (Bits.runBitPut . bitPutA)+++readShowSpec :: (Arbitrary a, Eq a, Show a, Read a) => Proxy a -> Spec+readShowSpec (Proxy :: Proxy a) =+ let+ showA = show :: a -> String+ readA = read :: String -> a+ in+ describe "Read/Show" $+ it "encodes and decodes correctly" $+ property $ \expected ->+ let output = readA $ showA expected in+ output `shouldBe` expected+++rpcSpec+ :: (Arbitrary a, Eq a, Show a, Typeable a, Binary a, MessagePack a)+ => Proxy a+ -> Spec+rpcSpec (Proxy :: Proxy a) =+ describe "MessagePack" $+ it "encodes and decodes correctly" $ property $ \x ->+ decodeA (encodeA x) `shouldBe` Just x++ where+ encodeA = Binary.encode :: a -> ByteString.ByteString+ decodeA = Binary.decode :: ByteString.ByteString -> Maybe a
+ test/Network/Tox/NodeInfo/HostAddressSpec.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.NodeInfo.HostAddressSpec where++import Test.Hspec++import Data.Proxy (Proxy (..))+import Network.Tox.EncodingSpec+import Network.Tox.NodeInfo.HostAddress (HostAddress)+++spec :: Spec+spec = do+ rpcSpec (Proxy :: Proxy HostAddress)+ binarySpec (Proxy :: Proxy HostAddress)+ readShowSpec (Proxy :: Proxy HostAddress)
+ test/Network/Tox/NodeInfo/NodeInfoSpec.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.NodeInfo.NodeInfoSpec where++import Test.Hspec++import qualified Data.Binary as Binary (get)+import qualified Data.Binary.Get as Binary (Get)+import Data.Proxy (Proxy (..))+import Network.Tox.EncodingSpec+import Network.Tox.NodeInfo.NodeInfo (NodeInfo)+++spec :: Spec+spec = do+ rpcSpec (Proxy :: Proxy NodeInfo)+ binarySpec (Proxy :: Proxy NodeInfo)+ readShowSpec (Proxy :: Proxy NodeInfo)++ it "should handle invalid packets as failures" $ do+ expectDecoderFailure [0x20] "Invalid address family: 32"+ expectDecoderFailure [0xa0] "Invalid address family: 32"+ expectDecoderFailure [0x00] "Invalid address family: 0"+ expectDecoderFailure [0x01] "Invalid address family: 1"++ where+ expectDecoderFailure =+ expectDecoderFail (Binary.get :: Binary.Get NodeInfo)
+ test/Network/Tox/NodeInfo/PortNumberSpec.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.NodeInfo.PortNumberSpec where++import Test.Hspec++import Data.Proxy (Proxy (..))+import Network.Tox.EncodingSpec+import Network.Tox.NodeInfo.PortNumber (PortNumber)+++spec :: Spec+spec = do+ rpcSpec (Proxy :: Proxy PortNumber)+ binarySpec (Proxy :: Proxy PortNumber)+ readShowSpec (Proxy :: Proxy PortNumber)
+ test/Network/Tox/NodeInfo/SocketAddressSpec.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.NodeInfo.SocketAddressSpec where++import Test.Hspec++import Data.Proxy (Proxy (..))+import Network.Tox.EncodingSpec+import Network.Tox.NodeInfo.SocketAddress (SocketAddress)+import qualified Network.Tox.NodeInfo.SocketAddress as SocketAddress+++spec :: Spec+spec = do+ rpcSpec (Proxy :: Proxy SocketAddress)+ binarySpec (Proxy :: Proxy SocketAddress)+ readShowSpec (Proxy :: Proxy SocketAddress)++ binaryGetPutSpec "{get,put}SocketAddress"+ SocketAddress.getSocketAddress+ (uncurry SocketAddress.putSocketAddress)
+ test/Network/Tox/NodeInfo/TransportProtocolSpec.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.NodeInfo.TransportProtocolSpec where++import Test.Hspec++import Data.Proxy (Proxy (..))+import Network.Tox.EncodingSpec+import Network.Tox.NodeInfo.TransportProtocol (TransportProtocol)+++spec :: Spec+spec = do+ rpcSpec (Proxy :: Proxy TransportProtocol)+ binarySpec (Proxy :: Proxy TransportProtocol)+ readShowSpec (Proxy :: Proxy TransportProtocol)+ bitEncodingSpec (Proxy :: Proxy TransportProtocol)
+ test/Network/Tox/NodeInfoSpec.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.NodeInfoSpec where++import Test.Hspec++import qualified Network.Tox.NodeInfo as NodeInfo+++spec :: Spec+spec = return ()
+ test/Network/Tox/Protocol/PacketKindSpec.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.Protocol.PacketKindSpec where++import Test.Hspec++import qualified Data.Binary as Binary (get)+import qualified Data.Binary.Get as Binary (Get)+import Data.Proxy (Proxy (..))+import Network.Tox.EncodingSpec+import Network.Tox.Protocol.PacketKind (PacketKind)+++spec :: Spec+spec = do+ rpcSpec (Proxy :: Proxy PacketKind)+ binarySpec (Proxy :: Proxy PacketKind)+ readShowSpec (Proxy :: Proxy PacketKind)++ it "should handle invalid packet kinds as failures" $ do+ expectDecoderFailure [0xfe] "packet kind 254"+ expectDecoderFailure [0xff] "packet kind 255"++ where+ expectDecoderFailure =+ expectDecoderFail (Binary.get :: Binary.Get PacketKind)
+ test/Network/Tox/Protocol/PacketSpec.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.Protocol.PacketSpec where++import Test.Hspec++import Data.Proxy (Proxy (..))+import Data.Word (Word64)++import Network.Tox.EncodingSpec+import Network.Tox.Protocol.Packet (Packet (..))+import qualified Network.Tox.Protocol.PacketKind as PacketKind+++spec :: Spec+spec = do+ rpcSpec (Proxy :: Proxy (Packet Word64))+ binarySpec (Proxy :: Proxy (Packet Word64))+ readShowSpec (Proxy :: Proxy (Packet Word64))++ it "has a kind and a payload" $ do+ let packet = Packet PacketKind.NodesRequest ["heyo"]+ packetKind packet `shouldBe` PacketKind.NodesRequest+ packetPayload packet `shouldBe` ["heyo"]
+ test/Network/Tox/ProtocolSpec.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.ProtocolSpec where++import Test.Hspec++import qualified Network.Tox.Protocol as Protocol+++spec :: Spec+spec = return ()
+ test/Network/Tox/SaveDataSpec.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE Trustworthy #-}+module Network.Tox.SaveDataSpec where++import Test.Hspec++import qualified Data.Binary as Binary (get)+import qualified Data.Binary.Get as Binary (Get)+import Data.Proxy (Proxy (..))+import Network.Tox.EncodingSpec (binarySpec)+import qualified Network.Tox.EncodingSpec as EncodingSpec (expectDecoderFail)+import Network.Tox.SaveData (SaveData)+++spec :: Spec+spec = do+ binarySpec (Proxy :: Proxy SaveData)++ it "should handle invalid magic numbers" $ do+ expectDecoderFail [0x00, 0x00, 0x00, 0x01]+ "savedata should start with 32 zero-bits"+ expectDecoderFail [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]+ "wrong magic number"++ where+ expectDecoderFail =+ EncodingSpec.expectDecoderFail (Binary.get :: Binary.Get SaveData)
− tools/groupbot/Main.hs
@@ -1,143 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE LambdaCase #-}-module Main (main) where--import Control.Concurrent (threadDelay)-import Control.Concurrent.MVar (MVar, newMVar)-import Control.Exception (AsyncException (UserInterrupt), catch,- throwIO)-import Control.Monad (forever)-import qualified Data.ByteString.Base16 as Base16-import qualified Data.ByteString.Char8 as BS-import Data.String (fromString)-import Data.Word (Word32)-import Foreign.Storable (Storable (..))-import System.Directory (doesFileExist)-import System.Exit (exitSuccess)--import qualified Network.Tox.C as C---bootstrapKey :: BS.ByteString-bootstrapKey =- fst . Base16.decode . fromString $- "15E9C309CFCB79FDDF0EBA057DABB49FE15F3803B1BFF06536AE2E5BA5E4690E"--isMasterKey :: BS.ByteString -> Bool-isMasterKey key =- (key ==) . fst . Base16.decode . fromString $- "040F75B5C8995F9525F9A8692A6C355286BBD3CF248C984560733421274F0365"--botName :: String-botName = "groupbot"--bootstrapHost :: String-bootstrapHost = "tox.ngc.zone"--savedataFilename :: String-savedataFilename = "groupbot.tox"--options :: BS.ByteString -> C.Options-options savedata = C.Options- { C.ipv6Enabled = True- , C.udpEnabled = True- , C.proxyType = C.ProxyTypeNone- , C.proxyHost = ""- , C.proxyPort = 0- , C.startPort = 33445- , C.endPort = 33545- , C.tcpPort = 3128- , C.savedataType = if savedata == BS.empty then C.SavedataTypeNone else C.SavedataTypeToxSave- , C.savedataData = savedata- }---getRight :: (Monad m, Show a) => Either a b -> m b-getRight (Left l) = fail $ show l-getRight (Right r) = return r---must :: Show a => IO (Either a b) -> IO b-must = (getRight =<<)---newtype UserData = UserData Word32- deriving (Eq, Storable, Read, Show)--instance C.CHandler UserData where- cSelfConnectionStatus _ conn ud = do- putStrLn "SelfConnectionStatusCb"- print conn- return ud-- cFriendRequest tox pk msg ud = do- putStrLn "FriendRequestCb"- Right fn <- C.toxFriendAddNorequest tox pk- putStrLn $ (BS.unpack . Base16.encode) pk- putStrLn msg- print fn- return ud-- cFriendConnectionStatus tox fn status ud@(UserData gn) = do- putStrLn "FriendConnectionStatusCb"- print fn- print status- if status /= C.ConnectionNone- then do- putStrLn "Inviting!"- _ <- C.toxConferenceInvite tox fn gn- return ()- else- putStrLn "Friend offline"- return ud-- cFriendMessage tox fn msgType msg ud = do- putStrLn "FriendMessage"- print fn- print msgType- putStrLn msg- _ <- C.toxFriendSendMessage tox fn msgType msg- return ud-- cConferenceInvite tox fn _confType cookie ud = do- putStrLn "ConferenceInvite"- print fn- pk <- getRight =<< C.toxFriendGetPublicKey tox fn- if isMasterKey pk- then do- putStrLn "Joining!"- gn <- getRight =<< C.toxConferenceJoin tox fn cookie- return $ UserData gn- else do- putStrLn "Not master!"- return ud---loop :: MVar ud -> C.Tox ud -> IO a-loop ud tox =- forever $ do- C.toxIterate tox ud- interval <- C.tox_iteration_interval tox- threadDelay $ fromIntegral $ interval * 10000---main :: IO ()-main = do- exists <- doesFileExist savedataFilename- loadedSavedata <- if exists then BS.readFile savedataFilename else return BS.empty- must $ C.withOptions (options loadedSavedata) $ \optPtr ->- must $ C.withTox optPtr $ \tox -> do- must $ C.toxBootstrap tox bootstrapHost 33445 bootstrapKey-- C.withCHandler tox $ do- adr <- C.toxSelfGetAddress tox- putStrLn $ (BS.unpack . Base16.encode) adr- _ <- C.toxSelfSetName tox botName- gn <- getRight =<< C.toxConferenceNew tox- ud <- newMVar (UserData gn)- catch (loop ud tox) $ \case- e@UserInterrupt -> throwIO e- _ -> do- savedSavedata <- C.toxGetSavedata tox- BS.writeFile savedataFilename savedSavedata- exitSuccess
+ tools/toxsave-convert.hs view
@@ -0,0 +1,21 @@+module Main (main) where++import Control.Applicative ((<$>), (<|>))+import qualified Data.Binary as Binary+import qualified Data.ByteString.Lazy as LBS+import qualified Data.ByteString.Lazy.Char8 as LBS8+import Data.Maybe (fromMaybe)+import Network.Tox.SaveData (SaveData)+import Text.Groom (groom)+import Text.Read (readMaybe)+++parse :: LBS.ByteString -> LBS.ByteString+parse str = fromMaybe LBS.empty $+ (Binary.encode <$> (readMaybe $ LBS8.unpack str :: Maybe SaveData))+ <|>+ (Just . LBS8.pack . (++ "\n") . groom $ (Binary.decode str :: SaveData))+++main :: IO ()+main = parse <$> LBS.getContents >>= LBS.putStr
toxcore.cabal view
@@ -1,61 +1,121 @@ name: toxcore-synopsis: Haskell bindings to the C reference implementation of Tox-version: 0.2.0+synopsis: A Tox protocol implementation in Haskell+version: 0.2.11 cabal-version: >= 1.10 license: GPL-3-license-file: LICENSE.md+license-file: LICENSE build-type: Simple author: iphy maintainer: iphy-copyright: © 2016-2018 iphy-homepage: https://toktok.github.io+copyright: © 2016-2020 The TokTok Team+homepage: https://toktok.ltd category: Network-description:- Haskell bindings to the C reference implementation of Tox.- .- See <https://github.com/TokTok/toxcore>.+description: A Tox protocol implementation in Haskell source-repository head type: git location: https://github.com/TokTok/hs-toxcore +flag library-only+ description: Build only library, no executables or tests.+ default: False+ library default-language: Haskell2010 hs-source-dirs: src ghc-options: -Wall- -- -fno-warn-unused-imports- extra-libraries: toxcore+ -fno-warn-unused-imports+ exposed-modules:+ Network.Tox+ Network.Tox.Application.GroupChats+ Network.Tox.Binary+ Network.Tox.Crypto+ Network.Tox.Crypto.Box+ Network.Tox.Crypto.CombinedKey+ Network.Tox.Crypto.Key+ Network.Tox.Crypto.Keyed+ Network.Tox.Crypto.KeyedT+ Network.Tox.Crypto.KeyPair+ Network.Tox.Crypto.Nonce+ Network.Tox.DHT+ Network.Tox.DHT.ClientList+ Network.Tox.DHT.ClientNode+ Network.Tox.DHT.DhtPacket+ Network.Tox.DHT.DhtRequestPacket+ Network.Tox.DHT.DhtState+ Network.Tox.DHT.Distance+ Network.Tox.DHT.KBuckets+ Network.Tox.DHT.NodeList+ Network.Tox.DHT.NodesRequest+ Network.Tox.DHT.NodesResponse+ Network.Tox.DHT.Operation+ Network.Tox.DHT.PendingReplies+ Network.Tox.DHT.PingPacket+ Network.Tox.DHT.RpcPacket+ Network.Tox.DHT.Stamped+ Network.Tox.Encoding+ Network.Tox.Network.Networked+ Network.Tox.Network.MonadRandomBytes+ Network.Tox.NodeInfo+ Network.Tox.NodeInfo.HostAddress+ Network.Tox.NodeInfo.NodeInfo+ Network.Tox.NodeInfo.PortNumber+ Network.Tox.NodeInfo.SocketAddress+ Network.Tox.NodeInfo.TransportProtocol+ Network.Tox.Protocol+ Network.Tox.Protocol.Packet+ Network.Tox.Protocol.PacketKind+ Network.Tox.SaveData+ Network.Tox.SaveData.Conferences+ Network.Tox.SaveData.DHT+ Network.Tox.SaveData.Friend+ Network.Tox.SaveData.Nodes+ Network.Tox.SaveData.Util+ Network.Tox.Testing+ Network.Tox.Time+ Network.Tox.Timed+ Network.Tox.TimedT build-depends: base < 5+ , QuickCheck >= 2.9.1+ , base16-bytestring+ , binary+ , binary-bits , bytestring- , data-default-class- exposed-modules:- Network.Tox.C- Network.Tox.C.CEnum- Network.Tox.C.Callbacks- Network.Tox.C.Constants- Network.Tox.C.Options- Network.Tox.C.Tox- Network.Tox.C.Type- Network.Tox.C.Version+ , clock >= 0.3+ , containers+ , entropy+ , integer-gmp+ , iproute+ , lens-family+ , MonadRandom+ , msgpack-binary >= 0.0.12+ , msgpack-rpc-conduit >= 0.0.5+ , mtl+ , network < 3+ , saltine+ , random+ , transformers -executable groupbot+executable toxsave-convert default-language: Haskell2010 hs-source-dirs:- tools/groupbot+ tools ghc-options: -Wall -fno-warn-unused-imports- extra-libraries: toxcore+ main-is: toxsave-convert.hs+ if flag(library-only)+ buildable: False build-depends: base < 5- , base16-bytestring+ , binary , bytestring- , directory+ , groom+ , text , toxcore- main-is: Main.hs test-suite testsuite default-language: Haskell2010@@ -65,17 +125,52 @@ ghc-options: -Wall -fno-warn-unused-imports+ main-is: testsuite.hs+ other-modules:+ Data.Result+ Network.Tox.Crypto.BoxSpec+ Network.Tox.Crypto.CombinedKeySpec+ Network.Tox.Crypto.KeyPairSpec+ Network.Tox.Crypto.KeySpec+ Network.Tox.Crypto.NonceSpec+ Network.Tox.CryptoSpec+ Network.Tox.DHT.ClientListSpec+ Network.Tox.DHT.DhtPacketSpec+ Network.Tox.DHT.DhtRequestPacketSpec+ Network.Tox.DHT.DhtStateSpec+ Network.Tox.DHT.DistanceSpec+ Network.Tox.DHT.KBucketsSpec+ Network.Tox.DHT.NodesRequestSpec+ Network.Tox.DHT.NodesResponseSpec+ Network.Tox.DHT.OperationSpec+ Network.Tox.DHT.PendingRepliesSpec+ Network.Tox.DHT.PingPacketSpec+ Network.Tox.DHT.RpcPacketSpec+ Network.Tox.DHTSpec+ Network.Tox.EncodingSpec+ Network.Tox.NodeInfo.HostAddressSpec+ Network.Tox.NodeInfo.NodeInfoSpec+ Network.Tox.NodeInfo.PortNumberSpec+ Network.Tox.NodeInfo.SocketAddressSpec+ Network.Tox.NodeInfoSpec+ Network.Tox.NodeInfo.TransportProtocolSpec+ Network.Tox.Protocol.PacketKindSpec+ Network.Tox.Protocol.PacketSpec+ Network.Tox.ProtocolSpec+ Network.Tox.SaveDataSpec build-depends: base < 5- , QuickCheck >= 2.9.1- , base16-bytestring+ , QuickCheck+ , async+ , binary+ , binary-bits , bytestring- , bytestring-arbitrary- , data-default-class+ , containers , hspec+ , msgpack-binary+ , msgpack-rpc-conduit+ , msgpack-types+ , mtl , saltine+ , text , toxcore- main-is: testsuite.hs- other-modules:- Network.Tox.C.ToxSpec- Network.Tox.CSpec