From b4afa06e383325f4a0c751a64ca896d769db07a8 Mon Sep 17 00:00:00 2001 From: Trumeet Date: Wed, 20 Jul 2022 18:12:22 -0700 Subject: libac: First Commit --- .gitmodules | 3 + README.md | 2 +- client/libacron/.gitignore | 81 +++++ client/libacron/CMakeLists.txt | 47 +++ client/libacron/LICENSE | 504 ++++++++++++++++++++++++++++++ client/libacron/README.md | 199 ++++++++++++ client/libacron/acronc/main.c | 169 +++++++++++ client/libacron/export.map | 4 + client/libacron/ids.c | 80 +++++ client/libacron/include/common.h | 64 ++++ client/libacron/include/events.h | 51 ++++ client/libacron/include/ids.h | 26 ++ client/libacron/include/libac.h | 34 +++ client/libacron/include/net.h | 29 ++ client/libacron/include/requests.h | 71 +++++ client/libacron/library.c | 44 +++ client/libacron/net.c | 306 +++++++++++++++++++ client/libacron/private/config.h | 17 ++ client/libacron/private/connection.h | 28 ++ client/libacron/private/helpers.c | 64 ++++ client/libacron/private/helpers.h | 22 ++ client/libacron/private/log.c | 52 ++++ client/libacron/private/log.h | 42 +++ client/libacron/private/serializer.c | 572 +++++++++++++++++++++++++++++++++++ client/libacron/private/serializer.h | 18 ++ client/libacron/requests.c | 5 + client/libacron/wic | 1 + 27 files changed, 2534 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 100644 client/libacron/.gitignore create mode 100644 client/libacron/CMakeLists.txt create mode 100644 client/libacron/LICENSE create mode 100644 client/libacron/README.md create mode 100644 client/libacron/acronc/main.c create mode 100644 client/libacron/export.map create mode 100644 client/libacron/ids.c create mode 100644 client/libacron/include/common.h create mode 100644 client/libacron/include/events.h create mode 100644 client/libacron/include/ids.h create mode 100644 client/libacron/include/libac.h create mode 100644 client/libacron/include/net.h create mode 100644 client/libacron/include/requests.h create mode 100644 client/libacron/library.c create mode 100644 client/libacron/net.c create mode 100644 client/libacron/private/config.h create mode 100644 client/libacron/private/connection.h create mode 100644 client/libacron/private/helpers.c create mode 100644 client/libacron/private/helpers.h create mode 100644 client/libacron/private/log.c create mode 100644 client/libacron/private/log.h create mode 100644 client/libacron/private/serializer.c create mode 100644 client/libacron/private/serializer.h create mode 100644 client/libacron/requests.c create mode 160000 client/libacron/wic diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e5605ba --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "client/libacron/wic"] + path = client/libacron/wic + url = https://github.com/cjhdev/wic.git diff --git a/README.md b/README.md index 14ccff9..aa7f24f 100644 --- a/README.md +++ b/README.md @@ -569,4 +569,4 @@ the email subject. If you are sending a patch, please include `[PATCH]` in the s ## License -GPL v2 only. +Acron is licensed under GPL-2.0-only except libac is licensed under LGPL-2.1-only. diff --git a/client/libacron/.gitignore b/client/libacron/.gitignore new file mode 100644 index 0000000..1bab38a --- /dev/null +++ b/client/libacron/.gitignore @@ -0,0 +1,81 @@ +cmake-build-debug/ +cmake-build-release/ + +# https://raw.githubusercontent.com/github/gitignore/main/Global/JetBrains.gitignore +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser diff --git a/client/libacron/CMakeLists.txt b/client/libacron/CMakeLists.txt new file mode 100644 index 0000000..71c0529 --- /dev/null +++ b/client/libacron/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.22) +project(libac C) + +set(CMAKE_C_STANDARD 11) + +set(CMAKE_C_FLAGS_DEBUG + "${CMAKE_C_FLAGS_DEBUG} -g3 -O0 -fsanitize=address -DDEBUG") +set(CMAKE_EXE_LINKER_FLAGS_DEBUG + "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fsanitize=address") +add_definitions(-D_POSIX_C_SOURCE=200809L) + +set(CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} "-Wl,--version-script='${CMAKE_SOURCE_DIR}/export.map'") + +find_package(PkgConfig) +pkg_check_modules(json-c REQUIRED json-c) +add_library(ac SHARED + include/libac.h + include/events.h + include/requests.h + include/common.h + include/net.h + include/ids.h + private/config.h + private/serializer.c + private/helpers.h + private/helpers.c + private/serializer.h + private/log.c + private/log.h + private/connection.h + wic/include/http_parser.h + wic/include/wic.h + wic/src/http_parser.c + wic/src/wic.c + net.c + ids.c + requests.c + library.c + ) +target_include_directories(ac PUBLIC "${PROJECT_BINARY_DIR}" include/ private/ wic/include/) +target_link_libraries(ac json-c) + +add_executable(acronc + acronc/main.c + ) +target_link_libraries(acronc ac) +target_include_directories(acronc PUBLIC "${PROJECT_BINARY_DIR}" include/) diff --git a/client/libacron/LICENSE b/client/libacron/LICENSE new file mode 100644 index 0000000..8000a6f --- /dev/null +++ b/client/libacron/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/client/libacron/README.md b/client/libacron/README.md new file mode 100644 index 0000000..11d7663 --- /dev/null +++ b/client/libacron/README.md @@ -0,0 +1,199 @@ +# `libac`: Acron Client Library + +A client library written in C, +based on [json-c](https://github.com/json-c/json-c) and [wic](https://github.com/cjhdev/wic). + +## Building + +Requirements: + +* `json-c` installed and using PkgConfig +* Git +* CMake +* A C11 or higher C compiler +* Connectivity to this git repository and `github.com` + +To build on Unix: + +```shell +git clone https://git.yuuta.moe/Minecraft/acron.git +cd acron +git submodule update --init +cd client/libacron/ +mkdir build +cd build +cmake -DCMAKE_BUILD_TYPE=Release .. +make +``` + +The shared library will be at `libac.so`. + +The distributable headers are at `client/libacron/include/`. + +To make debug builds, use the Debug build type. Debug builds will have ASAN enabled. + +## Usage + +All functions begin with `ac_`. Include `libac.h`. + + +### Thread Safety + +This library is single-threaded, meaning that all functions should be called on a +dedicated thread. However, user may initialize the library on multiple threads and call +the functions on different threads without interfering each other. + +### Error Handling + +All error codes are at `common.h`. `AC_E_*` are library errors (generated by library routines); +`AC_ER_*` are remote errors (generated by the server), and you should match them with `ac_response_error.code`. + +All libac functions return an integer value of the error code. `AC_E_OK(0)` indicates successful operation. + +Any library function calls not following the document will return `AC_E_INVALID_REQUEST`. + +### Struct Inheritance + +All libac exported structs have a `uint8_t type` as its first member. It indicates the type of the object. +The type ID is globally unique and stable across upgrades. + +Internally, the type is an unsigned 8-bit integer, with the first two bits indicating whether it is +an event, a response, or a request. The remaining 6 bits differentiates the type with others. + +Library users usually do not need to care about the internal assignment of type IDs. Every exported struct +have its ID defined in the corresponding headers (e.g. `AC_RESPONSE_OK`). Macros like `AC_IS_EVENT` is also +available (from `ids.h`). + +Thus, the base definition of all libac structs is: + +```c +/* ids.h */ +typedef struct ac_obj { + uint8_t type; +} ac_obj_t; +``` + +are classified in three types: + +* Event: Server-initiated events. Do not have IDs. Base definition: `ac_event_t`. +```c +/* events.h */ +typedef struct ac_event { + uint8_t type; +} ac_event_t; +``` +* Request: Program-allocated requests. Have a program-assigned ID. Base definition: `ac_request_t`. +```c +/* requests.h */ +typedef struct ac_request { + uint8_t type; + int id; +} ac_request_t; +``` +* Response: Responses to requests. Have the same ID as the request. Base definition: `ac_response_t`. +```c +/* requests.h */ +typedef struct ac_response { + uint8_t type; + int id; +} ac_response_t; +``` + +### Struct Memory Management + +For requests only, it is the program's responsibility to allocate them (on whether stack or heap) and +assign the correct type, so libac can recognize them. The program should also free the request after making +`ac_request()` calls, as libac will not modify the request object: + +```c +int list(void *connection) { + /* Allocated on the stack: Freed upon return, not by libac. */ + ac_request_cmd cmd = { + .type = AC_REQUEST_CMD, + .id = 114514, + .config = NULL, + .cmd = "list" + }; + int r = ac_request(connection, (ac_request_t *) &cmd); + /* cmd is freed. */ + return r; +} +``` + +For events and responses, libac will allocate them internally, and the program should free them using +`ac_obj_free(ac_obj_t)` function. + +### Initialization + +To initialize the library on the current thread, call: `ac_init()`: + +```c +int main(void) { + libac_config_t config = { + .out = NULL, /* Error output stream. NULL to disable any error logging. */ + .tok = NULL + } + int r = ac_init(&config); + if (r) return r; +} +``` + +When finished using libac on the thread, call `ac_free()`. + +Any function call without `ac_init()` first will return `AC_E_NOT_INITIALIZED`. + +### Making Connections + +To connect to a server on the current thread, use `ac_connect()`. +It will output a `void *` opaque pointer referencing to the connection. Programs should +pass this pointer as-is when making connections or disconnecting. + +Every thread can have multiple connections simultaneously. + +```c +void *connection = NULL; +int r; +ac_connection_parameters_t args = { + .url = "ws://localhost:25575/ws?id=1&token=123" +}; +if ((r = ac_connect(args, &connection))) { + return r; +} +/* Listen or make requests. */ +``` + +Then, the program can listen for responses or events: + +```c +ac_obj_t *obj = NULL; +while (!interrupted || !(r = ac_receive(connection, &obj))) { + /* The obj is now referencing to a library allocated event or response. */ + /* Do things with the event or response. */ + ac_obj_free(obj); +} +``` + +The program can make requests using `ac_request()`. ~~This function can +be called from any thread (not specific to the listening one), but any thread +calling this function must also call `ac_init()` first.~~ **On the roadmap.** + +Finally, when done, call `ac_disconnect()`. It will close the socket and free the +`connection` parameter. The `connection` pointer after the method call is undefined. + +> **Notes**: +> +> Only `ws://` is supported at this time. `wss://` support is +> on the roadmap. + + +Read more: `acronc/main.c` example client. + +## Roadmap + +* Make unit tests +* SSL support +* Calling `ac_request()` from any thread + +## License + +The libac library is licensed under LGPL-2.1-only. Other parts of Acron is still licensed under GPL-2.0-only. diff --git a/client/libacron/acronc/main.c b/client/libacron/acronc/main.c new file mode 100644 index 0000000..282f6e3 --- /dev/null +++ b/client/libacron/acronc/main.c @@ -0,0 +1,169 @@ +/* + * Created by yuuta on 7/20/22. + */ + +#include "libac.h" + +#include +#include + +static const char *world_name(const enum ac_world world) { + switch (world) { + case overworld: + return "overworld"; + case nether: + return "nether"; + case end: + return "end"; + default: + return "unknown world"; + } +} + +static void handle_event(const ac_event_t *event) { + switch (event->type) { + case AC_EVENT_LAGGING: { + ac_event_lagging_t *o = (ac_event_lagging_t *) event; + printf("Server lagging: running %lu milliseconds (%lu ticks) behind.\n", + o->ms, + o->ticks); + break; + } + case AC_EVENT_ENTITY_DEATH: { + ac_event_entity_death_t *o = (ac_event_entity_death_t *) event; + printf("Entity '%s' died at %s(%.2f, %.2f, %.2f): %s.\n", + o->entity.name, + world_name(o->entity.world), + o->entity.pos.x, + o->entity.pos.y, + o->entity.pos.z, + o->message); + break; + } + case AC_EVENT_PLAYER_MESSAGE: { + ac_event_player_message_t *o = (ac_event_player_message_t *) event; + printf("Player '%s' said at %s(%.2f, %.2f, %.2f): %s.\n", + o->player.name, + world_name(o->player.world), + o->player.pos.x, + o->player.pos.y, + o->player.pos.z, + o->text); + break; + } + case AC_EVENT_PLAYER_DISCONNECT: { + ac_event_player_disconnect_t *o = (ac_event_player_disconnect_t *) event; + printf("Player '%s' disconnected at %s(%.2f, %.2f, %.2f): %s.\n", + o->player.name, + world_name(o->player.world), + o->player.pos.x, + o->player.pos.y, + o->player.pos.z, + o->reason); + break; + } + case AC_EVENT_PLAYER_JOIN: { + ac_event_player_join_t *o = (ac_event_player_join_t *) event; + printf("Player '%s' joined at %s(%.2f, %.2f, %.2f).\n", + o->player.name, + world_name(o->player.world), + o->player.pos.x, + o->player.pos.y, + o->player.pos.z); + break; + } + default: { + printf("Received an unrecognized event of type '%u'.\n", + event->type); + } + } +} + +static void handle_response(const ac_response_t *response) { + switch (response->type) { + case AC_RESPONSE_OK: { + ac_response_ok_t *o = (ac_response_ok_t *) response; + printf("Request %d OK.\n", + o->id); + break; + } + case AC_RESPONSE_ERROR: { + ac_response_error_t *o = (ac_response_error_t *) response; + printf("Request %d failed: %s (%d).\n", + o->id, + o->message, + o->code); + break; + } + case AC_RESPONSE_CMD_OUT: { + ac_response_cmd_out_t *o = (ac_response_cmd_out_t *) response; + printf("Request %d output by %s: %s.\n", + o->id, + o->sender, + o->out); + break; + } + case AC_RESPONSE_CMD_RESULT: { + ac_response_cmd_result_t *o = (ac_response_cmd_result_t *) response; + printf("Request %d is done: %s (%d).\n", + o->id, + o->success ? "Success" : "Failed", + o->result); + break; + } + default: { + printf("Received an unrecognized response of type '%u'.\n", + response->type); + } + } +} + +int main(int argc, char **argv) { + int r; + libac_config_t config = { +#ifdef DEBUG + .out = stderr, +#else + .out = NULL, +#endif + .tok = NULL + }; + if ((r = ac_init(&config))) return r; + void *connection; + ac_connection_parameters_t parameters = { + .url = "ws://localhost:25575/ws?id=1&token=123" + }; + if ((r = ac_connect(parameters, &connection))) { + ac_free(); + return r; + } + ac_obj_t *obj; + ac_config_t conf = { + .name = "acronc" + }; + ac_request_cmd_t req = { + .type = AC_REQUEST_CMD, + .id = 100, + .config = &conf, + .cmd = "say Hi" + }; + if ((r = ac_request(connection, (ac_request_t *) &req))) { + ac_disconnect(connection); + ac_free(); + return r; + } + while (!(r = ac_receive(connection, &obj))) { + if (!obj) { + continue; + } + if (AC_IS_EVENT(obj->type)) { + handle_event((ac_event_t *) obj); + } else if (AC_IS_RESPONSE(obj->type)) { + handle_response((ac_response_t *) obj); + } + ac_object_free(obj); + } + ac_disconnect(connection); + ac_free(); + return r; +} \ No newline at end of file diff --git a/client/libacron/export.map b/client/libacron/export.map new file mode 100644 index 0000000..1d805fa --- /dev/null +++ b/client/libacron/export.map @@ -0,0 +1,4 @@ +AC { + global: ac_*; + local: *; +}; \ No newline at end of file diff --git a/client/libacron/ids.c b/client/libacron/ids.c new file mode 100644 index 0000000..89fff9a --- /dev/null +++ b/client/libacron/ids.c @@ -0,0 +1,80 @@ +/* + * Created by yuuta on 7/19/22. + */ + +#include "log.h" +#include "common.h" +#include "libac.h" + +#include + +int ac_object_free(ac_obj_t *obj) { + switch (obj->type & 192 /* 0b11000000 */) { + case AC_TYPE_EVENT: { + switch (obj->type) { + case AC_EVENT_PLAYER_JOIN: { + ac_event_player_join_t *v = (ac_event_player_join_t *) obj; + if (v->player.name) free(v->player.name); + if (v->player.uuid) free(v->player.uuid); + goto ok; + } + case AC_EVENT_PLAYER_DISCONNECT: { + ac_event_player_disconnect_t *v = (ac_event_player_disconnect_t *) obj; + if (v->player.name) free(v->player.name); + if (v->player.uuid) free(v->player.uuid); + if (v->reason) free(v->reason); + goto ok; + } + case AC_EVENT_ENTITY_DEATH: { + ac_event_entity_death_t *v = (ac_event_entity_death_t *) obj; + if (v->entity.name) free(v->entity.name); + if (v->entity.uuid) free(v->entity.uuid); + if (v->message) free(v->message); + goto ok; + } + case AC_EVENT_PLAYER_MESSAGE: { + ac_event_player_message_t *v = (ac_event_player_message_t *) obj; + if (v->player.name) free(v->player.name); + if (v->player.uuid) free(v->player.uuid); + if (v->text) free(v->text); + goto ok; + } + case AC_EVENT_LAGGING: { + goto ok; + } + } + } + case AC_TYPE_REQUEST: + /* Request objects are user-allocated. Do not free them and report an error. */ + break; + case AC_TYPE_RESPONSE: { + switch (obj->type) { + case AC_RESPONSE_CMD_RESULT: { + goto ok; + } + case AC_RESPONSE_CMD_OUT: { + ac_response_cmd_out_t *v = (ac_response_cmd_out_t *) obj; + if (v->sender) free(v->sender); + if (v->out) free(v->out); + goto ok; + } + case AC_RESPONSE_ERROR: { + ac_response_error_t *v = (ac_response_error_t *) obj; + if (v->message) free(v->message); + goto ok; + } + case AC_RESPONSE_OK: { + goto ok; + } + } + break; + } + } + LOGE("Invalid type passed to ac_obj_free function."); + return AC_E_INVALID_REQUEST; + ok: + { + free(obj); + return AC_E_OK; + }; +} \ No newline at end of file diff --git a/client/libacron/include/common.h b/client/libacron/include/common.h new file mode 100644 index 0000000..89738c4 --- /dev/null +++ b/client/libacron/include/common.h @@ -0,0 +1,64 @@ +/* + * Created by yuuta on 7/13/22. + */ + +#ifndef LIBAC_COMMON_H +#define LIBAC_COMMON_H + +/* Library errors */ +/* OK */ +#define AC_E_OK 0 +/* The library is not initialized on that thread. */ +#define AC_E_NOT_INITIALIZED 1 +/* ac_init(): The library is already initialized on that thread. */ +#define AC_E_ALREADY_INITIALIZED 2 +/* Cannot allocate memory. */ +#define AC_E_MEMORY_ALLOCATION 3 +/* Library internal error. */ +#define AC_E_INTERNAL 4 +/* The server returns an invalid response. */ +#define AC_E_INVALID_RESPONSE 5 +/* The request is invalid. */ +#define AC_E_INVALID_REQUEST 6 +/* Network error */ +#define AC_E_NET 7 + +/* Remote errors */ +#define AC_ER_BAD_REQUEST 400 +#define AC_ER_NOT_AUTHORIZED 401 +#define AC_ER_FORBIDDEN 403 +#define AC_ER_INTERNAL_ERROR 500 + +/* General Structs */ +enum ac_world { + overworld, + nether, + end +}; + +typedef struct ac_vec3d { + double x; + double y; + double z; +} ac_vec3d_t; + +typedef struct ac_vec2f { + double x; + double y; +} ac_vec2f_t; + +typedef struct ac_entity { + char *name; + char *uuid; + ac_vec3d_t pos; + enum ac_world world; +} ac_entity_t; + +typedef struct ac_config { + enum ac_world *world; + ac_vec3d_t *pos; + ac_vec2f_t *rot; + char *name; +} ac_config_t; + +#endif /* LIBAC_COMMON_H */ diff --git a/client/libacron/include/events.h b/client/libacron/include/events.h new file mode 100644 index 0000000..93a5184 --- /dev/null +++ b/client/libacron/include/events.h @@ -0,0 +1,51 @@ +/* + * Created by yuuta on 7/13/22. + */ + +#ifndef LIBAC_EVENTS_H +#define LIBAC_EVENTS_H + +#include "common.h" +#include "ids.h" +#include + +/* Events */ +typedef struct ac_event { + uint8_t type; +} ac_event_t; + +#define AC_EVENT_PLAYER_JOIN AC_ID(AC_TYPE_EVENT, 1 /* 0b0000001 */) /* 0b00000001 */ +typedef struct ac_event_player_join { + uint8_t type; + ac_entity_t player; +} ac_event_player_join_t; + +#define AC_EVENT_PLAYER_DISCONNECT AC_ID(AC_TYPE_EVENT, 2 /* 0b0000010 */) /* 0b00000010 */ +typedef struct ac_event_player_disconnect { + uint8_t type; + ac_entity_t player; + char *reason; +} ac_event_player_disconnect_t; + +#define AC_EVENT_ENTITY_DEATH AC_ID(AC_TYPE_EVENT, 3 /* 0b00000011 */) /* 0b00000011 */ +typedef struct ac_event_entity_death { + uint8_t type; + ac_entity_t entity; + char *message; +} ac_event_entity_death_t; + +#define AC_EVENT_PLAYER_MESSAGE AC_ID(AC_TYPE_EVENT, 4 /* 0b00000100 */) /* 0b00000100 */ +typedef struct ac_event_player_message { + uint8_t type; + ac_entity_t player; + char *text; +} ac_event_player_message_t; + +#define AC_EVENT_LAGGING AC_ID(AC_TYPE_EVENT, 5 /* 0b00000101 */) /* 0b00000101 */ +typedef struct ac_event_lagging { + uint8_t type; + int64_t ms; + int64_t ticks; +} ac_event_lagging_t; + +#endif /* LIBAC_EVENTS_H */ diff --git a/client/libacron/include/ids.h b/client/libacron/include/ids.h new file mode 100644 index 0000000..86fde47 --- /dev/null +++ b/client/libacron/include/ids.h @@ -0,0 +1,26 @@ +/* + * Created by yuuta on 7/19/22. + */ + +#ifndef LIBAC_IDS_H +#define LIBAC_IDS_H + +#define AC_TYPE_EVENT 0 /* 0b00000000 */ +#define AC_TYPE_REQUEST 64 /* 0b01000000 */ +#define AC_TYPE_RESPONSE 128 /* 0b10000000 */ + +#define AC_ID(type, num) type + num + +#define AC_IS_EVENT(type) (type & 192) == AC_TYPE_EVENT +#define AC_IS_REQUEST(type) (type & 192) == AC_TYPE_REQUEST +#define AC_IS_RESPONSE(type) (type & 192) == AC_TYPE_RESPONSE + +#include + +typedef struct ac_obj { + uint8_t type; +} ac_obj_t; + +int ac_object_free(ac_obj_t *obj); + +#endif /* LIBAC_IDS_H */ diff --git a/client/libacron/include/libac.h b/client/libacron/include/libac.h new file mode 100644 index 0000000..80734dc --- /dev/null +++ b/client/libacron/include/libac.h @@ -0,0 +1,34 @@ +/* + * Created by yuuta on 7/13/22. + */ + +#ifndef LIBAC_LIBAC_H +#define LIBAC_LIBAC_H + +#include "common.h" +#include "events.h" +#include "net.h" +#include "requests.h" +#include "ids.h" + +#include +#include + +typedef struct libac_config { + FILE *out; + json_tokener *tok; +} libac_config_t; + +/** + * Initialize libac for the calling thread. + * @return 0 on success. + */ +int ac_init(const libac_config_t *config); + +/** + * Destroy libac configuration for the calling thread. + * @return 0 on success. + */ +int ac_free(void); + +#endif /* LIBAC_LIBAC_H */ diff --git a/client/libacron/include/net.h b/client/libacron/include/net.h new file mode 100644 index 0000000..5b10470 --- /dev/null +++ b/client/libacron/include/net.h @@ -0,0 +1,29 @@ +/* + * Created by yuuta on 7/13/22. + */ + +#ifndef LIBAC_NET_H +#define LIBAC_NET_H + +#include "common.h" +#include "events.h" +#include "requests.h" + +/* Connection */ + +typedef struct ac_connection_parameters { + /** + * ws://host:port/ws?id=id&token=token + * Currently wss is not supported. + * Port must be supplied, or libac will connect to port zero. + */ + char *url; +} ac_connection_parameters_t; + +int ac_connect(ac_connection_parameters_t parameters, void **out); + +int ac_disconnect(void *connection); + +int ac_receive(void *connection, ac_obj_t **response); + +#endif /* LIBAC_NET_H */ diff --git a/client/libacron/include/requests.h b/client/libacron/include/requests.h new file mode 100644 index 0000000..40742e0 --- /dev/null +++ b/client/libacron/include/requests.h @@ -0,0 +1,71 @@ +/* + * Created by yuuta on 7/13/22. + */ + +#ifndef LIBAC_REQUESTS_H +#define LIBAC_REQUESTS_H + +#include "common.h" +#include "ids.h" +#include + +/* Responses */ +typedef struct ac_response { + uint8_t type; + int id; +} ac_response_t; + +#define AC_RESPONSE_ERROR AC_ID(AC_TYPE_RESPONSE, 1 /* 0b00000001 */) /* 0b10000001 */ +typedef struct ac_response_error { + uint8_t type; + int id; + int code; + char *message; +} ac_response_error_t; + +#define AC_RESPONSE_OK AC_ID(AC_TYPE_RESPONSE, 2 /* 0b00000010 */) /* 0b10000010 */ +typedef struct ac_response_ok { + uint8_t type; + int id; +} ac_response_ok_t; + +#define AC_RESPONSE_CMD_OUT AC_ID(AC_TYPE_RESPONSE, 3 /* 0b00000011 */) /* 0b10000011 */ +typedef struct ac_response_cmd_out { + uint8_t type; + int id; + char *sender; + char *out; +} ac_response_cmd_out_t; + +#define AC_RESPONSE_CMD_RESULT AC_ID(AC_TYPE_RESPONSE, 4 /* 0b00000100 */) /* 0b10000100 */ +typedef struct ac_response_cmd_result { + uint8_t type; + int id; + int result; + bool success; +} ac_response_cmd_result_t; + +/* Requests */ +typedef struct ac_request { + uint8_t type; + int id; +} ac_request_t; + +#define AC_REQUEST_CMD AC_ID(AC_TYPE_REQUEST, 1 /* 0b00000001 */) /* 0b01000001 */ +typedef struct ac_request_cmd { + uint8_t type; + int id; + ac_config_t *config; + char *cmd; +} ac_request_cmd_t; + +#define AC_REQUEST_SET_CONFIG AC_ID(AC_TYPE_REQUEST, 2 /* 0b00000010 */) /* 0b01000010 */ +typedef struct ac_request_set_config { + uint8_t type; + int id; + ac_config_t config; +} ac_request_set_config_t; + +int ac_request(void *connection, const ac_request_t *request); + +#endif /* LIBAC_REQUESTS_H */ diff --git a/client/libacron/library.c b/client/libacron/library.c new file mode 100644 index 0000000..dfc45e2 --- /dev/null +++ b/client/libacron/library.c @@ -0,0 +1,44 @@ +#include "libac.h" +#include "config.h" +#include "helpers.h" + +#include + +_Thread_local struct libac_config *config = NULL; + +int ac_init(const libac_config_t *c) { + if (config) { + return AC_E_ALREADY_INITIALIZED; + } + + int r; + if ((r = SALLOC(libac_config_t, &config))) { + return r; + } + + if (c->tok) { + config->tok = c->tok; + } else { + if (!(config->tok = json_tokener_new())) { + r = AC_E_MEMORY_ALLOCATION; + goto fail; + } + } + if (c->out) { + config->out = c->out; + } + return AC_E_OK; + fail: + ac_free(); + return r; +} + +int ac_free(void) { + AC_CHECK_INIT; + if (config->tok) { + json_tokener_free(config->tok); + config->tok = NULL; + } + free(config); + config = NULL; +} \ No newline at end of file diff --git a/client/libacron/net.c b/client/libacron/net.c new file mode 100644 index 0000000..51a99af --- /dev/null +++ b/client/libacron/net.c @@ -0,0 +1,306 @@ +/* + * Created by yuuta on 7/13/22. + */ + +#include "config.h" +#include "net.h" +#include "connection.h" +#include "log.h" +#include "helpers.h" +#include "serializer.h" + +/* TODO: wic logging is not yet supported */ +#include "wic.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static void on_open_handler(struct wic_inst *inst) { + struct ac_connection *conn = wic_get_app(inst); + conn->established = true; +} + +static void on_send_handler(struct wic_inst *inst, + const void *data, + size_t size, + enum wic_buffer type) { + struct ac_connection *conn = wic_get_app(inst); + size_t pos; + ssize_t retval; + + for (pos = 0U; pos < size; pos += retval) { + size_t n = size - pos; + LOGDV("write(%d, %p[%u, %u])", conn->fd, data, pos, n); + retval = write(conn->fd, &data[pos], (int) n); + if (retval <= 0) { + /* There's no way to abort the process. */ + int e = errno; + LOGEV("Cannot write to socket: %s (%d).", + strerror2(errno), + e); + break; + } + } + free((void *) data); +} + +static void *on_buffer_handler(struct wic_inst *inst, + size_t min_size, + enum wic_buffer type, + size_t *max_size) { + void *buf; + if ((alloc(NULL, + 1, + *max_size = (min_size ? min_size : 1024), + false, + &buf))) { + return NULL; + } + return buf; +} + +static bool on_message_handler(struct wic_inst *inst, + enum wic_encoding encoding, + bool fin, + const char *data, + uint16_t size) { + struct ac_connection *conn = wic_get_app(inst); + if (encoding != WIC_ENCODING_UTF8) { + LOGE("Invalid encoding received from server. Only text frames are supported."); + return true; + } + /* TODO: Only parse when fin = true? */ + struct ac_result *res = &conn->result; + int r; + if ((r = deserialize(data, size, &res->obj))) { + if (r == AC_E_SERIALIZER_CONTINUE) { + if (fin) { + serializer_finalize(); + LOGE("Server returned an incomplete response."); + res->res = AC_E_INVALID_RESPONSE; + } else { + res->res = AC_E_OK; + } + } else { + res->res = r; + } + } else { + res->has_result = true; + res->res = r; + } + LOGDV("Post deserializing: { has_result = %d, res = %d, obj = %p }", + res->has_result, + res->res, + res->obj); + + return true; +} + +int ac_connect(ac_connection_parameters_t parameters, void **out) { + AC_CHECK_INIT; + struct ac_connection *conn; + int r; + if ((r = SALLOC(struct ac_connection, &conn))) { + return r; + } + int s; + static uint8_t rx[1000]; + struct wic_inst *inst = &conn->inst; + struct wic_init_arg arg = {0}; + + arg.rx = rx; + arg.rx_max = sizeof(rx); + + arg.on_send = on_send_handler; + arg.on_open = on_open_handler; + arg.on_message = on_message_handler; + arg.on_buffer = on_buffer_handler; + + arg.app = &s; + arg.url = parameters.url; + arg.role = WIC_ROLE_CLIENT; + + if (!wic_init(inst, &arg)) { + LOGE("wic_init"); + free(conn); + return AC_E_INTERNAL; + } + inst->app = conn; + + struct addrinfo *res; + char service[6]; + + const enum wic_schema schema = wic_get_url_schema(inst); + const uint16_t port = wic_get_url_port(inst); + snprintf(service, 6, "%u", port); + const char *host = wic_get_url_hostname(inst); + + switch (schema) { + case WIC_SCHEMA_WS: + break; + case WIC_SCHEMA_WSS: + LOGE("WSS is not supported yet."); + free(conn); + return AC_E_INVALID_REQUEST; + default: + LOGE("Unsupported protocol. The URL must be ws://"); + free(conn); + return AC_E_INVALID_REQUEST; + } + + if ((r = getaddrinfo(host, service, NULL, &res))) { + LOGEV("Resolve host: %s.", gai_strerror(r)); + r = AC_E_NET; + free(conn); + return r; + } + + int fd; + if ((fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) <= 0) { + const int e = errno; + LOGEV("Cannot create socket: %s (%d).", + strerror2(e), + e); + freeaddrinfo(res); + free(conn); + return AC_E_NET; + } + if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) { + const int e = errno; + LOGEV("Cannot connect to socket: %s (%d).", + strerror2(e), + e); + close(fd); + freeaddrinfo(res); + free(conn); + return AC_E_NET; + } + freeaddrinfo(res); + + conn->fd = fd; + if (wic_start(inst) != WIC_STATUS_SUCCESS) { + LOGE("Cannot start the WIC client."); + close(fd); + free(conn); + return AC_E_NET; + } + + /* Wait until established (ready to send). */ + while (!conn->established) { + ac_obj_t *obj = NULL; + r = ac_receive(conn, &obj); + if (obj) { + LOGW("Received an object before connection is established. Dropping."); + ac_object_free(obj); + } + if (r) { + ac_disconnect(conn); + return r; + } + } + *out = conn; + return AC_E_OK; +} + +int ac_disconnect(void *connection) { + AC_CHECK_INIT; + struct ac_connection *conn = connection; + if (!conn->fd) { + LOGE("Trying to disconnect an already disconnected connection."); + return AC_E_INVALID_REQUEST; + } + LOGD("Disconnecting..."); + wic_close(&conn->inst); + close(conn->fd); + free(conn); + return AC_E_OK; +} + +int ac_receive(void *connection, ac_obj_t **response) { + AC_CHECK_INIT; + struct ac_connection *conn = connection; + struct wic_inst *inst = &conn->inst; + + static uint8_t buffer[1000U]; + ssize_t bytes; + size_t retval, pos; + + if ((bytes = recv(conn->fd, buffer, sizeof(buffer), 0)) <= 0) { + LOGDV("recv(%d) = %d", conn->fd, bytes); + if (bytes < 0) { + const int e = errno; + LOGEV("Failed to receive from socket : %s (%d).", + strerror2(e), + e); + } else { + LOGI("Peer abnormally shutdown."); + } + wic_close_with_reason(inst, WIC_CLOSE_ABNORMAL_2, NULL, 0); + return AC_E_NET; + } + LOGDV("recv(%d) = %d", conn->fd, bytes); + + /* In case the deserializer does not run at all. */ + memset(&conn->result, 0, sizeof(struct ac_result)); + for (pos = 0U; pos < bytes; pos += retval) { + retval = wic_parse(inst, &buffer[pos], bytes - pos); + if (wic_get_state(inst) == WIC_STATE_CLOSED) { + LOGE("Connection closed."); + return AC_E_NET; + } + } + + struct ac_result *res = &conn->result; + LOGDV("res { has_result = %d, res = %d, obj = %p }", + res->has_result, + res->res, + res->obj); + *response = NULL; + if (res->has_result) { + if (res->res) { + return res->res; + } else { + *response = res->obj; + return AC_E_OK; + } + } else { + *response = NULL; + } + + return AC_E_OK; +} + +int ac_request(void *connection, const ac_request_t *request) { + AC_CHECK_INIT; + struct ac_connection *conn = connection; + struct wic_inst *inst = &conn->inst; + if (wic_get_state(inst) != WIC_STATE_OPEN) { + LOGE("Invalid state."); + return AC_E_INVALID_REQUEST; + } + int r; + json_object *obj; + if ((r = serialize_request(request, &obj))) { + return r; + } + const char *str; + size_t len; + if (!(str = json_object_to_json_string_length(obj, JSON_C_TO_STRING_PLAIN, &len))) { + json_object_put(obj); + LOGE("Cannot serialize JSON."); + return AC_E_INTERNAL; + } + if (wic_send_text(inst, true, str, len) != WIC_STATUS_SUCCESS) { + LOGE("Cannot send."); + json_object_put(obj); + return AC_E_NET; + } + json_object_put(obj); + return AC_E_OK; +} diff --git a/client/libacron/private/config.h b/client/libacron/private/config.h new file mode 100644 index 0000000..3b670df --- /dev/null +++ b/client/libacron/private/config.h @@ -0,0 +1,17 @@ +/* + * Created by yuuta on 7/19/22. + */ + +#ifndef LIBAC_CONFIG_H +#define LIBAC_CONFIG_H + +#include "libac.h" + +#include +#include + +extern _Thread_local struct libac_config *config; + +#define AC_CHECK_INIT do { if (!config) return AC_E_NOT_INITIALIZED; } while(0) + +#endif /* LIBAC_CONFIG_H */ diff --git a/client/libacron/private/connection.h b/client/libacron/private/connection.h new file mode 100644 index 0000000..1c1567f --- /dev/null +++ b/client/libacron/private/connection.h @@ -0,0 +1,28 @@ +/* + * Created by yuuta on 7/19/22. + */ + +#ifndef LIBAC_CONNECTION_H +#define LIBAC_CONNECTION_H + +#include "wic.h" +#include "libac.h" +#include + +struct ac_result { + bool has_result; + int res; + ac_obj_t *obj; +}; + +struct ac_connection { + ac_connection_parameters_t parameters; + struct wic_inst inst; + int fd; + bool established; + /* Result from message handler. + * Message handling will happen at the same thread as ac_receive (lib wic). */ + struct ac_result result; +}; + +#endif /* LIBAC_CONNECTION_H */ diff --git a/client/libacron/private/helpers.c b/client/libacron/private/helpers.c new file mode 100644 index 0000000..4d4f3c8 --- /dev/null +++ b/client/libacron/private/helpers.c @@ -0,0 +1,64 @@ +/* + * Created by yuuta on 7/19/22. + */ + +#include "helpers.h" +#include "log.h" +#include "common.h" + +#include +#include +#include +#include + +int alloc(void *existing, + unsigned int count, + unsigned int bytes, + bool zero_out, + void **out) { + const unsigned int size = count * bytes; + void *ptr; + if (!(ptr = realloc(existing, size))) { + const int e = errno; + LOGEV("Cannot %sallocate %d bytes of memory: %s (%d).", + existing ? "re" : "", + size, + strerror2(e), + e); + return AC_E_MEMORY_ALLOCATION; + } + if (!existing && zero_out) { + memset(ptr, 0, size); + } + *out = ptr; + return AC_E_OK; +} + +int strdup2(const char *str, char **out) { + char *str2; + if (!(str2 = strdup(str))) { + const int e = errno; + LOGEV("Cannot duplicate string '%s': %s (%d).", + str, + strerror2(e), + e); + return AC_E_MEMORY_ALLOCATION; + } + *out = str2; + return AC_E_OK; +} + +static _Thread_local char err_buf[1024]; + +char *strerror2(int errnum) { +#ifdef WIN32 + /* Win32 strerror(3) is thread-safe. */ + return strerror(errnum); +#else + int r; + if ((strerror_r(errnum, err_buf, sizeof(err_buf) / sizeof(char)))) { + snprintf(err_buf, 1024, "%d (%d)", errnum, r); + } + return err_buf; +#endif +} \ No newline at end of file diff --git a/client/libacron/private/helpers.h b/client/libacron/private/helpers.h new file mode 100644 index 0000000..20dfeec --- /dev/null +++ b/client/libacron/private/helpers.h @@ -0,0 +1,22 @@ +/* + * Created by yuuta on 7/19/22. + */ + +#ifndef LIBAC_HELPERS_H +#define LIBAC_HELPERS_H + +#include + +#define SALLOC(s, out) alloc(NULL, 1, sizeof(s), true, (void **) out) + +int alloc(void *existing, + unsigned int count, + unsigned int bytes, + bool zero_out, + void **out); + +int strdup2(const char *str, char **out); + +char *strerror2(int errnum); + +#endif /* LIBAC_HELPERS_H */ diff --git a/client/libacron/private/log.c b/client/libacron/private/log.c new file mode 100644 index 0000000..b5a246a --- /dev/null +++ b/client/libacron/private/log.c @@ -0,0 +1,52 @@ +/* + * Created by yuuta on 1/1/22. + */ + +#include "log.h" +#include "config.h" + +#include +#include +#include + +void g_log(enum log_level level, + const char *file, + int line, + const char *format, + ...) { + if (config == NULL || config->out == NULL) { + return; + } + FILE *stream = config->out; + switch (level) { + case log_fetal: + fprintf(stream, "F"); + break; + case log_error: + fprintf(stream, "E"); + break; + case log_warn: + fprintf(stream, "W"); + break; + case log_info: + fprintf(stream, "I"); + break; + case log_debug: +#ifdef DEBUG + fprintf(stream, "D"); + break; +#else + return; +#endif + default: + fprintf(stderr, "Unknown log level: %d.\n", level); + assert(0); + } + fprintf(stream, "[%s:%d]: ", + file, line); + va_list list; + va_start(list, format); + vfprintf(stream, format, list); + va_end(list); + fprintf(stream, "\n"); +} diff --git a/client/libacron/private/log.h b/client/libacron/private/log.h new file mode 100644 index 0000000..7d41fbc --- /dev/null +++ b/client/libacron/private/log.h @@ -0,0 +1,42 @@ +/* + * Created by yuuta on 1/1/22. + */ + +#ifndef LOG_H +#define LOG_H + +enum log_level { + log_fetal = 1, + log_error = 2, + log_warn = 3, + log_info = 4, + log_debug = 5 +}; + +void g_log(enum log_level level, + const char *file, + int line, + const char *format, + ...); + +#define LOGF(X) g_log(log_fetal, __FUNCTION__, __LINE__, X) + +#define LOGFV(X, ...) g_log(log_fetal, __FUNCTION__, __LINE__, X, __VA_ARGS__) + +#define LOGE(X) g_log(log_error, __FUNCTION__, __LINE__, X) + +#define LOGEV(X, ...) g_log(log_error, __FUNCTION__, __LINE__, X, __VA_ARGS__) + +#define LOGW(X) g_log(log_warn, __FUNCTION__, __LINE__, X) + +#define LOGWV(X, ...) g_log(log_warn, __FUNCTION__, __LINE__, X, __VA_ARGS__) + +#define LOGI(X) g_log(log_info, __FUNCTION__, __LINE__, X) + +#define LOGIV(X, ...) g_log(log_info, __FUNCTION__, __LINE__, X, __VA_ARGS__) + +#define LOGD(X) g_log(log_debug, __FUNCTION__, __LINE__, X) + +#define LOGDV(X, ...) g_log(log_debug, __FUNCTION__, __LINE__, X, __VA_ARGS__) + +#endif /* LOG_H */ diff --git a/client/libacron/private/serializer.c b/client/libacron/private/serializer.c new file mode 100644 index 0000000..c34c81f --- /dev/null +++ b/client/libacron/private/serializer.c @@ -0,0 +1,572 @@ +/* + * Created by yuuta on 7/13/22. + */ + +#include "libac.h" +#include "config.h" +#include "serializer.h" +#include "helpers.h" +#include "log.h" + +#include +#include +#include +#include + +static int assert_type(const json_object *obj, const json_type type, const char *location) { + if (json_object_is_type(obj, type)) { + return AC_E_OK; + } + LOGEV("Cannot deserialize JSON at '%s': It should be a(n) %s, but it is a(n) %s.", + location, + json_type_to_name(type), + json_type_to_name(json_object_get_type(obj))); + return AC_E_INVALID_RESPONSE; +} + +static int get_child(const json_object *obj, + const char *key, + const json_type type, + const bool mandatory, + json_object **out) { + if (!json_object_object_get_ex(obj, key, out)) { + if (!mandatory) { + *out = NULL; + return AC_E_OK; + } + LOGEV("Cannot deserialize JSON: A mandatory child '%s' is missing.", + key); + return AC_E_INVALID_RESPONSE; + } + return assert_type(*out, type, key); +} + +static int deserialize_pre(const char *string, + unsigned int len, + json_object **out_json, + const char **out_type, + bool id_mandatory, + bool *out_has_id, + int32_t *out_id) { + AC_CHECK_INIT; + json_object *obj = NULL; + if (!(obj = json_tokener_parse_ex(config->tok, + string, + (int) len))) { + enum json_tokener_error err = json_tokener_get_error(config->tok); + if (err == json_tokener_continue) { + return AC_E_SERIALIZER_CONTINUE; + } + LOGEV("Cannot parse JSON response '%s': %s.", + string, + json_tokener_error_desc(err)); + json_tokener_reset(config->tok); + return AC_E_INVALID_RESPONSE; + } + int r; + if ((r = assert_type(obj, json_type_object, "."))) { + json_object_put(obj); + return r; + } + json_object *arg; + if ((r = get_child(obj, "type", json_type_string, true, &arg))) { + json_object_put(obj); + return r; + } + *out_json = obj; + *out_type = json_object_get_string(arg); + if ((r = get_child(obj, "id", json_type_int, id_mandatory, &arg))) { + json_object_put(obj); + *out_has_id = false; + return r; + } else { + *out_id = json_object_get_int(arg); + /* If is_mandatory is false, get_child will return 0 even it arg == NULL. */ + *out_has_id = arg; + } + return AC_E_OK; +} + +static int deserialize_entity(const json_object *json, ac_entity_t *entity) { + int r; + json_object *arg; + if ((r = get_child(json, "name", json_type_string, true, &arg))) { + return r; + } + if ((r = strdup2(json_object_get_string(arg), &entity->name))) { + return r; + } + + if ((r = get_child(json, "uuid", json_type_string, true, &arg))) { + return r; + } + if ((r = strdup2(json_object_get_string(arg), &entity->uuid))) { + return r; + } + + if ((r = get_child(json, "world", json_type_string, false, &arg))) { + return r; + } + if (arg) { + const char *world_str = json_object_get_string(arg); + if (!strcmp("overworld", world_str)) { + entity->world = overworld; + } else if (!strcmp("nether", world_str)) { + entity->world = nether; + } else if (!strcmp("end", world_str)) { + entity->world = end; + } else { + LOGEV("Server returned an invalid world: %s.", world_str); + return r; + } + } + + json_object *vec3d; + if ((r = get_child(json, "pos", json_type_object, true, &vec3d))) { + return r; + } + json_object *d; + if ((r = get_child(vec3d, "x", json_type_double, true, &d))) { + return r; + } + entity->pos.x = json_object_get_double(d); + + if ((r = get_child(vec3d, "y", json_type_double, true, &d))) { + return r; + } + entity->pos.y = json_object_get_double(d); + + if ((r = get_child(vec3d, "z", json_type_double, true, &d))) { + return r; + } + entity->pos.z = json_object_get_double(d); + return AC_E_OK; +} + +static int deserialize_response(json_object *obj, const char *type_str, const int id, ac_response_t **out) { + int r; + + if (!strcmp("ok", type_str)) { + ac_response_ok_t *ok; + if ((r = SALLOC(ac_response_t, &ok))) { + goto fail; + } + ok->type = AC_RESPONSE_OK; + ok->id = id; + + *out = (ac_response_t *) ok; + } else if (!strcmp("error", type_str)) { + ac_response_error_t *error; + if ((r = SALLOC(ac_response_error_t, &error))) { + goto fail; + } + error->type = AC_RESPONSE_ERROR; + error->id = id; + json_object *arg; + + if ((r = get_child(obj, "code", json_type_int, true, &arg))) { + ac_object_free((ac_obj_t *) error); + goto fail; + } + error->code = json_object_get_int(arg); + + if ((r = get_child(obj, "message", json_type_string, true, &arg))) { + ac_object_free((ac_obj_t *) error); + goto fail; + } + if ((r = strdup2(json_object_get_string(arg), &error->message))) { + ac_object_free((ac_obj_t *) error); + goto fail; + } + + *out = (ac_response_t *) error; + } else if (!strcmp("cmd_out", type_str)) { + ac_response_cmd_out_t *o; + if ((r = SALLOC(ac_response_cmd_out_t, &o))) { + goto fail; + } + o->type = AC_RESPONSE_CMD_OUT; + o->id = id; + json_object *arg; + + if ((r = get_child(obj, "sender", json_type_string, true, &arg))) { + ac_object_free((ac_obj_t *) o); + goto fail; + } + if ((r = strdup2(json_object_get_string(arg), &o->sender))) { + ac_object_free((ac_obj_t *) o); + goto fail; + } + + if ((r = get_child(obj, "out", json_type_string, true, &arg))) { + ac_object_free((ac_obj_t *) o); + goto fail; + } + if ((r = strdup2(json_object_get_string(arg), &o->out))) { + ac_object_free((ac_obj_t *) o); + goto fail; + } + + *out = (ac_response_t *) o; + } else if (!strcmp("cmd_result", type_str)) { + ac_response_cmd_result_t *res; + if ((r = SALLOC(ac_response_cmd_result_t, &res))) { + goto fail; + } + res->type = AC_RESPONSE_CMD_RESULT; + res->id = id; + json_object *arg; + + if ((r = get_child(obj, "result", json_type_int, true, &arg))) { + ac_object_free((ac_obj_t *) res); + goto fail; + } + res->result = json_object_get_int(arg); + + if ((r = get_child(obj, "success", json_type_boolean, true, &arg))) { + ac_object_free((ac_obj_t *) res); + goto fail; + } + res->success = json_object_get_boolean(arg); + + *out = (ac_response_t *) res; + } else { + LOGEV("Invalid response type: '%s'.", type_str); + r = AC_E_INVALID_RESPONSE; + goto fail; + } + return AC_E_OK; + fail: + if (obj) { + json_object_put(obj); + } + return r; +} + +static int deserialize_event(json_object *obj, const char *type_str, ac_event_t **out) { + LOGDV("Deserializing event type '%s'", type_str); + int r; + + if (!strcmp("join", type_str)) { + ac_event_player_join_t *join; + if ((r = SALLOC(ac_event_player_join_t, &join))) { + goto fail; + } + join->type = AC_EVENT_PLAYER_JOIN; + + json_object *arg; + if ((r = get_child(obj, "player", json_type_object, true, &arg))) { + ac_object_free((ac_obj_t *) join); + goto fail; + } + if ((r = deserialize_entity(arg, &join->player))) { + ac_object_free((ac_obj_t *) join); + goto fail; + } + + *out = (ac_event_t *) join; + } else if (!strcmp("disconnect", type_str)) { + ac_event_player_disconnect_t *disconnect; + if ((r = SALLOC(ac_event_player_disconnect_t, &disconnect))) { + goto fail; + } + disconnect->type = AC_EVENT_PLAYER_DISCONNECT; + + json_object *arg; + if ((r = get_child(obj, "player", json_type_object, true, &arg))) { + ac_object_free((ac_obj_t *) disconnect); + goto fail; + } + if ((r = deserialize_entity(arg, &disconnect->player))) { + ac_object_free((ac_obj_t *) disconnect); + goto fail; + } + + if ((r = get_child(obj, "reason", json_type_string, true, &arg))) { + ac_object_free((ac_obj_t *) disconnect); + goto fail; + } + if ((r = strdup2(json_object_get_string(arg), &disconnect->reason))) { + ac_object_free((ac_obj_t *) disconnect); + goto fail; + } + + *out = (ac_event_t *) disconnect; + } else if (!strcmp("message", type_str)) { + ac_event_player_message_t *message; + if ((r = SALLOC(ac_event_player_message_t, &message))) { + goto fail; + } + message->type = AC_EVENT_PLAYER_MESSAGE; + + json_object *arg; + if ((r = get_child(obj, "player", json_type_object, true, &arg))) { + ac_object_free((ac_obj_t *) message); + goto fail; + } + if ((r = deserialize_entity(arg, &message->player))) { + ac_object_free((ac_obj_t *) message); + goto fail; + } + + if ((r = get_child(obj, "text", json_type_string, true, &arg))) { + ac_object_free((ac_obj_t *) message); + goto fail; + } + if ((r = strdup2(json_object_get_string(arg), &message->text))) { + ac_object_free((ac_obj_t *) message); + goto fail; + } + + *out = (ac_event_t *) message; + } else if (!strcmp("death", type_str)) { + ac_event_entity_death_t *death; + if ((r = SALLOC(ac_event_entity_death_t, &death))) { + goto fail; + } + death->type = AC_EVENT_ENTITY_DEATH; + + json_object *arg; + if ((r = get_child(obj, "entity", json_type_object, true, &arg))) { + ac_object_free((ac_obj_t *) death); + goto fail; + } + if ((r = deserialize_entity(arg, &death->entity))) { + ac_object_free((ac_obj_t *) death); + goto fail; + } + + if ((r = get_child(obj, "message", json_type_string, true, &arg))) { + ac_object_free((ac_obj_t *) death); + goto fail; + } + if ((r = strdup2(json_object_get_string(arg), &death->message))) { + ac_object_free((ac_obj_t *) death); + goto fail; + } + + *out = (ac_event_t *) death; + } else if (!strcmp("lagging", type_str)) { + ac_event_lagging_t *lagging; + if ((r = SALLOC(ac_event_lagging_t, &lagging))) { + goto fail; + } + lagging->type = AC_EVENT_LAGGING; + + json_object *arg; + if ((r = get_child(obj, "ms", json_type_int, true, &arg))) { + goto fail; + } + lagging->ms = json_object_get_int64(arg); + + if ((r = get_child(obj, "ticks", json_type_int, true, &arg))) { + goto fail; + } + lagging->ticks = json_object_get_int64(arg); + + *out = (ac_event_t *) lagging; + } else { + LOGEV("Invalid event type: '%s'.", type_str); + r = AC_E_INVALID_RESPONSE; + goto fail; + } + + return AC_E_OK; + fail: + if (obj) { + json_object_put(obj); + } + return r; +} + +int deserialize(const char *string, unsigned int len, ac_obj_t **out) { + AC_CHECK_INIT; + json_object *obj; + const char *type_str; + int id; + bool has_id; + int r; + + if ((r = deserialize_pre(string, + len, + &obj, + &type_str, + false, + &has_id, + &id))) { + return r; + } + + if (has_id) { + return deserialize_response(obj, type_str, id, (ac_response_t **) out); + } else { + return deserialize_event(obj, type_str, (ac_event_t **) out); + } +} + +static int json_add(json_object *obj, const char *key, json_object *val) { + if (!val) { + LOGEV("Cannot initialize JSON child '%s'.", key); + return AC_E_MEMORY_ALLOCATION; + } + if ((json_object_object_add(obj, key, val))) { + LOGEV("Cannot put '%s' to the JSON object.", key); + json_object_put(val); + return AC_E_MEMORY_ALLOCATION; + } + return AC_E_OK; +} + +static int serialize_config(const ac_config_t *c, json_object *obj) { + int r; + if (c->pos) { + ac_vec3d_t p = *c->pos; + json_object *pos; + if (!(pos = json_object_new_object())) { + LOGE("Cannot initialize a new JSON object."); + return AC_E_MEMORY_ALLOCATION; + } + if ((r = json_add(pos, "x", json_object_new_double(p.x)))) { + json_object_put(pos); + return r; + } + if ((r = json_add(pos, "y", json_object_new_double(p.y)))) { + json_object_put(pos); + return r; + } + if ((r = json_add(pos, "z", json_object_new_double(p.z)))) { + json_object_put(pos); + return r; + } + if ((r = json_add(obj, "pos", pos))) { + return r; + } + } + if (c->world) { + enum ac_world world = *c->world; + const char *str; + switch (world) { + case overworld: + str = "overworld"; + break; + case nether: + str = "nether"; + break; + case end: + str = "end"; + break; + default: + LOGE("Invalid world supplied."); + return AC_E_INVALID_REQUEST; + } + if ((r = json_add(obj, "world", json_object_new_string(str)))) { + return r; + } + } + if (c->name) { + if ((r = json_add(obj, "name", json_object_new_string(c->name)))) { + return r; + } + } + if (c->rot) { + ac_vec2f_t t = *c->rot; + json_object *rot; + if (!(rot = json_object_new_object())) { + LOGE("Cannot initialize a new JSON object."); + return AC_E_MEMORY_ALLOCATION; + } + if ((r = json_add(rot, "x", json_object_new_double(t.x)))) { + json_object_put(rot); + return r; + } + if ((r = json_add(rot, "y", json_object_new_double(t.y)))) { + json_object_put(rot); + return r; + } + if ((r = json_add(obj, "rot", rot))) { + return r; + } + } + return AC_E_OK; +} + +int serialize_request(const ac_request_t *req, json_object **out) { + AC_CHECK_INIT; + if ((req->type & 192) != AC_TYPE_REQUEST) { + LOGE("Invalid argument to serialize_request. It should be a request."); + return AC_E_INVALID_REQUEST; + } + json_object *obj; + if (!(obj = json_object_new_object())) { + LOGE("Cannot initialize a new JSON object."); + return AC_E_MEMORY_ALLOCATION; + } + int r; + if ((r = json_add(obj, "id", json_object_new_int(req->id)))) { + goto fail; + } + switch (req->type) { + case AC_REQUEST_CMD: { + if ((r = json_add(obj, "type", json_object_new_string("cmd")))) { + goto fail; + } + ac_request_cmd_t *cmd = (ac_request_cmd_t *) req; + if ((r = json_add(obj, "cmd", json_object_new_string(cmd->cmd)))) { + goto fail; + } + if (cmd->config) { + json_object *c; + if (!(c = json_object_new_object())) { + LOGE("Cannot initialize a new JSON object."); + goto fail; + } + if ((r = serialize_config(cmd->config, c))) { + json_object_put(c); + goto fail; + } + if ((r = json_add(obj, "config", c))) { + goto fail; + } + } + break; + } + case AC_REQUEST_SET_CONFIG: { + if ((r = json_add(obj, "set_config", json_object_new_string("cmd")))) { + goto fail; + } + ac_request_set_config_t *set_config = (ac_request_set_config_t *) req; + json_object *c; + if (!(c = json_object_new_object())) { + LOGE("Cannot initialize a new JSON object."); + goto fail; + } + if ((r = serialize_config(&set_config->config, c))) { + json_object_put(c); + goto fail; + } + if ((r = json_add(obj, "config", c))) { + goto fail; + } + break; + } + default: { + LOGE("Invalid request supplied."); + r = AC_E_INVALID_REQUEST; + goto fail; + } + } + *out = obj; + return AC_E_OK; + fail: + { + if (obj) { + json_object_put(obj); + } + return r; + } +} + +int serializer_finalize(void) { + AC_CHECK_INIT; + json_tokener_reset(config->tok); +} diff --git a/client/libacron/private/serializer.h b/client/libacron/private/serializer.h new file mode 100644 index 0000000..1b9a38b --- /dev/null +++ b/client/libacron/private/serializer.h @@ -0,0 +1,18 @@ +/* + * Created by yuuta on 7/19/22. + */ + +#ifndef LIBAC_SERIALIZER_H +#define LIBAC_SERIALIZER_H + +/* An internal error code indicating that more data is required. + * Should not conflict with other error codes. */ +#define AC_E_SERIALIZER_CONTINUE -1 + +int deserialize(const char *string, unsigned int len, ac_obj_t **out); + +int serializer_finalize(void); + +int serialize_request(const ac_request_t *req, json_object **out); + +#endif /* LIBAC_SERIALIZER_H */ diff --git a/client/libacron/requests.c b/client/libacron/requests.c new file mode 100644 index 0000000..c71d6c2 --- /dev/null +++ b/client/libacron/requests.c @@ -0,0 +1,5 @@ +/* + * Created by yuuta on 7/19/22. + */ +#include "requests.h" + diff --git a/client/libacron/wic b/client/libacron/wic new file mode 160000 index 0000000..54d1240 --- /dev/null +++ b/client/libacron/wic @@ -0,0 +1 @@ +Subproject commit 54d124005c25ca376aa4e85c45f87b6c3bb95797 -- cgit v1.2.3