diff --git a/LICENSE.md b/LICENSE.md
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,12 +1,595 @@
-The software is licensed under [AGPLv3](licenses/gnu-agpl-v3.0.md).
+GNU General Public License
+==========================
 
-The documentation is the Markdown text extracted from the Software. The parts
-of the extracted documentation that were originally part of the
-[Tox-Docs](https://github.com/Tox-Docs/Text/commit/8f77b8a7c935871eea48cc5abeef26dfa42a108a)
-repository are licensed under [MIT](licenses/mit.md). The parts that were not
-originally part of that repository are licensed under AGPLv3.
+_Version 3, 29 June 2007_
+_Copyright © 2007 Free Software Foundation, Inc. &lt;<http://fsf.org/>&gt;_
 
-The copyright of all contributions is owned by their respective contributors.
-The TokTok project will not accept, request, or perform copyright assignments.
+Everyone is permitted to copy and distribute verbatim copies of this license
+document, but changing it is not allowed.
 
-Contact Iphigenia Df <iphydf@gmail.com> for license inquiries.
+## 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
+&lt;<http://www.gnu.org/licenses/>&gt;.
+
+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
+&lt;<http://www.gnu.org/philosophy/why-not-lgpl.html>&gt;.
diff --git a/src/Network/Tox/C.hs b/src/Network/Tox/C.hs
--- a/src/Network/Tox/C.hs
+++ b/src/Network/Tox/C.hs
@@ -3,4 +3,8 @@
   ) 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
diff --git a/src/Network/Tox/C/CEnum.hs b/src/Network/Tox/C/CEnum.hs
--- a/src/Network/Tox/C/CEnum.hs
+++ b/src/Network/Tox/C/CEnum.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE ScopedTypeVariables        #-}
+{-# LANGUAGE Trustworthy                #-}
 module Network.Tox.C.CEnum where
 
 import           Control.Applicative   ((<$>))
diff --git a/src/Network/Tox/C/Callbacks.hs b/src/Network/Tox/C/Callbacks.hs
--- a/src/Network/Tox/C/Callbacks.hs
+++ b/src/Network/Tox/C/Callbacks.hs
@@ -1,54 +1,83 @@
 module Network.Tox.C.Callbacks where
 
-import           Control.Exception (bracket)
-import           Foreign.Ptr       (FunPtr, freeHaskellFunPtr, nullFunPtr)
+import           Control.Exception  (bracket)
+import           Foreign.Ptr        (freeHaskellFunPtr, nullFunPtr)
 
-import           Network.Tox.C.Tox (Tox)
-import qualified Network.Tox.C.Tox as Tox
+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. Since userdata @a@ needs to be
--- in C memory, we recommend to keep it small, so marshalling costs are kept to
--- a minimum. Use 'StablePtr' to pass larger opaque Haskell values around in C.
+-- 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
-  cFriendName             :: Tox.FriendNameCb             a
-  cFriendStatusMessage    :: Tox.FriendStatusMessageCb    a
-  cFriendStatus           :: Tox.FriendStatusCb           a
-  cFriendConnectionStatus :: Tox.FriendConnectionStatusCb a
-  cFriendTyping           :: Tox.FriendTypingCb           a
-  cFriendReadReceipt      :: Tox.FriendReadReceiptCb      a
-  cFriendRequest          :: Tox.FriendRequestCb          a
-  cFriendMessage          :: Tox.FriendMessageCb          a
-  cFileRecvControl        :: Tox.FileRecvControlCb        a
-  cFileChunkRequest       :: Tox.FileChunkRequestCb       a
-  cFileRecv               :: Tox.FileRecvCb               a
-  cFileRecvChunk          :: Tox.FileRecvChunkCb          a
-  cFriendLossyPacket      :: Tox.FriendLossyPacketCb      a
-  cFriendLosslessPacket   :: Tox.FriendLosslessPacketCb   a
+  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.
-withHandler :: CHandler a => Tox a -> IO r -> IO r
-withHandler tox =
-  install Tox.tox_callback_self_connection_status   (Tox.wrapSelfConnectionStatusCb   cSelfConnectionStatus  ) .
-  install Tox.tox_callback_friend_name              (Tox.wrapFriendNameCb             cFriendName            ) .
-  install Tox.tox_callback_friend_status_message    (Tox.wrapFriendStatusMessageCb    cFriendStatusMessage   ) .
-  install Tox.tox_callback_friend_status            (Tox.wrapFriendStatusCb           cFriendStatus          ) .
-  install Tox.tox_callback_friend_connection_status (Tox.wrapFriendConnectionStatusCb cFriendConnectionStatus) .
-  install Tox.tox_callback_friend_typing            (Tox.wrapFriendTypingCb           cFriendTyping          ) .
-  install Tox.tox_callback_friend_read_receipt      (Tox.wrapFriendReadReceiptCb      cFriendReadReceipt     ) .
-  install Tox.tox_callback_friend_request           (Tox.wrapFriendRequestCb          cFriendRequest         ) .
-  install Tox.tox_callback_friend_message           (Tox.wrapFriendMessageCb          cFriendMessage         ) .
-  install Tox.tox_callback_file_recv_control        (Tox.wrapFileRecvControlCb        cFileRecvControl       ) .
-  install Tox.tox_callback_file_chunk_request       (Tox.wrapFileChunkRequestCb       cFileChunkRequest      ) .
-  install Tox.tox_callback_file_recv                (Tox.wrapFileRecvCb               cFileRecv              ) .
-  install Tox.tox_callback_file_recv_chunk          (Tox.wrapFileRecvChunkCb          cFileRecvChunk         ) .
-  install Tox.tox_callback_friend_lossy_packet      (Tox.wrapFriendLossyPacketCb      cFriendLossyPacket     ) .
-  install Tox.tox_callback_friend_lossless_packet   (Tox.wrapFriendLosslessPacketCb   cFriendLosslessPacket  )
+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
@@ -56,5 +85,5 @@
         action
 
     uninstall cInstall cb = do
-      () <- cInstall tox nullFunPtr
       freeHaskellFunPtr cb
+      cInstall tox nullFunPtr
diff --git a/src/Network/Tox/C/Constants.hs b/src/Network/Tox/C/Constants.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Tox/C/Constants.hs
@@ -0,0 +1,50 @@
+{-# 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
diff --git a/src/Network/Tox/C/Options.hs b/src/Network/Tox/C/Options.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Tox/C/Options.hs
@@ -0,0 +1,317 @@
+{-# 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)
diff --git a/src/Network/Tox/C/Tox.hs b/src/Network/Tox/C/Tox.hs
--- a/src/Network/Tox/C/Tox.hs
+++ b/src/Network/Tox/C/Tox.hs
@@ -1,1798 +1,2405 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE LambdaCase    #-}
-{-# LANGUAGE Trustworthy   #-}
--- | 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.Exception         (bracket)
-import qualified Crypto.Saltine.Class      as Sodium (encode)
-import           Crypto.Saltine.Core.Box   (CombinedKey, Nonce, PublicKey,
-                                            SecretKey)
-import           Data.ByteString           (ByteString)
-import qualified Data.ByteString           as BS
-import           Data.ByteString.Arbitrary (fromABS)
-import           Data.Default.Class        (Default (..))
-import           Data.Word                 (Word16, Word32, Word64)
-import           Foreign.C.String          (CString, peekCString, withCString)
-import           Foreign.C.Types           (CInt (..), CSize (..))
-import           Foreign.Ptr               (FunPtr, Ptr, nullPtr)
-import           GHC.Generics              (Generic)
-import           Test.QuickCheck.Arbitrary (Arbitrary (..),
-                                            arbitraryBoundedEnum)
-
-import           Network.Tox.C.CEnum
-
--- | 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)
-
-
---------------------------------------------------------------------------------
---
--- :: 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
-
-
---------------------------------------------------------------------------------
---
--- :: 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
-
-
---------------------------------------------------------------------------------
---
--- :: Global enumerations
---
---------------------------------------------------------------------------------
-
-
--- | 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)
-
-instance Arbitrary UserStatus where
-  arbitrary = arbitraryBoundedEnum
-
-
--- | 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)
-
-instance Arbitrary MessageType where
-  arbitrary = arbitraryBoundedEnum
-
-
---------------------------------------------------------------------------------
---
--- :: 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)
-
-instance Arbitrary ProxyType where
-  arbitrary = arbitraryBoundedEnum
-
-
--- 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)
-
-instance Arbitrary SavedataType where
-  arbitrary = arbitraryBoundedEnum
-
-
--- 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
-    }
-
-
-instance Arbitrary Options where
-  arbitrary = Options
-    <$> arbitrary
-    <*> arbitrary
-    <*> arbitrary
-    <*> arbitraryCString
-    <*> arbitrary
-    <*> arbitrary
-    <*> arbitrary
-    <*> arbitrary
-    <*> arbitrary
-    <*> (fromABS <$> arbitrary)
-    where
-      arbitraryCString = filter (/= '\NUL') <$> arbitrary
-
-
-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)
-
-
---------------------------------------------------------------------------------
---
--- :: 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 ()
-
-withTox :: OptionsPtr -> (Tox a -> IO r) -> IO (Either ErrNew r)
-withTox options f =
-  bracket (toxNew options) (either (const $ return ()) tox_kill) $ \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 ()
-
-
---------------------------------------------------------------------------------
---
--- :: 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 COptions.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 -> PublicKey -> IO (Either ErrBootstrap ())
-callBootstrapFun f tox address port pubKey =
-  withCString address $ \address' ->
-    BS.useAsCString (Sodium.encode pubKey) $ \pubKey' ->
-      callErrFun $ f tox address' (fromIntegral port) pubKey'
-
-toxBootstrap :: Tox a -> String -> Word16 -> PublicKey -> 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 -> PublicKey -> 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)
-
-instance Arbitrary Connection where
-  arbitrary = arbitraryBoundedEnum
-
-
--- | @param connection_status Whether we are connected to the DHT.
-type SelfConnectionStatusCb a = Tox a -> CEnum Connection -> Ptr a -> IO ()
-foreign import ccall "wrapper" wrapSelfConnectionStatusCb :: SelfConnectionStatusCb a -> IO (FunPtr (SelfConnectionStatusCb a))
-
-
--- | 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 (SelfConnectionStatusCb 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
-
--- | The main loop that needs to be run in intervals of tox_iteration_interval()
--- milliseconds.
-foreign import ccall tox_iterate :: Tox a -> Ptr a -> IO ()
-
-
---------------------------------------------------------------------------------
---
--- :: 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 ()
-
--- | 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 ()
-
--- | Get the 4-byte nospam part of the address.
-foreign import ccall tox_self_get_nospam :: Tox a -> IO Word32
-
--- | 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 ()
-
--- | 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 ()
-
-
---------------------------------------------------------------------------------
---
--- :: 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 ()
-
--- | 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 ()
-
--- | 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 ()
-
-
---------------------------------------------------------------------------------
---
--- :: 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
-
--- | 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
-
-
-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 ()
-
-
---------------------------------------------------------------------------------
---
--- :: 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
-
--- | 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
-
--- | 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 ()
-
-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
-
-
---------------------------------------------------------------------------------
---
--- :: Friend-specific state queries (can also be received through callbacks)
---
---------------------------------------------------------------------------------
-
-
--- | @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 -> CString -> CSize -> Ptr a
-foreign import ccall "wrapper" wrapFriendNameCb :: FriendNameCb a -> IO (FunPtr (FriendNameCb a))
-
--- | 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 (FriendNameCb 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 -> CString -> CSize -> Ptr a -> IO ()
-foreign import ccall "wrapper" wrapFriendStatusMessageCb :: FriendStatusMessageCb a -> IO (FunPtr (FriendStatusMessageCb a))
-
--- | 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 (FriendStatusMessageCb 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 -> CEnum UserStatus -> Ptr a -> IO ()
-foreign import ccall "wrapper" wrapFriendStatusCb :: FriendStatusCb a -> IO (FunPtr (FriendStatusCb a))
-
-
--- | 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 (FriendStatusCb a) -> IO ()
-
-
--- | @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 -> CEnum Connection -> Ptr a -> IO ()
-foreign import ccall "wrapper" wrapFriendConnectionStatusCb :: FriendConnectionStatusCb a -> IO (FunPtr (FriendConnectionStatusCb a))
-
--- | 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 (FriendConnectionStatusCb a) -> IO ()
-
-
--- | @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 -> Ptr a -> IO ()
-foreign import ccall "wrapper" wrapFriendTypingCb :: FriendTypingCb a -> IO (FunPtr (FriendTypingCb a))
-
--- | 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 (FriendTypingCb 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
-
-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
-
--- | @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 -> Ptr a -> IO ()
-foreign import ccall "wrapper" wrapFriendReadReceiptCb :: FriendReadReceiptCb a -> IO (FunPtr (FriendReadReceiptCb a))
-
-
--- | 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 (FriendReadReceiptCb 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 -> CString -> CString -> CSize -> Ptr a -> IO ()
-foreign import ccall "wrapper" wrapFriendRequestCb :: FriendRequestCb a -> IO (FunPtr (FriendRequestCb a))
-
-
--- | 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 (FriendRequestCb 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 -> CEnum MessageType -> CString -> CSize -> Ptr a -> IO ()
-foreign import ccall "wrapper" wrapFriendMessageCb :: FriendMessageCb a -> IO (FunPtr (FriendMessageCb a))
-
-
--- | 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 (FriendMessageCb 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
-
-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)
-
-instance Arbitrary FileKind where
-  arbitrary = arbitraryBoundedEnum
-
-
-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)
-
-instance Arbitrary FileControl where
-  arbitrary = arbitraryBoundedEnum
-
-
-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
-
--- | 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 -> CEnum FileControl -> Ptr a -> IO ()
-foreign import ccall "wrapper" wrapFileRecvControlCb :: FileRecvControlCb a -> IO (FunPtr (FileRecvControlCb a))
-
-
--- | 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 (FileRecvControlCb 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
-
-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
-
-
---------------------------------------------------------------------------------
---
--- :: 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 -> Word32 -> Word64 -> CString -> CString -> CSize -> CErr ErrFileSend -> IO Word32
-
-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
-
--- | 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 -> Ptr a -> IO ()
-foreign import ccall "wrapper" wrapFileChunkRequestCb :: FileChunkRequestCb a -> IO (FunPtr (FileChunkRequestCb a))
-
-
--- | 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 (FileChunkRequestCb 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 -> Word32 -> Word64 -> CString -> CSize -> Ptr a -> IO ()
-foreign import ccall "wrapper" wrapFileRecvCb :: FileRecvCb a -> IO (FunPtr (FileRecvCb a))
-
-
--- | 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 (FileRecvCb 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 -> CString -> CSize -> Ptr a -> IO ()
-foreign import ccall "wrapper" wrapFileRecvChunkCb :: FileRecvChunkCb a -> IO (FunPtr (FileRecvChunkCb a))
-
-
--- | 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 (FileRecvChunkCb a) -> IO ()
-
-
---------------------------------------------------------------------------------
---
--- :: Group chat management
---
---------------------------------------------------------------------------------
-
-
---------------------------------------------------------------------------------
---
--- :: Group chat message sending and receiving
---
---------------------------------------------------------------------------------
-
-
---------------------------------------------------------------------------------
---
--- :: 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
-
--- | 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
-
--- | @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 -> CString -> CSize -> Ptr a -> IO ()
-foreign import ccall "wrapper" wrapFriendLossyPacketCb :: FriendLossyPacketCb a -> IO (FunPtr (FriendLossyPacketCb a))
-
-
--- | Set the callback for the `friend_lossy_packet` event. Pass 'nullPtr' to
--- unset.
---
-foreign import ccall tox_callback_friend_lossy_packet :: Tox a -> FunPtr (FriendLossyPacketCb 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 -> CString -> CSize -> Ptr a -> IO ()
-foreign import ccall "wrapper" wrapFriendLosslessPacketCb :: FriendLosslessPacketCb a -> IO (FunPtr (FriendLosslessPacketCb a))
-
-
--- | Set the callback for the `friend_lossless_packet` event. Pass 'nullPtr' to
--- unset.
---
-foreign import ccall tox_callback_friend_lossless_packet :: Tox a -> FunPtr (FriendLosslessPacketCb 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 ()
-
-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
-
--- | 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
+{-# 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
diff --git a/src/Network/Tox/C/Type.hs b/src/Network/Tox/C/Type.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Tox/C/Type.hs
@@ -0,0 +1,17 @@
+{-# 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)
diff --git a/src/Network/Tox/C/Version.hs b/src/Network/Tox/C/Version.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Tox/C/Version.hs
@@ -0,0 +1,27 @@
+{-# 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
diff --git a/test/Network/Tox/C/ToxSpec.hs b/test/Network/Tox/C/ToxSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Network/Tox/C/ToxSpec.hs
@@ -0,0 +1,119 @@
+{-# 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
diff --git a/test/Network/Tox/CSpec.hs b/test/Network/Tox/CSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Network/Tox/CSpec.hs
@@ -0,0 +1,96 @@
+{-# 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"
diff --git a/tools/groupbot/Main.hs b/tools/groupbot/Main.hs
new file mode 100644
--- /dev/null
+++ b/tools/groupbot/Main.hs
@@ -0,0 +1,143 @@
+{-# 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
diff --git a/toxcore.cabal b/toxcore.cabal
--- a/toxcore.cabal
+++ b/toxcore.cabal
@@ -1,13 +1,13 @@
 name:                 toxcore
 synopsis:             Haskell bindings to the C reference implementation of Tox
-version:              0.0.2
+version:              0.2.0
 cabal-version:        >= 1.10
-license:              AGPL-3
+license:              GPL-3
 license-file:         LICENSE.md
 build-type:           Simple
 author:               iphy
 maintainer:           iphy
-copyright:            © 2016 iphy
+copyright:            © 2016-2018 iphy
 homepage:             https://toktok.github.io
 category:             Network
 description:
@@ -25,21 +25,38 @@
       src
   ghc-options:
       -Wall
-      -fno-warn-unused-imports
+      -- -fno-warn-unused-imports
   extra-libraries: toxcore
   build-depends:
       base < 5
-    , QuickCheck >= 2.9.1
     , bytestring
-    , bytestring-arbitrary
     , data-default-class
-    , saltine
   exposed-modules:
       Network.Tox.C
-      Network.Tox.C.Callbacks
       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
 
+executable groupbot
+  default-language: Haskell2010
+  hs-source-dirs:
+      tools/groupbot
+  ghc-options:
+      -Wall
+      -fno-warn-unused-imports
+  extra-libraries: toxcore
+  build-depends:
+      base < 5
+    , base16-bytestring
+    , bytestring
+    , directory
+    , toxcore
+  main-is: Main.hs
+
 test-suite testsuite
   default-language: Haskell2010
   type: exitcode-stdio-1.0
@@ -51,8 +68,14 @@
   build-depends:
       base < 5
     , QuickCheck >= 2.9.1
+    , base16-bytestring
+    , bytestring
+    , bytestring-arbitrary
     , data-default-class
     , hspec
     , saltine
     , toxcore
   main-is: testsuite.hs
+  other-modules:
+      Network.Tox.C.ToxSpec
+      Network.Tox.CSpec
