From e88490d1afc8ff825b68a4b85fd40c5a60f001dd Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Sun, 1 Nov 2020 21:19:24 +0000 Subject: [PATCH] version 1.0.0 --- .github/workflows/codeql-analysis.yml | 68 + .github/workflows/linux-ci.yml | 22 + .gitignore | 20 + CMakeLists.txt | 47 + LICENSE | 674 +++++++ README.md | 85 + code_of_conduct.md | 130 ++ include/CMakeLists.txt | 20 + include/DBusParse/dbus.hpp | 1769 ++++++++++++++++++ include/DBusParse/dbus_auth.hpp | 23 + include/DBusParse/dbus_print.hpp | 56 + include/DBusParse/dbus_random.hpp | 91 + include/DBusParse/dbus_serialize.hpp | 242 +++ include/DBusParse/dbus_utils.hpp | 67 + include/DBusParseUtils/endianness.hpp | 24 + include/DBusParseUtils/error.hpp | 48 + include/DBusParseUtils/parse.hpp | 418 +++++ include/DBusParseUtils/utils.hpp | 59 + src/CMakeLists.txt | 20 + src/DBusParse/CMakeLists.txt | 52 + src/DBusParse/dbus.cpp | 285 +++ src/DBusParse/dbus_auth.cpp | 73 + src/DBusParse/dbus_parse.cpp | 1560 +++++++++++++++ src/DBusParse/dbus_print.cpp | 343 ++++ src/DBusParse/dbus_random.cpp | 286 +++ src/DBusParse/dbus_serialize.cpp | 121 ++ src/DBusParse/dbus_utils.cpp | 373 ++++ src/DBusParseUtils/CMakeLists.txt | 45 + src/DBusParseUtils/parse.cpp | 124 ++ src/DBusParseUtils/utils.cpp | 73 + tests/CMakeLists.txt | 19 + tests/DBusParseUnitTests/CMakeLists.txt | 27 + tests/DBusParseUnitTests/dbus_unit_tests.cpp | 152 ++ 33 files changed, 7416 insertions(+) create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/linux-ci.yml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 code_of_conduct.md create mode 100644 include/CMakeLists.txt create mode 100644 include/DBusParse/dbus.hpp create mode 100644 include/DBusParse/dbus_auth.hpp create mode 100644 include/DBusParse/dbus_print.hpp create mode 100644 include/DBusParse/dbus_random.hpp create mode 100644 include/DBusParse/dbus_serialize.hpp create mode 100644 include/DBusParse/dbus_utils.hpp create mode 100644 include/DBusParseUtils/endianness.hpp create mode 100644 include/DBusParseUtils/error.hpp create mode 100644 include/DBusParseUtils/parse.hpp create mode 100644 include/DBusParseUtils/utils.hpp create mode 100644 src/CMakeLists.txt create mode 100644 src/DBusParse/CMakeLists.txt create mode 100644 src/DBusParse/dbus.cpp create mode 100644 src/DBusParse/dbus_auth.cpp create mode 100644 src/DBusParse/dbus_parse.cpp create mode 100644 src/DBusParse/dbus_print.cpp create mode 100644 src/DBusParse/dbus_random.cpp create mode 100644 src/DBusParse/dbus_serialize.cpp create mode 100644 src/DBusParse/dbus_utils.cpp create mode 100644 src/DBusParseUtils/CMakeLists.txt create mode 100644 src/DBusParseUtils/parse.cpp create mode 100644 src/DBusParseUtils/utils.cpp create mode 100644 tests/CMakeLists.txt create mode 100644 tests/DBusParseUnitTests/CMakeLists.txt create mode 100644 tests/DBusParseUnitTests/dbus_unit_tests.cpp diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..92e2a7d --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,68 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# ******** NOTE ******** + +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '19 1 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml new file mode 100644 index 0000000..3b1cbdb --- /dev/null +++ b/.github/workflows/linux-ci.yml @@ -0,0 +1,22 @@ +name: Linux build and test + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + compiler: [g++, clang++] + build_type: [Release, Debug] + use_sanitizers: [ON, OFF] + + steps: + - uses: actions/checkout@v2 + - name: build and test + run: | + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} -DUSE_SANITIZERS=${{ matrix.use_sanitizers }} .. + make -j $(nproc) + make test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab5c208 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Copyright 2020 Kevin Backhouse. +# +# This file is part of DBusParse. +# +# DBusParse 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. +# +# DBusParse 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 DBusParse. If not, see . + + +*~ +build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2bddcd4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,47 @@ +# Copyright 2020 Kevin Backhouse. +# +# This file is part of DBusParse. +# +# DBusParse 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. +# +# DBusParse 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 DBusParse. If not, see . + + +cmake_minimum_required(VERSION 3.10) + +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +enable_testing() + +# set the project name +project(DBusParse VERSION 1.0.0 DESCRIPTION "C++ Library for parsing D-Bus messages") + +# specify the C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +option(USE_SANITIZERS "Enable ASAN and UBSAN" OFF) + +add_compile_options(-Wall -Wextra -pedantic -Werror) + +if (USE_SANITIZERS) + set(SANITIZER_FLAGS "-fsanitize=address,undefined") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SANITIZER_FLAGS}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SANITIZER_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${SANITIZER_FLAGS}") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${SANITIZER_FLAGS}") +endif() + +add_subdirectory(include) +add_subdirectory(src) +add_subdirectory(tests) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..44efb2c --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +Copyright 2020 Kevin Backhouse. + +# DBusParse + +DBusParse is a C++ library for parsing +[D-Bus](https://www.freedesktop.org/wiki/Software/dbus/) messages. It +can also serialize a D-Bus message to an array of bytes, ready for +sending. DBusParse includes some simple utility functions for sending +and receiving D-Bus messages, but is not intended to be a fully +fledged D-Bus message handler. For example, it does not include logic +for generating message serial numbers. However, it is certainly +suitable to be used as a sub-component of a complete D-Bus +implementation. + +The parser is designed to be able to parse an incomplete input. You +can feed it some bytes, then save its state until you have received +more bytes. This is handy when reading bytes from a socket, because +you might not receive the complete message in a single read. It means, +for example, that the parser is well-suited for use in an +[epoll](https://man7.org/linux/man-pages/man7/epoll.7.html) event +loop. The parser implementation uses +[continuation-passing style](https://en.wikipedia.org/wiki/Continuation-passing_style). +The parser state includes a continuation function, which is invoked +when more bytes are received. + +I wrote DBusParse because I was doing security research on +[dbus-daemon](https://dbus.freedesktop.org/doc/dbus-daemon.1.html) and +several D-Bus services, and wanted to gain a thorough understanding of +the message format. I also, stupidly, thought that it wouldn't be +much work. Initially, I only needed the ability to send messages and I +wasn't aware of the +[dbus-send](https://dbus.freedesktop.org/doc/dbus-send.1.html) tool, +so I wrote a serializer. But I quickly discovered that I also needed +to parse the reply messages, so I started implementing a parser +too. Now that it has grown into a complete implementation, I figure I +might as well make it open source. + +## Usage + +DBusParse is a library. For an example of how to use it in a simple +application, please see the +[DBusParseDemo](https://github.com/kevinbackhouse/DBusParseDemo) demo +repository. + +## Building + +On Linux, you can build DBusParse as follows: + +```bash +mkdir build +cd build +cmake .. +make +``` + +To run the tests: + +```bash +make test +``` + +You can install DBusParse on your system like this: + +```bash +sudo make install +``` + +However, if you are not keen to use `sudo` to modify your system, you +can instead use DBusParse by including it as a sub-module in your project. +The [DBusParseDemo](https://github.com/kevinbackhouse/DBusParseDemo) +project does exactly that. + +## Design notes + +[dbus.hpp](/include/DBusParse/dbus.hpp) defines a class named +`DBusType` with sub-types such as `DBusTypeInt32` and `DBusTypeArray`. +Similarly, it defines a class named `DBusObject` with sub-types such as +`DBusObjectInt32` and `DBusObjectArray`. These class hierarchies +correspond to the types and objects defined in the [D-Bus +specification](http://dbus.freedesktop.org/doc/dbus-specification.html). + +Smart pointers are used to manage memory automatically. For example, +composite objects such as `DBusObjectStruct` use +[`std::unique_ptr`](https://en.cppreference.com/w/cpp/memory/unique_ptr) +to point to their child objects. diff --git a/code_of_conduct.md b/code_of_conduct.md new file mode 100644 index 0000000..90272fe --- /dev/null +++ b/code_of_conduct.md @@ -0,0 +1,130 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[INSERT CONTACT METHOD]. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. + diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 0000000..9635939 --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright 2020 Kevin Backhouse. +# +# This file is part of DBusParse. +# +# DBusParse 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. +# +# DBusParse 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 DBusParse. If not, see . + + +install(DIRECTORY DBusParseUtils/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/DBusParseUtils) +install(DIRECTORY DBusParse/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/DBusParse) diff --git a/include/DBusParse/dbus.hpp b/include/DBusParse/dbus.hpp new file mode 100644 index 0000000..672ac07 --- /dev/null +++ b/include/DBusParse/dbus.hpp @@ -0,0 +1,1769 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#pragma once + +#include "error.hpp" +#include "parse.hpp" +#include + +enum MessageType { + MSGTYPE_INVALID = 0, + MSGTYPE_METHOD_CALL = 1, + MSGTYPE_METHOD_RETURN = 2, + MSGTYPE_ERROR = 3, + MSGTYPE_SIGNAL = 4 +}; + +enum MessageFlags { + MSGFLAGS_EMPTY = 0x0, + MSGFLAGS_NO_REPLY_EXPECTED = 0x1, + MSGFLAGS_NO_AUTO_START = 0x2, + MSGFLAGS_ALLOW_INTERACTIVE_AUTHORIZATION = 0x4 +}; + +enum HeaderFieldName { + MSGHDR_INVALID = 0, + MSGHDR_PATH = 1, + MSGHDR_INTERFACE = 2, + MSGHDR_MEMBER = 3, + MSGHDR_ERROR_NAME = 4, + MSGHDR_REPLY_SERIAL = 5, + MSGHDR_DESTINATION = 6, + MSGHDR_SENDER = 7, + MSGHDR_SIGNATURE = 8, + MSGHDR_UNIX_FDS = 9 +}; + +class Serializer { +public: + Serializer() {} + virtual ~Serializer() {} + + virtual void writeByte(char c) = 0; + virtual void writeBytes(const char* buf, size_t bufsize) = 0; + virtual void writeUint16(uint16_t x) = 0; + virtual void writeUint32(uint32_t x) = 0; + virtual void writeUint64(uint64_t x) = 0; + virtual void writeDouble(double d) = 0; + + // Insert padding bytes until the position is at the next multiple + // of `alignment`. The alignment must be a power of 2. + virtual void insertPadding(size_t alignment) = 0; + + // Number of bytes serialized so far. + virtual size_t getPos() const = 0; + + virtual void recordArraySize(const std::function& f) = 0; +}; + +// Interface for pretty printing. +class Printer { +public: + virtual ~Printer() {} + + virtual void printChar(char c) = 0; + virtual void printUint8(uint8_t x) = 0; + virtual void printInt8(int8_t x) = 0; + virtual void printUint16(uint16_t x) = 0; + virtual void printInt16(int16_t x) = 0; + virtual void printUint32(uint32_t x) = 0; + virtual void printInt32(int32_t x) = 0; + virtual void printUint64(uint64_t x) = 0; + virtual void printInt64(int64_t x) = 0; + virtual void printDouble(double x) = 0; + virtual void printString(const std::string& str) = 0; + virtual void printNewline(size_t indent) = 0; +}; + +class DBusType; +class DBusTypeChar; +class DBusTypeBoolean; +class DBusTypeUint16; +class DBusTypeInt16; +class DBusTypeUint32; +class DBusTypeInt32; +class DBusTypeUint64; +class DBusTypeInt64; +class DBusTypeDouble; +class DBusTypeUnixFD; +class DBusTypeString; +class DBusTypePath; +class DBusTypeSignature; +class DBusTypeVariant; +class DBusTypeDictEntry; +class DBusTypeArray; +class DBusTypeStruct; + +class DBusTypeStorage; + +class DBusObject; +class DBusObjectChar; +class DBusObjectBoolean; +class DBusObjectUint16; +class DBusObjectInt16; +class DBusObjectUint32; +class DBusObjectInt32; +class DBusObjectUint64; +class DBusObjectInt64; +class DBusObjectDouble; +class DBusObjectUnixFD; +class DBusObjectString; +class DBusObjectPath; +class DBusObjectSignature; +class DBusObjectVariant; +class DBusObjectDictEntry; +class DBusObjectArray; +class DBusObjectStruct; + +class DBusType { +public: + // Visitor interface + class Visitor { + public: + virtual void visitChar(const DBusTypeChar&) = 0; + virtual void visitBoolean(const DBusTypeBoolean&) = 0; + virtual void visitUint16(const DBusTypeUint16&) = 0; + virtual void visitInt16(const DBusTypeInt16&) = 0; + virtual void visitUint32(const DBusTypeUint32&) = 0; + virtual void visitInt32(const DBusTypeInt32&) = 0; + virtual void visitUint64(const DBusTypeUint64&) = 0; + virtual void visitInt64(const DBusTypeInt64&) = 0; + virtual void visitDouble(const DBusTypeDouble&) = 0; + virtual void visitUnixFD(const DBusTypeUnixFD&) = 0; + virtual void visitString(const DBusTypeString&) = 0; + virtual void visitPath(const DBusTypePath&) = 0; + virtual void visitSignature(const DBusTypeSignature&) = 0; + virtual void visitVariant(const DBusTypeVariant&) = 0; + virtual void visitDictEntry(const DBusTypeDictEntry&) = 0; + virtual void visitArray(const DBusTypeArray&) = 0; + virtual void visitStruct(const DBusTypeStruct&) = 0; + }; + + // Continuation for parsing a type. + class ParseTypeCont { + public: + virtual ~ParseTypeCont() {} + virtual std::unique_ptr parse( + DBusTypeStorage& typeStorage, // Type allocator + const Parse::State& p, + const DBusType& t + ) = 0; + virtual std::unique_ptr parseCloseParen( + DBusTypeStorage& typeStorage, // Type allocator + const Parse::State& p + ) = 0; + }; + + // Continuation for parsing an object of this type. + template + class ParseObjectCont { + public: + virtual ~ParseObjectCont() {} + virtual std::unique_ptr parse( + const Parse::State& p, std::unique_ptr&& obj + ) = 0; + }; + + virtual ~DBusType() {} + + // When D-Bus objects are serialized, they are aligned. For example + // a UINT32 is 32-bit aligned and a STRUCT is 64-bit aligned. This + // virtual method returns the alignment for the type. It corresponds + // to the alignment column of this table: + // https://dbus.freedesktop.org/doc/dbus-specification.html#idm694 + virtual size_t alignment() const = 0; + + virtual void serialize(Serializer& s) const = 0; + + virtual void print(Printer& p) const = 0; + + std::string toString() const; + + // Create a parser for this type. The parameter is a continuation + // function, which will receive the `DBusObject` which was parsed. + // This method uses the template method design pattern to delegate + // most of the work to `mkObjectParserImpl` (below). But this + // wrapper takes care of alignment. + template + std::unique_ptr mkObjectParser( + const Parse::State& p, + std::unique_ptr>&& cont + ) const; + + virtual void accept(Visitor& visitor) const = 0; + +protected: + // Little endian parser. + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const = 0; + + // Big endian parser. + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const = 0; +}; + +class DBusTypeChar final : public DBusType { +public: + virtual size_t alignment() const override { return sizeof(char); } + + // DBusTypeChar is constant and doesn't have any parameters, + // so this instance is available for anyone to use. + static const DBusTypeChar instance_; + + virtual void serialize(Serializer& s) const override { + s.writeByte('y'); + } + + virtual void print(Printer& p) const override { + p.printChar('y'); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitChar(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; +}; + +class DBusTypeBoolean final : public DBusType { +public: + // D-Bus Booleans are 32 bits. + // https://dbus.freedesktop.org/doc/dbus-specification.html#idm694 + virtual size_t alignment() const override { return sizeof(uint32_t); } + + // DBusTypeBoolean is constant and doesn't have any parameters, + // so this instance is available for anyone to use. + static const DBusTypeBoolean instance_; + + virtual void serialize(Serializer& s) const override { + s.writeByte('b'); + } + + virtual void print(Printer& p) const override { + p.printChar('b'); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitBoolean(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; +}; + +class DBusTypeUint16 final : public DBusType { +public: + virtual size_t alignment() const override { return sizeof(uint16_t); } + + // DBusTypeUint16 is constant and doesn't have any parameters, + // so this instance is available for anyone to use. + static const DBusTypeUint16 instance_; + + virtual void serialize(Serializer& s) const override { + s.writeByte('q'); + } + + virtual void print(Printer& p) const override { + p.printChar('q'); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitUint16(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; +}; + +class DBusTypeInt16 final : public DBusType { +public: + virtual size_t alignment() const override { return sizeof(int16_t); } + + // DBusTypeInt16 is constant and doesn't have any parameters, + // so this instance is available for anyone to use. + static const DBusTypeInt16 instance_; + + virtual void serialize(Serializer& s) const override { + s.writeByte('n'); + } + + virtual void print(Printer& p) const override { + p.printChar('n'); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitInt16(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; +}; + +class DBusTypeUint32 final : public DBusType { +public: + virtual size_t alignment() const override { return sizeof(uint32_t); } + + // DBusTypeUint32 is constant and doesn't have any parameters, + // so this instance is available for anyone to use. + static const DBusTypeUint32 instance_; + + virtual void serialize(Serializer& s) const override { + s.writeByte('u'); + } + + virtual void print(Printer& p) const override { + p.printChar('u'); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitUint32(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; +}; + +class DBusTypeInt32 final : public DBusType { +public: + virtual size_t alignment() const override { return sizeof(int32_t); } + + // DBusTypeInt32 is constant and doesn't have any parameters, + // so this instance is available for anyone to use. + static const DBusTypeInt32 instance_; + + virtual void serialize(Serializer& s) const override { + s.writeByte('i'); + } + + virtual void print(Printer& p) const override { + p.printChar('i'); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitInt32(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; +}; + +class DBusTypeUint64 final : public DBusType { +public: + virtual size_t alignment() const override { return sizeof(uint64_t); } + + // DBusTypeUint64 is constant and doesn't have any parameters, + // so this instance is available for anyone to use. + static const DBusTypeUint64 instance_; + + virtual void serialize(Serializer& s) const override { + s.writeByte('t'); + } + + virtual void print(Printer& p) const override { + p.printChar('t'); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitUint64(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; +}; + +class DBusTypeInt64 final : public DBusType { +public: + virtual size_t alignment() const override { return sizeof(int64_t); } + + // DBusTypeInt64 is constant and doesn't have any parameters, + // so this instance is available for anyone to use. + static const DBusTypeInt64 instance_; + + virtual void serialize(Serializer& s) const override { + s.writeByte('x'); + } + + virtual void print(Printer& p) const override { + p.printChar('x'); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitInt64(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; +}; + +class DBusTypeDouble final : public DBusType { +public: + virtual size_t alignment() const override { return sizeof(int32_t); } + + // DBusTypeDouble is constant and doesn't have any parameters, + // so this instance is available for anyone to use. + static const DBusTypeDouble instance_; + + virtual void serialize(Serializer& s) const override { + s.writeByte('d'); + } + + virtual void print(Printer& p) const override { + p.printChar('d'); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitDouble(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; +}; + +class DBusTypeUnixFD final : public DBusType { +public: + virtual size_t alignment() const override { return sizeof(int32_t); } + + // DBusTypeUnixFD is constant and doesn't have any parameters, + // so this instance is available for anyone to use. + static const DBusTypeUnixFD instance_; + + virtual void serialize(Serializer& s) const override { + s.writeByte('h'); + } + + virtual void print(Printer& p) const override { + p.printChar('h'); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitUnixFD(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; +}; + +class DBusTypeString final : public DBusType { +public: + virtual size_t alignment() const override { + return sizeof(uint32_t); // For the length + } + + // DBusTypeString is constant and doesn't have any parameters, + // so this instance is available for anyone to use. + static const DBusTypeString instance_; + + virtual void serialize(Serializer& s) const override { + s.writeByte('s'); + } + + virtual void print(Printer& p) const override { + p.printChar('s'); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitString(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; +}; + +class DBusTypePath final : public DBusType { +public: + virtual size_t alignment() const override { + return sizeof(uint32_t); // For the length + } + + // DBusTypePath is constant and doesn't have any parameters, + // so this instance is available for anyone to use. + static const DBusTypePath instance_; + + virtual void serialize(Serializer& s) const override { + s.writeByte('o'); + } + + virtual void print(Printer& p) const override { + p.printChar('o'); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitPath(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; +}; + +class DBusTypeSignature final : public DBusType { +public: + virtual size_t alignment() const override { + return sizeof(char); // The length of a signature fits in a char + } + + // DBusTypeSignature is constant and doesn't have any parameters, + // so this instance is available for anyone to use. + static const DBusTypeSignature instance_; + + virtual void serialize(Serializer& s) const override { + s.writeByte('g'); + } + + virtual void print(Printer& p) const override { + p.printChar('g'); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitSignature(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; +}; + +class DBusTypeVariant final : public DBusType { +public: + virtual size_t alignment() const override { + // A serialized variant starts with a signature, which has a 1-byte + // alignment. + return sizeof(char); + } + + // DBusTypeVariant is constant and doesn't have any parameters, + // so this instance is available for anyone to use. + static const DBusTypeVariant instance_; + + virtual void serialize(Serializer& s) const override { + s.writeByte('v'); + } + + virtual void print(Printer& p) const override { + p.printChar('v'); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitVariant(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const override; +}; + +class DBusTypeDictEntry : public DBusType { + // Reference to the key type which is not owned by this class. + const DBusType& keyType_; + + // Reference to the value type which is not owned by this class. + const DBusType& valueType_; + +public: + // We keep references to `keyType` and `valueType`, but do not take + // ownership of them. + DBusTypeDictEntry(const DBusType& keyType, const DBusType& valueType) : + keyType_(keyType), valueType_(valueType) + {} + + const DBusType& getKeyType() const { return keyType_; } + const DBusType& getValueType() const { return valueType_; } + + virtual size_t alignment() const final override { + return sizeof(uint64_t); // Same as DBusTypeStruct + } + + virtual void serialize(Serializer& s) const final override { + s.writeByte('{'); + keyType_.serialize(s); + valueType_.serialize(s); + s.writeByte('}'); + } + + virtual void print(Printer& p) const override { + p.printChar('{'); + keyType_.print(p); + valueType_.print(p); + p.printChar('}'); + } + + virtual void accept(Visitor& visitor) const final override { + visitor.visitDictEntry(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const final override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const final override; +}; + +class DBusTypeArray : public DBusType { + // Reference to the base type which is not owned by this class. + const DBusType& baseType_; + +public: + // We keep a reference to the baseType, but do not take ownership of it. + explicit DBusTypeArray(const DBusType& baseType) : baseType_(baseType) {} + + const DBusType& getBaseType() const { return baseType_; } + + virtual size_t alignment() const final override { + return sizeof(uint32_t); // For the length + } + + virtual void serialize(Serializer& s) const final override { + s.writeByte('a'); + baseType_.serialize(s); + } + + virtual void print(Printer& p) const override { + p.printChar('a'); + baseType_.print(p); + } + + virtual void accept(Visitor& visitor) const final override { + visitor.visitArray(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const final override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const final override; +}; + +class DBusTypeStruct : public DBusType { + // The vector of field types is owned by this class, but it contains + // references to types which we do not own. + const std::vector> fieldTypes_; + +public: + // We take ownership of the vector, but not the field types which it + // references. + explicit DBusTypeStruct( + std::vector>&& fieldTypes + ) : + fieldTypes_(std::move(fieldTypes)) + {} + + const std::vector>& + getFieldTypes() const { + return fieldTypes_; + } + + virtual size_t alignment() const final override { + return sizeof(uint64_t); + } + + virtual void serialize(Serializer& s) const final override { + s.writeByte('('); + for (const DBusType& i: fieldTypes_) { + i.serialize(s); + } + s.writeByte(')'); + } + + virtual void print(Printer& p) const override { + p.printChar('('); + for (const DBusType& i: fieldTypes_) { + i.print(p); + } + p.printChar(')'); + } + + virtual void accept(Visitor& visitor) const final override { + visitor.visitStruct(*this); + } + +protected: + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const final override; + + virtual std::unique_ptr mkObjectParserImpl( + const Parse::State& p, std::unique_ptr>&& cont + ) const final override; +}; + +// `DBusType` uses references to refer to sub-types. This is because the +// type is usually embedded in a `DBusObject`, so there is no need to store +// it separately. But there are two occasions where we need to store types +// separately: +// +// 1. During parsing the signature of a `DBusObjectVariant`. +// 2. The element type of a `DBusObjectArray` with zero elements. +// +// Leaf types like `DBusTypeChar` do not need to be allocated because they +// have a global constant instance. So we only need to allocate memory for +// array and struct types, which are the only non-leaf types. This class +// allocates and stores objects of type `DBusTypeArray` and +// `DBusTypeStruct`. It is unlikely to be used much, so it just uses a pair +// of simply linked lists. +class DBusTypeStorage final { + class ArrayLink final : public DBusTypeArray { + const std::unique_ptr next_; + + public: + ArrayLink( + const DBusType& baseType, + std::unique_ptr&& next + ) : + DBusTypeArray(baseType), + next_(std::move(next)) + {} + }; + + class DictEntryLink final : public DBusTypeDictEntry { + const std::unique_ptr next_; + + public: + DictEntryLink( + const DBusType& keyType, + const DBusType& valueType, + std::unique_ptr&& next + ) : + DBusTypeDictEntry(keyType, valueType), + next_(std::move(next)) + {} + }; + + class StructLink final : public DBusTypeStruct { + const std::unique_ptr next_; + + public: + StructLink( + std::vector>&& fieldTypes, + std::unique_ptr&& next + ) : + DBusTypeStruct(std::move(fieldTypes)), + next_(std::move(next)) + {} + }; + + std::unique_ptr arrays_; + std::unique_ptr dict_entries_; + std::unique_ptr structs_; + +public: + DBusTypeStorage() {} + + const DBusTypeArray& allocArray(const DBusType& baseType) { + arrays_ = std::make_unique(baseType, std::move(arrays_)); + return *arrays_; + } + + const DBusTypeDictEntry& allocDictEntry( + const DBusType& keyType, const DBusType& valueType + ) { + dict_entries_ = + std::make_unique( + keyType, valueType, std::move(dict_entries_) + ); + return *dict_entries_; + } + + const DBusTypeStruct& allocStruct( + std::vector>&& fieldTypes + ) { + structs_ = std::make_unique( + std::move(fieldTypes), std::move(structs_) + ); + return *structs_; + } +}; + +class ObjectCastError : public Error { +public: + explicit ObjectCastError(const char* name) : + Error(std::string("ObjectCastError:" + std::string(name))) + {} +}; + +class DBusObject { +public: + // Visitor interface + class Visitor { + public: + virtual void visitChar(const DBusObjectChar&) = 0; + virtual void visitBoolean(const DBusObjectBoolean&) = 0; + virtual void visitUint16(const DBusObjectUint16&) = 0; + virtual void visitInt16(const DBusObjectInt16&) = 0; + virtual void visitUint32(const DBusObjectUint32&) = 0; + virtual void visitInt32(const DBusObjectInt32&) = 0; + virtual void visitUint64(const DBusObjectUint64&) = 0; + virtual void visitInt64(const DBusObjectInt64&) = 0; + virtual void visitDouble(const DBusObjectDouble&) = 0; + virtual void visitUnixFD(const DBusObjectUnixFD&) = 0; + virtual void visitString(const DBusObjectString&) = 0; + virtual void visitPath(const DBusObjectPath&) = 0; + virtual void visitSignature(const DBusObjectSignature&) = 0; + virtual void visitVariant(const DBusObjectVariant&) = 0; + virtual void visitDictEntry(const DBusObjectDictEntry&) = 0; + virtual void visitArray(const DBusObjectArray&) = 0; + virtual void visitStruct(const DBusObjectStruct&) = 0; + }; + + DBusObject() {} + virtual ~DBusObject() = default; + + virtual const DBusType& getType() const = 0; + + // Always call serializePadding before calling this method. + virtual void serializeAfterPadding(Serializer& s) const = 0; + + virtual void print(Printer& p, size_t indent) const = 0; + + virtual void accept(Visitor& visitor) const = 0; + + virtual const DBusObjectChar& toChar() const { + throw ObjectCastError("Char"); + } + virtual const DBusObjectBoolean& toBoolean() const { + throw ObjectCastError("Boolean"); + } + virtual const DBusObjectUint16& toUint16() const { + throw ObjectCastError("Uint16"); + } + virtual const DBusObjectInt16& toInt16() const { + throw ObjectCastError("Int16"); + } + virtual const DBusObjectUint32& toUint32() const { + throw ObjectCastError("Uint32"); + } + virtual const DBusObjectInt32& toInt32() const { + throw ObjectCastError("Int32"); + } + virtual const DBusObjectUint64& toUint64() const { + throw ObjectCastError("Uint64"); + } + virtual const DBusObjectInt64& toInt64() const { + throw ObjectCastError("Int64"); + } + virtual const DBusObjectDouble& toDouble() const { + throw ObjectCastError("Double"); + } + virtual const DBusObjectUnixFD& toUnixFD() const { + throw ObjectCastError("UnixFD"); + } + virtual const DBusObjectString& toString() const { + throw ObjectCastError("String"); + } + virtual const DBusObjectPath& toPath() const { + throw ObjectCastError("Path"); + } + virtual const DBusObjectSignature& toSignature() const { + throw ObjectCastError("Signature"); + } + virtual const DBusObjectVariant& toVariant() const { + throw ObjectCastError("Variant"); + } + virtual const DBusObjectDictEntry& toDictEntry() const { + throw ObjectCastError("DictEntry"); + } + virtual const DBusObjectArray& toArray() const { + throw ObjectCastError("Array"); + } + virtual const DBusObjectStruct& toStruct() const { + throw ObjectCastError("Struct"); + } + + void print(Printer& p) const { + print(p, 0); + p.printNewline(0); + } + + void serialize(Serializer& s) const { + s.insertPadding(getType().alignment()); + serializeAfterPadding(s); + } + + size_t serializedSize() const; +}; + +class DBusObjectChar final : public DBusObject { + const char c_; + +public: + explicit DBusObjectChar(char c); + + static std::unique_ptr mk(char c) { + return std::make_unique(c); + } + + virtual const DBusType& getType() const override { + return DBusTypeChar::instance_; + } + + virtual void serializeAfterPadding(Serializer& s) const override { + s.writeByte(c_); + } + + virtual void print(Printer& p, size_t) const override; + + virtual void accept(Visitor& visitor) const override { + visitor.visitChar(*this); + } + + const DBusObjectChar& toChar() const override { return *this; } + + char getValue() const { return c_; } +}; + +class DBusObjectBoolean final : public DBusObject { + const bool b_; + +public: + explicit DBusObjectBoolean(bool b); + + static std::unique_ptr mk(bool b) { + return std::make_unique(b); + } + + virtual const DBusType& getType() const override { + return DBusTypeBoolean::instance_; + } + + virtual void serializeAfterPadding(Serializer& s) const override { + // D-Bus Booleans are 32 bits. + // https://dbus.freedesktop.org/doc/dbus-specification.html#idm694 + s.writeUint32(static_cast(b_)); + } + + virtual void print(Printer& p, size_t) const override; + + virtual void accept(Visitor& visitor) const override { + visitor.visitBoolean(*this); + } + + const DBusObjectBoolean& toBoolean() const override { return *this; } + + bool getValue() const { return b_; } +}; + +class DBusObjectUint16 final : public DBusObject { + const uint16_t x_; + +public: + explicit DBusObjectUint16(uint16_t x); + + static std::unique_ptr mk(uint16_t x) { + return std::make_unique(x); + } + + virtual const DBusType& getType() const override { + return DBusTypeUint16::instance_; + } + + virtual void serializeAfterPadding(Serializer& s) const override { + s.writeUint16(x_); + } + + virtual void print(Printer& p, size_t) const override; + + virtual void accept(Visitor& visitor) const override { + visitor.visitUint16(*this); + } + + const DBusObjectUint16& toUint16() const override { return *this; } + + uint16_t getValue() const { return x_; } +}; + +class DBusObjectInt16 final : public DBusObject { + const int16_t x_; + +public: + explicit DBusObjectInt16(int16_t x); + + static std::unique_ptr mk(int16_t x) { + return std::make_unique(x); + } + + virtual const DBusType& getType() const override { + return DBusTypeInt16::instance_; + } + + virtual void serializeAfterPadding(Serializer& s) const override { + s.writeUint16(static_cast(x_)); + } + + virtual void print(Printer& p, size_t) const override; + + virtual void accept(Visitor& visitor) const override { + visitor.visitInt16(*this); + } + + const DBusObjectInt16& toInt16() const override { return *this; } + + int16_t getValue() const { return x_; } +}; + +class DBusObjectUint32 final : public DBusObject { + const uint32_t x_; + +public: + explicit DBusObjectUint32(uint32_t x); + + static std::unique_ptr mk(uint32_t x) { + return std::make_unique(x); + } + + virtual const DBusType& getType() const override { + return DBusTypeUint32::instance_; + } + + virtual void serializeAfterPadding(Serializer& s) const override { + s.writeUint32(x_); + } + + virtual void print(Printer& p, size_t) const override; + + virtual void accept(Visitor& visitor) const override { + visitor.visitUint32(*this); + } + + const DBusObjectUint32& toUint32() const override { return *this; } + + uint32_t getValue() const { return x_; } +}; + +class DBusObjectInt32 final : public DBusObject { + const int32_t x_; + +public: + explicit DBusObjectInt32(int32_t x); + + static std::unique_ptr mk(int32_t x) { + return std::make_unique(x); + } + + virtual const DBusType& getType() const override { + return DBusTypeInt32::instance_; + } + + virtual void serializeAfterPadding(Serializer& s) const override { + s.writeUint32(static_cast(x_)); + } + + virtual void print(Printer& p, size_t) const override; + + virtual void accept(Visitor& visitor) const override { + visitor.visitInt32(*this); + } + + const DBusObjectInt32& toInt32() const override { return *this; } + + int32_t getValue() const { return x_; } +}; + +class DBusObjectUint64 final : public DBusObject { + const uint64_t x_; + +public: + explicit DBusObjectUint64(uint64_t x); + + static std::unique_ptr mk(uint64_t x) { + return std::make_unique(x); + } + + virtual const DBusType& getType() const override { + return DBusTypeUint64::instance_; + } + + virtual void serializeAfterPadding(Serializer& s) const override { + s.writeUint64(x_); + } + + virtual void print(Printer& p, size_t) const override; + + virtual void accept(Visitor& visitor) const override { + visitor.visitUint64(*this); + } + + const DBusObjectUint64& toUint64() const override { return *this; } + + uint64_t getValue() const { return x_; } +}; + +class DBusObjectInt64 final : public DBusObject { + const int64_t x_; + +public: + explicit DBusObjectInt64(int64_t x); + + static std::unique_ptr mk(int64_t x) { + return std::make_unique(x); + } + + virtual const DBusType& getType() const override { + return DBusTypeInt64::instance_; + } + + virtual void serializeAfterPadding(Serializer& s) const override { + s.writeUint64(static_cast(x_)); + } + + virtual void print(Printer& p, size_t) const override; + + virtual void accept(Visitor& visitor) const override { + visitor.visitInt64(*this); + } + + const DBusObjectInt64& toInt64() const override { return *this; } + + int64_t getValue() const { return x_; } +}; + +class DBusObjectDouble final : public DBusObject { + const double d_; + +public: + explicit DBusObjectDouble(double d); + + static std::unique_ptr mk(double d) { + return std::make_unique(d); + } + + virtual const DBusType& getType() const override { + return DBusTypeDouble::instance_; + } + + virtual void serializeAfterPadding(Serializer& s) const override { + s.writeDouble(d_); + } + + virtual void print(Printer& p, size_t) const override; + + virtual void accept(Visitor& visitor) const override { + visitor.visitDouble(*this); + } + + const DBusObjectDouble& toDouble() const override { return *this; } + + double getValue() const { return d_; } +}; + +class DBusObjectUnixFD final : public DBusObject { + // Note: `i_` is not a file descriptor. It is an index into + // an array of file descriptors, which is passed out-of-band. + const uint32_t i_; + +public: + explicit DBusObjectUnixFD(uint32_t i); + + static std::unique_ptr mk(uint32_t i) { + return std::make_unique(i); + } + + virtual const DBusType& getType() const override { + return DBusTypeUnixFD::instance_; + } + + virtual void serializeAfterPadding(Serializer& s) const override { + s.writeUint32(i_); + } + + virtual void print(Printer& p, size_t) const override; + + virtual void accept(Visitor& visitor) const override { + visitor.visitUnixFD(*this); + } + + const DBusObjectUnixFD& toUnixFD() const override { return *this; } + + uint32_t getValue() const { return i_; } +}; + +class DBusObjectString final : public DBusObject { + const std::string str_; + +public: + explicit DBusObjectString(std::string&& str); + + static std::unique_ptr mk(std::string&& str) { + return std::make_unique(std::move(str)); + } + + virtual const DBusType& getType() const override { + return DBusTypeString::instance_; + } + + virtual void serializeAfterPadding(Serializer& s) const override { + uint32_t len = str_.size(); + s.writeUint32(len); + s.writeBytes(str_.c_str(), len+1); + } + + virtual void print(Printer& p, size_t) const override; + + virtual void accept(Visitor& visitor) const override { + visitor.visitString(*this); + } + + const DBusObjectString& toString() const override { return *this; } + + const std::string& getValue() const { return str_; } +}; + +class DBusObjectPath final : public DBusObject { + const std::string str_; + +public: + explicit DBusObjectPath(std::string&& str); + + static std::unique_ptr mk(std::string&& str) { + return std::make_unique(std::move(str)); + } + + virtual const DBusType& getType() const override { + return DBusTypePath::instance_; + } + + virtual void serializeAfterPadding(Serializer& s) const override { + uint32_t len = str_.size(); + s.writeUint32(len); + s.writeBytes(str_.c_str(), len+1); + } + + virtual void print(Printer& p, size_t) const override { + p.printString(str_); + } + + virtual void accept(Visitor& visitor) const override { + visitor.visitPath(*this); + } + + const DBusObjectPath& toPath() const override { return *this; } + + const std::string& getValue() const { return str_; } +}; + +// Almost identical to DBusObjectString and DBusObjectPath, except that a +// single byte is used to serialize the length. (The maximum length of a +// signature is 255.) +class DBusObjectSignature final : public DBusObject { + const std::string str_; + +public: + explicit DBusObjectSignature(std::string&& str); + + static std::unique_ptr mk(std::string&& str) { + return std::make_unique(std::move(str)); + } + + virtual const DBusType& getType() const override { + return DBusTypeSignature::instance_; + } + + virtual void serializeAfterPadding(Serializer& s) const override { + uint8_t len = str_.size(); + s.writeByte(len); + s.writeBytes(str_.c_str(), len+1); + } + + virtual void print(Printer& p, size_t) const override; + + virtual void accept(Visitor& visitor) const override { + visitor.visitSignature(*this); + } + + const DBusObjectSignature& toSignature() const override { return *this; } + + const std::string& getValue() const { return str_; } + + // Parse the sequence of types from the signature string. You need to + // supply a `DBusTypeStorage` so that the parser can allocate new + // types. The return value of the function contains references (into the + // `DBusTypeStorage` object), so you need to make sure that the storage + // doesn't get deallocated until you are finished with the types. + std::vector> toTypes( + DBusTypeStorage& typeStorage // Type allocator + ) const; +}; + +class DBusObjectVariant final : public DBusObject { + const std::unique_ptr object_; + const DBusObjectSignature signature_; + +public: + explicit DBusObjectVariant(std::unique_ptr&& object); + + static std::unique_ptr mk( + std::unique_ptr&& object + ) { + return std::make_unique(std::move(object)); + } + + virtual const DBusType& getType() const override { + return DBusTypeVariant::instance_; + } + + virtual void serializeAfterPadding(Serializer& s) const override { + signature_.serialize(s); + object_->serialize(s); + } + + virtual void print(Printer& p, size_t indent) const override; + + virtual void accept(Visitor& visitor) const override { + visitor.visitVariant(*this); + } + + const DBusObjectVariant& toVariant() const override { return *this; } + + const std::unique_ptr& getValue() const { return object_; } +}; + +class DBusObjectDictEntry : public DBusObject { + const std::unique_ptr key_; + const std::unique_ptr value_; + const DBusTypeDictEntry dictEntryType_; + +public: + DBusObjectDictEntry( + std::unique_ptr&& key, + std::unique_ptr&& value + ); + + static std::unique_ptr mk( + std::unique_ptr&& key, + std::unique_ptr&& value + ) { + return std::make_unique( + std::move(key), std::move(value) + ); + } + + virtual const DBusType& getType() const final override { + return dictEntryType_; + } + + virtual void serializeAfterPadding(Serializer& s) const final override { + key_->serialize(s); + value_->serialize(s); + } + + virtual void print(Printer& p, size_t indent) const override final; + + virtual void accept(Visitor& visitor) const override { + visitor.visitDictEntry(*this); + } + + const DBusObjectDictEntry& toDictEntry() const override { return *this; } + + const std::unique_ptr& getKey() const { return key_; } + const std::unique_ptr& getValue() const { return value_; } +}; + +class DBusObjectSeq final { + const std::vector> elements_; + +public: + explicit DBusObjectSeq(std::vector>&& elements); + + size_t length() const { return elements_.size(); } + + std::vector> elementTypes() const { + std::vector> types; + types.reserve(elements_.size()); + for (auto& element : elements_) { + types.push_back(std::cref(element->getType())); + } + return types; + } + + void print(Printer& p, size_t indent, char lbracket, char rbracket) const; + + void serialize(Serializer& s) const { + for (auto& p: elements_) { + p->serialize(s); + } + } + + const std::unique_ptr& getElement(size_t i) const { + return elements_.at(i); + } +}; + +class DBusObjectArray : public DBusObject { + const DBusObjectSeq seq_; + + // Note: this type contains a reference to the base type of the array + // type. It doesn't own the base type, so we need to make sure that it + // cannot become a dangling pointer. That's easy when the array has + // at least one element because we can just make it a reference to the + // type of element zero. But if there are zero elements, then we need + // to make sure that we own the base type. This problem is solved by + // DBusObjectArray0, which owns any struct or array types that are used + // in the base type. + const DBusTypeArray arrayType_; + +public: + // DBusObjectArray keeps a reference to baseType, so the + // lifetime of baseType must exceed that of the DBusObjectArray. + DBusObjectArray( + const DBusType& baseType, + std::vector>&& elements + ); + + // Constructing an array with zero elements needs to be handled as + // a special case to avoid the `arrayType_` field containing a dangling + // pointer. See the comment on `arrayType_`. + static std::unique_ptr mk0(const DBusType& baseType); + + // If the number of elements is non-zero, then we can deduce the base type + // from the zero'th element. + static std::unique_ptr mk1( + std::vector>&& elements + ) { + return std::make_unique( + elements.at(0)->getType(), std::move(elements) + ); + } + + static std::unique_ptr mk( + const DBusType& baseType, + std::vector>&& elements + ) { + if (elements.size() == 0) { + return mk0(baseType); + } else { + return mk1(std::move(elements)); + } + } + + virtual const DBusType& getType() const final override { return arrayType_; } + + virtual void serializeAfterPadding(Serializer& s) const final override { + s.recordArraySize( + [this, &s](uint32_t arraySize) { + s.writeUint32(arraySize); + s.insertPadding(arrayType_.getBaseType().alignment()); + const size_t posBefore = s.getPos(); + seq_.serialize(s); + const size_t posAfter = s.getPos(); + return posAfter - posBefore; + } + ); + } + + virtual void print(Printer& p, size_t indent) const override final; + + virtual void accept(Visitor& visitor) const override { + visitor.visitArray(*this); + } + + const DBusObjectArray& toArray() const override { return *this; } + + size_t numElements() const { return seq_.length(); } + + const std::unique_ptr& getElement(size_t i) const { + return seq_.getElement(i); + } +}; + +// An array with zero elements. +class DBusObjectArray0 final : public DBusObjectArray { + DBusTypeStorage typeStorage_; + +public: + DBusObjectArray0( + const DBusType& baseType, + std::vector>&& elements, + DBusTypeStorage&& typeStorage + ); +}; + +class DBusObjectStruct : public DBusObject { + const DBusObjectSeq seq_; + const DBusTypeStruct structType_; + +public: + explicit DBusObjectStruct(std::vector>&& elements); + + static std::unique_ptr mk( + std::vector>&& elements + ) { + return std::make_unique(std::move(elements)); + } + + virtual const DBusType& getType() const final override { return structType_; } + + virtual void serializeAfterPadding(Serializer& s) const final override { + seq_.serialize(s); + } + + virtual void print(Printer& p, size_t indent) const override final; + + virtual void accept(Visitor& visitor) const override { + visitor.visitStruct(*this); + } + + const DBusObjectStruct& toStruct() const override { return *this; } + + size_t numFields() const { return seq_.length(); } + + const std::unique_ptr& getElement(size_t i) const { + return seq_.getElement(i); + } +}; + +// Utility for constructing the message header. +class DBusHeaderField final : public DBusObjectStruct { +public: + DBusHeaderField(HeaderFieldName name, std::unique_ptr&& v); + + static std::unique_ptr mk( + HeaderFieldName name, std::unique_ptr&& v + ) { + return std::make_unique(name, std::move(v)); + } +}; + +class DBusMessageBody { + const DBusObjectSeq seq_; + +public: + explicit DBusMessageBody(std::vector>&& elements); + + // Create an empty message body. + static std::unique_ptr mk0(); + + // Create a message body with 1 element. + static std::unique_ptr mk1( + std::unique_ptr&& element + ); + + // Create a message body with multiple elements. + static std::unique_ptr mk( + std::vector>&& elements + ); + + std::string signature() const; + + void serialize(Serializer& s) const; + + size_t serializedSize() const; + + void print(Printer& p, size_t indent) const; + + size_t numElements() const { return seq_.length(); } + + const std::unique_ptr& getElement(size_t i) const { + return seq_.getElement(i); + } +}; + +class DBusMessage { + std::unique_ptr header_; + std::unique_ptr body_; + +public: + DBusMessage( + std::unique_ptr&& header, + std::unique_ptr&& body + ) : + header_(std::move(header)), body_(std::move(body)) + {} + + const DBusObjectStruct& getHeader() const { + return header_->toStruct(); + } + + const DBusMessageBody& getBody() const { + return *body_; + } + + // Read the endianness value in the header. + char getHeader_endianness() const { + return getHeader().getElement(0)->toChar().getValue(); + } + + // Read the message type in the header. + MessageType getHeader_messageType() const { + return static_cast( + getHeader().getElement(1)->toChar().getValue() + ); + } + + // Read the message flags in the header. + MessageFlags getHeader_messageFlags() const { + return static_cast( + getHeader().getElement(2)->toChar().getValue() + ); + } + + // Read the major protocol version in the header. + uint8_t getHeader_protocolVersion() const { + return static_cast( + getHeader().getElement(3)->toChar().getValue() + ); + } + + // Read the body size value in the header. + uint32_t getHeader_bodySize() const { + return getHeader().getElement(4)->toUint32().getValue(); + } + + // Read the reply serial number in the header. + uint32_t getHeader_replySerial() const { + return getHeader().getElement(5)->toUint32().getValue(); + } + + const DBusObjectVariant& getHeader_lookupField(HeaderFieldName name) const { + const DBusObjectArray& fields = getHeader().getElement(6)->toArray(); + const size_t n = fields.numElements(); + for (size_t i = 0; i < n; i++) { + const DBusObjectStruct& field = fields.getElement(i)->toStruct(); + if (field.getElement(0)->toChar().getValue() == name) { + return field.getElement(1)->toVariant(); + } + } + throw ObjectCastError("DBusMessage::getHeader_lookupField"); + } + + // Parse a `DBusMessage`. On success the message is assigned + // to `result`. + template + static std::unique_ptr parse( + std::unique_ptr& result + ); + + // Shorthand for `parse`. + static std::unique_ptr parseLE( + std::unique_ptr& result + ); + + // Shorthand for `parse`. + static std::unique_ptr parseBE( + std::unique_ptr& result + ); + + void serialize(Serializer& s) const; + + void print(Printer& p, size_t indent) const; +}; + +// The type of the header of a DBus message. +extern const DBusTypeStruct headerType; + +// Utility for downcasting to std::unique_ptr. +inline std::unique_ptr _obj(std::unique_ptr&& o) { + return std::unique_ptr(std::move(o)); +} + +// Make a deep copy of the type. The leaf types, like `DBusTypeChar` do not +// need to be allocated because they have a global constant instance. But +// we need to allocate memory for arrays and structs. This is done by adding +// elements to the `arrays` or `structs` vectors. +const DBusType& cloneType( + DBusTypeStorage& typeStorage, // Type allocator + const DBusType& t +); diff --git a/include/DBusParse/dbus_auth.hpp b/include/DBusParse/dbus_auth.hpp new file mode 100644 index 0000000..f06f8ad --- /dev/null +++ b/include/DBusParse/dbus_auth.hpp @@ -0,0 +1,23 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#pragma once + +#include + +void dbus_sendauth(const uid_t uid, const int fd); diff --git a/include/DBusParse/dbus_print.hpp b/include/DBusParse/dbus_print.hpp new file mode 100644 index 0000000..fcd9d34 --- /dev/null +++ b/include/DBusParse/dbus_print.hpp @@ -0,0 +1,56 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#pragma once + +#include "dbus.hpp" + +// Implementation of the `Printer` interface which sends its output to a +// file descriptor. +class PrinterFD final : public Printer { + // This class is not responsible for closing the file descriptor. + const int fd_; + + // Decimal or hexadecimal output? + const int base_; + + const size_t tabsize_; + + void printBytes(const char* buf, size_t bufsize); + +public: + PrinterFD(int fd, size_t base, size_t tabsize) : + fd_(fd), base_(base), tabsize_(tabsize) + {} + + void printChar(char c) override; + void printUint8(uint8_t x) override; + void printInt8(int8_t x) override; + void printUint16(uint16_t x) override; + void printInt16(int16_t x) override; + void printUint32(uint32_t x) override; + void printInt32(int32_t x) override; + void printUint64(uint64_t x) override; + void printInt64(int64_t x) override; + void printDouble(double x) override; + void printString(const std::string& str) override; + + // Print a newline character, followed by `tabsize_ * indent` + // space chracters. + void printNewline(size_t indent) override; +}; diff --git a/include/DBusParse/dbus_random.hpp b/include/DBusParse/dbus_random.hpp new file mode 100644 index 0000000..482fb50 --- /dev/null +++ b/include/DBusParse/dbus_random.hpp @@ -0,0 +1,91 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#pragma once + +#include +#include "dbus.hpp" + +// Parameters for generating random DBus types and objects. +class DBusRandom { +public: + // Return a randomly chosen letter corresponding to a valid DBus type. + // If `maxdepth` is zero, then the result should not be a basic type, + // not a container type (like array or struct). + // https://dbus.freedesktop.org/doc/dbus-specification.html#idm487 + virtual char randomType(const size_t maxdepth) = 0; + + // Choose a random number of fields for a struct. + virtual size_t randomNumFields() = 0; + + // Choose a random number of elements for an array. + virtual size_t randomArraySize() = 0; + + virtual char randomChar() = 0; + virtual bool randomBoolean() = 0; + virtual uint16_t randomUint16() = 0; + virtual uint32_t randomUint32() = 0; + virtual uint64_t randomUint64() = 0; + virtual double randomDouble() = 0; + virtual std::string randomString() = 0; + virtual std::string randomPath() = 0; +}; + +// Implementation of DBusRandom, using a Mersenne Twister +// pseudo random number generator. +class DBusRandomMersenne : public DBusRandom { + std::mt19937_64 gen_; // Random number generator + + // Keeps track of the total number of struct and array elements allocated + // so far, to prevent the total size from getting too big. + size_t maxsize_; + +public: + DBusRandomMersenne(std::uint_fast64_t seed, size_t maxsize); + + char randomType(const size_t maxdepth) final override; + + // Choose a random number of fields for a struct. + size_t randomNumFields() final override; + + char randomChar() final override; + bool randomBoolean() final override; + uint16_t randomUint16() final override; + uint32_t randomUint32() final override; + uint64_t randomUint64() final override; + double randomDouble() final override; + std::string randomString() final override; + std::string randomPath() final override; + + // Choose a random number of elements for an array. + size_t randomArraySize() final override; +}; + +// Generate a random DBusType. +const DBusType& randomType( + DBusRandom& r, + DBusTypeStorage& typeStorage, // Type allocator + const size_t maxdepth +); + +// Generate a random DBusObject. +std::unique_ptr randomObject( + DBusRandom& r, + const DBusType& t, + const size_t maxdepth +); diff --git a/include/DBusParse/dbus_serialize.hpp b/include/DBusParse/dbus_serialize.hpp new file mode 100644 index 0000000..ce2e7e6 --- /dev/null +++ b/include/DBusParse/dbus_serialize.hpp @@ -0,0 +1,242 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#pragma once + +#include +#include "dbus.hpp" + +// Round pos up to a multiple of alignment. +inline size_t alignup(size_t pos, size_t alignment) { + // Check that alignment is a power of 2. + assert((alignment & (alignment - 1)) == 0); + return (pos + alignment - 1) & ~(alignment - 1); +} + +// This implementation of the Serializer interface is +// used to count how many bytes the output buffer will need. +class SerializerDryRunBase : public Serializer { + size_t pos_; + +public: + SerializerDryRunBase() : pos_(0) {} + + virtual void writeByte(char) override { pos_ += sizeof(char); } + virtual void writeBytes(const char*, size_t bufsize) override { pos_ += bufsize; } + virtual void writeUint16(uint16_t) override { pos_ += sizeof(uint16_t); } + virtual void writeUint32(uint32_t) override { pos_ += sizeof(uint32_t); } + virtual void writeUint64(uint64_t) override { pos_ += sizeof(uint64_t); } + virtual void writeDouble(double) override { pos_ += sizeof(double); } + + virtual void insertPadding(size_t alignment) override; + + virtual size_t getPos() const override { return pos_; } +}; + +// This implementation of the Serializer interface is +// used to count how many bytes the output buffer will need. +class SerializerDryRun final : public SerializerDryRunBase { + size_t arrayCount_; + +public: + SerializerDryRun() : arrayCount_(0) {} + + size_t getArrayCount() const { return arrayCount_; } + + virtual void recordArraySize( + const std::function& f + ) override; +}; + +class SerializerInitArraySizes final : public SerializerDryRunBase { + std::vector& arraySizes_; // Not owned + +public: + SerializerInitArraySizes(std::vector& arraySizes) : + arraySizes_(arraySizes) + {} + + virtual void recordArraySize( + const std::function& f + ) override; +}; + +class SerializeToBufferBase : public Serializer { + size_t arrayCount_; + const std::vector& arraySizes_; // Not owned + +public: + SerializeToBufferBase(const std::vector& arraySizes) : + arrayCount_(0), arraySizes_(arraySizes) + {} + + virtual void recordArraySize( + const std::function& f + ) override; +}; + +template +class SerializeToBuffer final : public SerializeToBufferBase { + size_t pos_; + char* buf_; // Not owned by this class + +public: + SerializeToBuffer(const std::vector& arraySizes, char* buf) : + SerializeToBufferBase(arraySizes), pos_(0), buf_(buf) + {} + + virtual void writeByte(char c) override { + buf_[pos_] = c; + pos_ += sizeof(char); + } + + virtual void writeBytes(const char* buf, size_t bufsize) override { + memcpy(&buf_[pos_], buf, bufsize); + pos_ += bufsize; + } + + virtual void writeUint16(uint16_t x) override { + static_assert(endianness == LittleEndian || endianness == BigEndian); + if (endianness == LittleEndian) { + *(uint16_t*)&buf_[pos_] = htole16(x); + } else { + *(uint16_t*)&buf_[pos_] = htobe16(x); + } + pos_ += sizeof(uint16_t); + } + + virtual void writeUint32(uint32_t x) override { + static_assert(endianness == LittleEndian || endianness == BigEndian); + if (endianness == LittleEndian) { + *(uint32_t*)&buf_[pos_] = htole32(x); + } else { + *(uint32_t*)&buf_[pos_] = htobe32(x); + } + pos_ += sizeof(uint32_t); + } + + virtual void writeUint64(uint64_t x) override { + static_assert(endianness == LittleEndian || endianness == BigEndian); + if (endianness == LittleEndian) { + *(uint64_t*)&buf_[pos_] = htole64(x); + } else { + *(uint64_t*)&buf_[pos_] = htobe64(x); + } + pos_ += sizeof(uint64_t); + } + + virtual void writeDouble(double d) override { + // double is the same size as uint64_t, so we cast the value + // to uint64_t and use writeUint64. + static_assert(sizeof(double) == sizeof(uint64_t)); + union Cast { + double d_; + uint64_t x_; + }; + Cast c; + c.d_ = d; + writeUint64(c.x_); + } + + virtual void insertPadding(size_t alignment) override { + // Calculate the new position. + const size_t newpos = alignup(pos_, alignment); + // Insert zero bytes. + memset(&buf_[pos_], 0, newpos - pos_); + pos_ = newpos; + } + + virtual size_t getPos() const override { return pos_; } +}; + +// Warning: this serializer is only suitable for serializing types, not +// objects. That's because you can't put '\0' bytes into a std::string, and +// '\0' bytes are required for objects. (Types serialize to pure ASCII, so +// they're ok.) +template +class SerializeToString final : public SerializeToBufferBase { + std::string& str_; // Not owned + +public: + SerializeToString(const std::vector& arraySizes, std::string& str) : + SerializeToBufferBase(arraySizes), str_(str) + {} + + virtual void writeByte(char c) override { + str_.push_back(c); + } + + virtual void writeBytes(const char* buf, size_t bufsize) override { + str_.append(buf, bufsize); + } + + virtual void writeUint16(uint16_t x) override { + static_assert(endianness == LittleEndian || endianness == BigEndian); + if (endianness == LittleEndian) { + x = htole16(x); + } else { + x = htobe16(x); + } + writeBytes((const char*)&x, sizeof(x)); + } + + virtual void writeUint32(uint32_t x) override { + static_assert(endianness == LittleEndian || endianness == BigEndian); + if (endianness == LittleEndian) { + x = htole32(x); + } else { + x = htobe32(x); + } + writeBytes((const char*)&x, sizeof(x)); + } + + virtual void writeUint64(uint64_t x) override { + static_assert(endianness == LittleEndian || endianness == BigEndian); + if (endianness == LittleEndian) { + x = htole64(x); + } else { + x = htobe64(x); + } + writeBytes((const char*)&x, sizeof(x)); + } + + virtual void writeDouble(double d) override { + // double is the same size as uint64_t, so we cast the value + // to uint64_t and use writeUint64. + static_assert(sizeof(double) == sizeof(uint64_t)); + union Cast { + double d_; + uint64_t x_; + }; + Cast c; + c.d_ = d; + writeUint64(c.x_); + } + + virtual void insertPadding(size_t alignment) override { + // Check that alignment is a power of 2. + assert((alignment & (alignment - 1)) == 0); + // Calculate the new position. + const size_t pos = getPos(); + const size_t newpos = (pos + alignment - 1) & ~(alignment - 1); + // Insert zero bytes. + str_.append('\0', newpos - pos); + } + + virtual size_t getPos() const override { return str_.length(); } +}; diff --git a/include/DBusParse/dbus_utils.hpp b/include/DBusParse/dbus_utils.hpp new file mode 100644 index 0000000..5ebc101 --- /dev/null +++ b/include/DBusParse/dbus_utils.hpp @@ -0,0 +1,67 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#pragma once + +#include +#include "dbus.hpp" + +void send_dbus_message_with_fds( + const int fd, const DBusMessage& message, + const size_t nfds, const int* fds +); + +void send_dbus_message(const int fd, const DBusMessage& message); + +void print_dbus_object(const int fd, const DBusObject& obj); + +void print_dbus_message(const int fd, const DBusMessage& message); + +std::unique_ptr receive_dbus_message(const int fd); + +void dbus_method_call_with_fds( + const int fd, + const uint32_t serialNumber, + std::unique_ptr&& body, + std::string&& path, + std::string&& interface, + std::string&& destination, + std::string&& member, + const size_t nfds, + const int* fds +); + +void dbus_method_call( + const int fd, + const uint32_t serialNumber, + std::unique_ptr&& body, + std::string&& path, + std::string&& interface, + std::string&& destination, + std::string&& member +); + +void dbus_method_reply( + const int fd, + const uint32_t serialNumber, + const uint32_t replySerialNumber, // serial number that we are replying to + std::unique_ptr&& body, + std::string&& destination +); + +void dbus_send_hello(const int fd); diff --git a/include/DBusParseUtils/endianness.hpp b/include/DBusParseUtils/endianness.hpp new file mode 100644 index 0000000..08fc42d --- /dev/null +++ b/include/DBusParseUtils/endianness.hpp @@ -0,0 +1,24 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#pragma once + +enum Endianness { + LittleEndian, + BigEndian +}; diff --git a/include/DBusParseUtils/error.hpp b/include/DBusParseUtils/error.hpp new file mode 100644 index 0000000..e3a4d45 --- /dev/null +++ b/include/DBusParseUtils/error.hpp @@ -0,0 +1,48 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#pragma once + +#include + +// Exception class. Caught in main(). +class Error : public std::exception { + std::string msg_; + +public: + Error() = delete; // No default constructor. + explicit Error(const char* msg) : msg_(msg) {} + explicit Error(std::string&& msg) : msg_(std::move(msg)) {} + + const char* what() const noexcept override { + return msg_.c_str(); + } +}; + +// Exception class for system errors that include an errno. Caught in +// main(). +class ErrorWithErrno : public Error { + const int err_; + +public: + ErrorWithErrno() = delete; // No default constructor. + explicit ErrorWithErrno(const char* msg) : Error(msg), err_(errno) {} + explicit ErrorWithErrno(std::string&& msg) : Error(std::move(msg)), err_(errno) {} + + int getErrno() const { return err_; } +}; diff --git a/include/DBusParseUtils/parse.hpp b/include/DBusParseUtils/parse.hpp new file mode 100644 index 0000000..bd3696c --- /dev/null +++ b/include/DBusParseUtils/parse.hpp @@ -0,0 +1,418 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#pragma once + +#include +#include +#include +#include "endianness.hpp" + +// Continuation-passing-style parser implementation. The main class +// is `Parse`. You initialize the parser with a continuation of +// type `Parse::Cont`. The continuation consumes a fixed number of +// bytes and returns a new continuation to keep parsing the rest of +// the input. The continuation-passing design has several benefits: +// +// 1. It consumes the input in small chunks, so it is easy to feed it with +// network data. This means that we can easily pause parsing while we +// wait for more data, so it works well in conjunction with something +// like epoll. +// 2. Because the parser processes data incrementally it can reject +// invalid messages early, without needing to wait for the whole +// message to arrive. +// 3. The implementation does not use recursion, so it is impossible for a +// malicious input to trigger stack exhaustion in the parser. (Of course +// a stack exhaustion could still happen later if we run a recursive +// algorithm over the parsed object.) There is a still a parsing stack, +// but it consists of a linked list of continuations on the heap. +// +// Implementing this design in C++ may have been a mistake. + +// Thrown as an exception when the parser encounters an invalid input. +class ParseError final : public std::exception { + // Byte position of the parse error. + const size_t pos_; + + // Error message. + std::string msg_; + +public: + ParseError() = delete; // No default constructor. + explicit ParseError(size_t pos, const char* msg) : + pos_(pos), msg_(msg) + {} + explicit ParseError(size_t pos, std::string&& msg) : + pos_(pos), msg_(std::move(msg)) + {} + + size_t getPos() const noexcept { return pos_; } + + const char* what() const noexcept override { + return msg_.c_str(); + } +}; + +class Parse final { +public: + // This class has a virtual method which is the continuation function. + class Cont; + + // The parsing state is currently just the number of bytes parsed so far, + // but it is wrapped in a class to make it easier to add other status + // information in the future. A const reference to the parsing state is + // passed as an argument to `Parse::Cont::parse()`, so that the parsers + // can inspect the current state. For example, this is often used to + // calculate the alignment of the current byte position. + class State { + friend Parse; + + protected: + // The number of bytes parsed so far. + // This is used for calculating alignments. + size_t pos_; + + explicit State(size_t pos) : pos_(pos) {} + + public: + // No copy constructor + State(const State&) = delete; + + size_t getPos() const { return pos_; } + + static const State initialState_; + }; + +private: + State state_; + std::unique_ptr cont_; + +public: + // No copy constructor + Parse(const Parse&) = delete; + + // For initializing the parser. + explicit Parse(std::unique_ptr&& cont) : + state_(0), + cont_(std::move(cont)) + {} + + // Before calling this method, you should call `minRequiredBytes()` + // and `maxRequiredBytes()` to find + // out how many bytes the parser is prepared to accept. You must call + // this method with a value of `bufsize` that satisfies these rules: + // + // 1. minRequiredBytes() <= bufsize + // 2. bufsize <= maxRequiredBytes() + // + // The reason why the number of required bytes is specified as a range + // is to enable the caller to use a fixed-size buffer. A fixed size + // buffer of 255 bytes is guaranteed to be sufficient. So the caller + // can keep feeding the parser small chunks of bytes until parsing + // is complete. + void parse(const char* buf, size_t bufsize); + + // The number of bytes parsed so far. + size_t getPos() const { return state_.pos_; } + + // The minimum number of bytes that the parser needs to make + // progress. This simplifies the implementation of the parsers for simple + // types like `uint32_t`, because it means that they don't need to be + // able to accept partial input. For example, the parser for `uint32_t` + // can specify that it needs at least 4 bytes to make progress. Note + // that the result of this function is a `uint8_t`, which means that the + // caller doesn't need to implement an arbitrary sized buffer. A 255 + // byte fixed-size buffer is guaranteed to be sufficient. + uint8_t minRequiredBytes() const; + + // The maximum number of bytes that the parser is prepared to + // consume at this time. If this method returns 0 then it + // means that parsing is complete. + size_t maxRequiredBytes() const; +}; + +class Parse::Cont { +public: + virtual ~Cont() {} + virtual std::unique_ptr parse( + const Parse::State& p, const char* buf, size_t bufsize + ) = 0; + + // The minimum number of bytes that this continuation is willing to + // accept. + virtual uint8_t minRequiredBytes() const = 0; + + // The maximum number of bytes that this continuation is willing to + // accept. + virtual size_t maxRequiredBytes() const = 0; +}; + +// This continuation is used to indicate that parsing is complete. +// It does so by returning 0 in `maxRequiredBytes`. +class ParseStop final : public Parse::Cont { +public: + // This constructor is only public for std::make_unique's benefit. + // Use factory method `mk()` to construct. + ParseStop() {} + + virtual std::unique_ptr parse( + const Parse::State& p, const char* buf, size_t + ) override; + + // Factory method. + static std::unique_ptr mk(); + + uint8_t minRequiredBytes() const override { return 0; } + size_t maxRequiredBytes() const override { return 0; } +}; + +class ParseChar final : public Parse::Cont { +public: + class Cont { + public: + virtual ~Cont() {} + virtual std::unique_ptr parse( + const Parse::State& p, char c + ) = 0; + }; + +private: + // Continuation + const std::unique_ptr cont_; + +public: + // This constructor is only public for std::make_unique's benefit. + // Use factory method `mk()` to construct. + ParseChar(std::unique_ptr&& cont) : cont_(std::move(cont)) {} + + virtual std::unique_ptr parse( + const Parse::State& p, const char* buf, size_t + ) override; + + // Factory method. + static std::unique_ptr mk(std::unique_ptr&& cont); + + uint8_t minRequiredBytes() const override { return sizeof(char); } + size_t maxRequiredBytes() const override { return sizeof(char); } +}; + +template +class ParseUint16 final : public Parse::Cont { +public: + class Cont { + public: + virtual ~Cont() {} + virtual std::unique_ptr parse( + const Parse::State& p, uint16_t c + ) = 0; + }; + +private: + // Continuation + const std::unique_ptr cont_; + +public: + // This constructor is only public for std::make_unique's benefit. + // Use factory method `mk()` to construct. + ParseUint16(std::unique_ptr&& cont) : cont_(std::move(cont)) {} + + virtual std::unique_ptr parse( + const Parse::State& p, const char* buf, size_t bufsize + ) override { + (void)bufsize; + assert(bufsize == sizeof(uint16_t)); + static_assert(endianness == LittleEndian || endianness == BigEndian); + const uint16_t x = + endianness == LittleEndian ? + le16toh(*(uint16_t*)buf) : + be16toh(*(uint16_t*)buf); + return cont_->parse(p, x); + } + + // Factory method. + static std::unique_ptr mk(std::unique_ptr&& cont) { + return std::make_unique(std::move(cont)); + } + + uint8_t minRequiredBytes() const override { return sizeof(uint16_t); } + size_t maxRequiredBytes() const override { return sizeof(uint16_t); } +}; + +template +class ParseUint32 final : public Parse::Cont { +public: + class Cont { + public: + virtual ~Cont() {} + virtual std::unique_ptr parse( + const Parse::State& p, uint32_t c + ) = 0; + }; + +private: + // Continuation + const std::unique_ptr cont_; + +public: + // This constructor is only public for std::make_unique's benefit. + // Use factory method `mk()` to construct. + ParseUint32(std::unique_ptr&& cont) : cont_(std::move(cont)) {} + + virtual std::unique_ptr parse( + const Parse::State& p, const char* buf, size_t bufsize + ) override { + (void)bufsize; + assert(bufsize == sizeof(uint32_t)); + static_assert(endianness == LittleEndian || endianness == BigEndian); + const uint32_t x = + endianness == LittleEndian ? + le32toh(*(uint32_t*)buf) : + be32toh(*(uint32_t*)buf); + return cont_->parse(p, x); + } + + // Factory method. + static std::unique_ptr mk(std::unique_ptr&& cont) { + return std::make_unique(std::move(cont)); + } + + uint8_t minRequiredBytes() const override { return sizeof(uint32_t); } + size_t maxRequiredBytes() const override { return sizeof(uint32_t); } +}; + +template +class ParseUint64 final : public Parse::Cont { +public: + class Cont { + public: + virtual ~Cont() {} + virtual std::unique_ptr parse( + const Parse::State& p, uint64_t c + ) = 0; + }; + +private: + // Continuation + const std::unique_ptr cont_; + +public: + // This constructor is only public for std::make_unique's benefit. + // Use factory method `mk()` to construct. + ParseUint64(std::unique_ptr&& cont) : cont_(std::move(cont)) {} + + virtual std::unique_ptr parse( + const Parse::State& p, const char* buf, size_t bufsize + ) override { + (void)bufsize; + assert(bufsize == sizeof(uint64_t)); + static_assert(endianness == LittleEndian || endianness == BigEndian); + const uint64_t x = + endianness == LittleEndian ? + le64toh(*(uint64_t*)buf) : + be64toh(*(uint64_t*)buf); + return cont_->parse(p, x); + } + + // Factory method. + static std::unique_ptr mk(std::unique_ptr&& cont) { + return std::make_unique(std::move(cont)); + } + + uint8_t minRequiredBytes() const override { return sizeof(uint64_t); } + size_t maxRequiredBytes() const override { return sizeof(uint64_t); } +}; + +// Parser for a std::string with a known length. +class ParseNChars final : public Parse::Cont { +public: + class Cont { + public: + virtual ~Cont() {} + virtual std::unique_ptr parse( + const Parse::State& p, std::string&& str + ) = 0; + }; + +private: + // Buffer for the bytes. (May already contain some bytes + // which were already received on a previous iteration.) + std::string str_; + + // Number of bytes we expect to receive. + const size_t n_; + + // Continuation + std::unique_ptr cont_; + +public: + // This constructor is only public for std::make_unique's benefit. + // Use factory method `mk()` to construct. + ParseNChars(std::string&& str, size_t n, std::unique_ptr&& cont) : + str_(std::move(str)), n_(n), cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, const char* buf, size_t bufsize + ) override; + + // Factory method. + static std::unique_ptr mk( + const Parse::State& p, std::string&& str, size_t n, + std::unique_ptr&& cont + ); + + uint8_t minRequiredBytes() const override { return 0; } + size_t maxRequiredBytes() const override { return n_; } +}; + +// Parse N bytes and check that they are all zero bytes. +class ParseZeros final : public Parse::Cont { +public: + class Cont { + public: + virtual ~Cont() {} + virtual std::unique_ptr parse(const Parse::State& p) = 0; + }; + +private: + // Number of bytes we expect to receive. + const size_t n_; + + // Continuation + std::unique_ptr cont_; + +public: + // This constructor is only public for std::make_unique's benefit. + // Use factory method `mk()` to construct. + ParseZeros(size_t n, std::unique_ptr&& cont) : + n_(n), cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, const char* buf, size_t bufsize + ) override; + + // Factory method. + // Note: if `n == 0` then this will invoke the continuation immediately. + static std::unique_ptr mk( + const Parse::State& p, size_t n, std::unique_ptr&& cont + ); + + uint8_t minRequiredBytes() const override { return 0; } + size_t maxRequiredBytes() const override { return n_; } +}; diff --git a/include/DBusParseUtils/utils.hpp b/include/DBusParseUtils/utils.hpp new file mode 100644 index 0000000..91f07eb --- /dev/null +++ b/include/DBusParseUtils/utils.hpp @@ -0,0 +1,59 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#pragma once + +#include +#include + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +// This class automatically closes the file descriptor in its destructor. +class AutoCloseFD { + const int fd_; + +public: + AutoCloseFD() = delete; // No default constructor. + explicit AutoCloseFD(const int fd) : fd_(fd) {} + ~AutoCloseFD(); + + int get() const { return fd_; } +}; + +// Shorthand for creating a std::string from a string literal. +inline std::string _s(const char* s) { + return std::string(s); +} + +inline std::string _s(const std::string& s) { + return std::string(s); +} + +// Utility for constructing a std::vector. +template +std::vector::type> _vec(Ts&&... args) { + std::vector::type> result; + result.reserve(sizeof...(args)); + int bogus[] = { ((void)result.emplace_back(std::forward(args)), 0)... }; + static_assert(sizeof(bogus) == sizeof(int) * sizeof...(args)); + return result; +} + +// Get the process's start time by reading /proc/[pid]/stat. +uint64_t process_start_time(pid_t pid); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..799046c --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright 2020 Kevin Backhouse. +# +# This file is part of DBusParse. +# +# DBusParse 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. +# +# DBusParse 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 DBusParse. If not, see . + + +add_subdirectory(DBusParseUtils) +add_subdirectory(DBusParse) diff --git a/src/DBusParse/CMakeLists.txt b/src/DBusParse/CMakeLists.txt new file mode 100644 index 0000000..2c19c72 --- /dev/null +++ b/src/DBusParse/CMakeLists.txt @@ -0,0 +1,52 @@ +# Copyright 2020 Kevin Backhouse. +# +# This file is part of DBusParse. +# +# DBusParse 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. +# +# DBusParse 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 DBusParse. If not, see . + + +add_library(DBusParse SHARED) + +target_sources( + DBusParse PRIVATE + ../../include/DBusParse/dbus.hpp + dbus.cpp + ../../include/DBusParse/dbus_auth.hpp + dbus_auth.cpp + dbus_parse.cpp + ../../include/DBusParse/dbus_print.hpp + dbus_print.cpp + ../../include/DBusParse/dbus_random.hpp + dbus_random.cpp + ../../include/DBusParse/dbus_serialize.hpp + dbus_serialize.cpp + ../../include/DBusParse/dbus_utils.hpp + dbus_utils.cpp) + +target_include_directories( + DBusParse PRIVATE + $ + $) + +write_basic_package_version_file(DBusParseConfigVersion.cmake COMPATIBILITY ExactVersion) + +install(TARGETS DBusParse EXPORT DBusParseConfig + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install(EXPORT DBusParseConfig DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/DBusParse") + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/DBusParseConfigVersion.cmake DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/DBusParse") diff --git a/src/DBusParse/dbus.cpp b/src/DBusParse/dbus.cpp new file mode 100644 index 0000000..0838df0 --- /dev/null +++ b/src/DBusParse/dbus.cpp @@ -0,0 +1,285 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#include +#include "dbus.hpp" +#include "utils.hpp" + +static_assert( + !std::is_polymorphic::value, + "DBusTypeStorage does not have any virtual methods" +); + +const DBusTypeChar DBusTypeChar::instance_; +const DBusTypeBoolean DBusTypeBoolean::instance_; +const DBusTypeUint16 DBusTypeUint16::instance_; +const DBusTypeInt16 DBusTypeInt16::instance_; +const DBusTypeUint32 DBusTypeUint32::instance_; +const DBusTypeInt32 DBusTypeInt32::instance_; +const DBusTypeUint64 DBusTypeUint64::instance_; +const DBusTypeInt64 DBusTypeInt64::instance_; +const DBusTypeDouble DBusTypeDouble::instance_; +const DBusTypeUnixFD DBusTypeUnixFD::instance_; +const DBusTypeString DBusTypeString::instance_; +const DBusTypePath DBusTypePath::instance_; +const DBusTypeSignature DBusTypeSignature::instance_; +const DBusTypeVariant DBusTypeVariant::instance_; + +DBusObjectChar::DBusObjectChar(char c) : c_(c) {} + +DBusObjectBoolean::DBusObjectBoolean(bool b) : b_(b) {} + +DBusObjectUint16::DBusObjectUint16(uint16_t x) : x_(x) {} + +DBusObjectInt16::DBusObjectInt16(int16_t x) : x_(x) {} + +DBusObjectUint32::DBusObjectUint32(uint32_t x) : x_(x) {} + +DBusObjectInt32::DBusObjectInt32(int32_t x) : x_(x) {} + +DBusObjectUint64::DBusObjectUint64(uint64_t x) : x_(x) {} + +DBusObjectInt64::DBusObjectInt64(int64_t x) : x_(x) {} + +DBusObjectDouble::DBusObjectDouble(double d) : d_(d) {} + +DBusObjectUnixFD::DBusObjectUnixFD(uint32_t i) : i_(i) {} + +DBusObjectString::DBusObjectString(std::string&& str) : str_(std::move(str)) { + // String length must fit in a `uint32_t`. + assert((str_.size() >> 32) == 0); +} + +DBusObjectPath::DBusObjectPath(std::string&& str) : str_(std::move(str)) { + // String length must fit in a `uint32_t`. + assert((str_.size() >> 32) == 0); +} + +DBusObjectSignature::DBusObjectSignature(std::string&& str) : str_(std::move(str)) { + // String length must fit in a `uint8_t`. + assert((str_.size() >> 8) == 0); +} + +DBusObjectVariant::DBusObjectVariant( + std::unique_ptr&& object +) : + object_(std::move(object)), + signature_(object_->getType().toString()) +{} + +DBusObjectDictEntry::DBusObjectDictEntry( + std::unique_ptr&& key, + std::unique_ptr&& value +) : + key_(std::move(key)), + value_(std::move(value)), + dictEntryType_(key_->getType(), value_->getType()) +{} + +DBusObjectSeq::DBusObjectSeq(std::vector>&& elements) + : elements_(std::move(elements)) +{} + +DBusObjectArray::DBusObjectArray( + const DBusType& baseType, + std::vector>&& elements +) : + seq_(std::move(elements)), + arrayType_(baseType) +{} + +DBusObjectArray0::DBusObjectArray0( + const DBusType& baseType, + std::vector>&& elements, + DBusTypeStorage&& typeStorage +) : + DBusObjectArray(baseType, std::move(elements)), + typeStorage_(std::move(typeStorage)) +{} + +std::unique_ptr DBusObjectArray::mk0( + const DBusType& baseType +) { + // Clone the base type to ensure that it won't contain + // any dangling references. + DBusTypeStorage typeStorage; + const DBusType& newBaseType = cloneType(typeStorage, baseType); + + return std::make_unique( + newBaseType, + std::vector>(), + std::move(typeStorage) + ); +} + +DBusObjectStruct::DBusObjectStruct( + std::vector>&& elements +) : + seq_(std::move(elements)), + structType_(seq_.elementTypes()) +{} + +DBusHeaderField::DBusHeaderField( + HeaderFieldName name, std::unique_ptr&& v +) : + DBusObjectStruct( + _vec( + _obj(DBusObjectChar::mk(name)), + std::move(v) + ) + ) +{} + +static const DBusTypeStruct headerFieldType( + _vec( + std::reference_wrapper(DBusTypeChar::instance_), + std::reference_wrapper(DBusTypeVariant::instance_) + ) +); + +static const DBusTypeArray headerFieldsType(headerFieldType); + +// The type of the header of a DBus message. +const DBusTypeStruct headerType( + _vec( + std::reference_wrapper(DBusTypeChar::instance_), + std::reference_wrapper(DBusTypeChar::instance_), + std::reference_wrapper(DBusTypeChar::instance_), + std::reference_wrapper(DBusTypeChar::instance_), + std::reference_wrapper(DBusTypeUint32::instance_), + std::reference_wrapper(DBusTypeUint32::instance_), + std::reference_wrapper(headerFieldsType) + ) +); + +DBusMessageBody::DBusMessageBody( + std::vector>&& elements +) : + seq_(std::move(elements)) +{} + +std::unique_ptr DBusMessageBody::mk0() { + return std::make_unique( + std::vector>() + ); +} + +std::unique_ptr DBusMessageBody::mk1( + std::unique_ptr&& element +) { + return std::make_unique(_vec(std::move(element))); +} + +std::unique_ptr DBusMessageBody::mk( + std::vector>&& elements +) { + return std::make_unique(std::move(elements)); +} + +const DBusType& cloneType( + DBusTypeStorage& typeStorage, // Type allocator + const DBusType& t +) { + class CloneVisitor final : public DBusType::Visitor { + DBusTypeStorage& typeStorage_; // Not owned + const DBusType* result_; + + public: + CloneVisitor( + DBusTypeStorage& typeStorage // Type allocator + ) : + typeStorage_(typeStorage), + result_(0) + {} + + virtual void visitChar(const DBusTypeChar&) override { + result_ = &DBusTypeChar::instance_; + } + virtual void visitBoolean(const DBusTypeBoolean&) override { + result_ = &DBusTypeBoolean::instance_; + } + virtual void visitUint16(const DBusTypeUint16&) override { + result_ = &DBusTypeUint16::instance_; + } + virtual void visitInt16(const DBusTypeInt16&) override { + result_ = &DBusTypeInt16::instance_; + } + virtual void visitUint32(const DBusTypeUint32&) override { + result_ = &DBusTypeUint32::instance_; + } + virtual void visitInt32(const DBusTypeInt32&) override { + result_ = &DBusTypeInt32::instance_; + } + virtual void visitUint64(const DBusTypeUint64&) override { + result_ = &DBusTypeUint64::instance_; + } + virtual void visitInt64(const DBusTypeInt64&) override { + result_ = &DBusTypeInt64::instance_; + } + virtual void visitDouble(const DBusTypeDouble&) override { + result_ = &DBusTypeDouble::instance_; + } + virtual void visitUnixFD(const DBusTypeUnixFD&) override { + result_ = &DBusTypeUnixFD::instance_; + } + virtual void visitString(const DBusTypeString&) override { + result_ = &DBusTypeString::instance_; + } + virtual void visitPath(const DBusTypePath&) override { + result_ = &DBusTypePath::instance_; + } + virtual void visitSignature(const DBusTypeSignature&) override { + result_ = &DBusTypeSignature::instance_; + } + virtual void visitVariant(const DBusTypeVariant&) override { + result_ = &DBusTypeVariant::instance_; + } + + virtual void visitDictEntry(const DBusTypeDictEntry& t) override { + const DBusType& keyType = + cloneType(typeStorage_, t.getKeyType()); + const DBusType& valueType = + cloneType(typeStorage_, t.getValueType()); + // Allocate a `DBusTypeDictEntry` by adding it to `typeStorage_`. + result_ = &typeStorage_.allocDictEntry(keyType, valueType); + } + + virtual void visitArray(const DBusTypeArray& t) override { + const DBusType& baseType = + cloneType(typeStorage_, t.getBaseType()); + // Allocate a `DBusTypeArray` by adding it to `typeStorage_`. + result_ = &typeStorage_.allocArray(baseType); + } + + virtual void visitStruct(const DBusTypeStruct& t) override { + auto fieldTypes = t.getFieldTypes(); + const size_t n = fieldTypes.size(); + for (size_t i = 0; i < n; i++) { + fieldTypes[i] = cloneType(typeStorage_, fieldTypes[i]); + } + // Allocate a `DBusTypeStruct` by adding it to `structStorage_`. + result_ = &typeStorage_.allocStruct(std::move(fieldTypes)); + } + + const DBusType& getResult() const { return *result_; } + }; + + CloneVisitor visitor(typeStorage); + t.accept(visitor); + return visitor.getResult(); +} diff --git a/src/DBusParse/dbus_auth.cpp b/src/DBusParse/dbus_auth.cpp new file mode 100644 index 0000000..e7a4dad --- /dev/null +++ b/src/DBusParse/dbus_auth.cpp @@ -0,0 +1,73 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#include +#include +#include +#include +#include +#include +#include +#include "dbus_auth.hpp" + +static size_t write_string(char* buf, size_t pos, const char* str) { + const size_t len = strlen(str); + memcpy(&buf[pos], str, len); + return pos + len; +} + +static void sendbuf(const int fd, char* buf, size_t pos, size_t bufsize) { + const ssize_t n = write(fd, buf, pos); + printf("wrote %ld bytes\n", n); + memset(&buf[0], 0, bufsize); + const ssize_t r = read(fd, buf, bufsize); + printf("%ld: %s\n", r, buf); + printf("%x %x\n", buf[r-2], buf[r-1]); +} + +void dbus_sendauth(const uid_t uid, const int fd) { + char buf[1024]; + size_t pos = 0; + + buf[pos++] = '\0'; + pos = write_string(buf, pos, "AUTH EXTERNAL "); + + char uidstr[16]; + snprintf(uidstr, sizeof(uidstr), "%d", uid); + size_t n = strlen(uidstr); + for (size_t i = 0; i < n; i++) { + char tmp[4]; + snprintf(tmp, sizeof(tmp), "%.2x", (int)uidstr[i]); + pos = write_string(buf, pos, tmp); + } + pos = write_string(buf, pos, "\r\n"); + + sendbuf(fd, buf, pos, sizeof(buf)); + + pos = write_string(buf, 0, "NEGOTIATE_UNIX_FD\r\n"); + sendbuf(fd, buf, pos, sizeof(buf)); + + pos = write_string(buf, 0, "BEGIN\r\n"); + const ssize_t wr = write(fd, buf, pos); + if (wr < 0) { + const int err = errno; + fprintf(stderr, "write failed: %s\n", strerror(err)); + } else if (static_cast(wr) != pos) { + fprintf(stderr, "write incomplete: %ld < %lu\n", wr, pos); + } +} diff --git a/src/DBusParse/dbus_parse.cpp b/src/DBusParse/dbus_parse.cpp new file mode 100644 index 0000000..5ee9445 --- /dev/null +++ b/src/DBusParse/dbus_parse.cpp @@ -0,0 +1,1560 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#include "utils.hpp" +#include "dbus.hpp" + +static std::unique_ptr parseType( + DBusTypeStorage& typeStorage, // Type allocator + std::unique_ptr&& cont +) { + class ContDictCloseBrace final : public ParseChar::Cont { + DBusTypeStorage& typeStorage_; // Not owned + const DBusType& keyType_; + const DBusType& valueType_; + const std::unique_ptr cont_; + + public: + ContDictCloseBrace( + DBusTypeStorage& typeStorage, + const DBusType& keyType, + const DBusType& valueType, + std::unique_ptr&& cont + ) : + typeStorage_(typeStorage), + keyType_(keyType), + valueType_(valueType), + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, char c + ) override { + if (c != '}') { + throw ParseError( + p.getPos(), + "Expected a '}' character." + ); + } + return cont_->parse( + typeStorage_, p, typeStorage_.allocDictEntry(keyType_, valueType_) + ); + } + }; + + class ContDictValue final : public DBusType::ParseTypeCont { + const DBusType& keyType_; + std::unique_ptr cont_; + + public: + ContDictValue( + const DBusType& keyType, + std::unique_ptr&& cont + ) : + keyType_(keyType), + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + DBusTypeStorage& typeStorage, // Type allocator + const Parse::State&, + const DBusType& valueType + ) override { + return ParseChar::mk( + std::make_unique( + typeStorage, keyType_, valueType, std::move(cont_) + ) + ); + } + + virtual std::unique_ptr parseCloseParen( + DBusTypeStorage&, // Type allocator + const Parse::State& p + ) override { + throw ParseError( + p.getPos(), "Unexpected close paren while parsing dict entry type." + ); + } + }; + + class ContDictKey final : public DBusType::ParseTypeCont { + std::unique_ptr cont_; + + public: + ContDictKey(std::unique_ptr&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + DBusTypeStorage& typeStorage, // Type allocator + const Parse::State&, + const DBusType& keyType + ) override { + return parseType( + typeStorage, + std::make_unique(keyType, std::move(cont_)) + ); + } + + virtual std::unique_ptr parseCloseParen( + DBusTypeStorage&, // Type allocator + const Parse::State& p + ) override { + throw ParseError( + p.getPos(), "Unexpected close paren while parsing dict entry type." + ); + } + }; + + class ContStruct final : public DBusType::ParseTypeCont { + std::vector> fieldTypes_; + std::unique_ptr cont_; + + public: + ContStruct( + std::vector>&& fieldTypes, + std::unique_ptr&& cont + ) : + fieldTypes_(std::move(fieldTypes)), + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + DBusTypeStorage& typeStorage, // Type allocator + const Parse::State&, + const DBusType& t + ) override { + // Add the type to the vector of field types and continuing parsing + // field types. + fieldTypes_.push_back(t); + return parseType( + typeStorage, + std::make_unique( + std::move(fieldTypes_), + std::move(cont_) + ) + ); + } + + virtual std::unique_ptr parseCloseParen( + DBusTypeStorage& typeStorage, // Type allocator + const Parse::State& p + ) override { + // Allocate a `DBusTypeStruct` by adding it to `structStorage`. + return cont_->parse( + typeStorage, p, typeStorage.allocStruct(std::move(fieldTypes_)) + ); + } + }; + + class ContArray final : public DBusType::ParseTypeCont { + const std::unique_ptr cont_; + + public: + ContArray(std::unique_ptr&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + DBusTypeStorage& typeStorage, // Type allocator + const Parse::State& p, + const DBusType& t + ) override { + // Allocate a `DBusTypeArray` by adding it to `typeStorage`. + return cont_->parse( + typeStorage, p, typeStorage.allocArray(t) + ); + } + + virtual std::unique_ptr parseCloseParen( + DBusTypeStorage&, // Type allocator + const Parse::State& p + ) override { + throw ParseError( + p.getPos(), "Unexpected close paren while parsing array type." + ); + } + }; + + class Cont final : public ParseChar::Cont { + DBusTypeStorage& typeStorage_; // Not owned + std::unique_ptr cont_; + + public: + Cont( + DBusTypeStorage& typeStorage, // Type allocator + std::unique_ptr&& cont + ) : + typeStorage_(typeStorage), + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, char c + ) override { + switch (c) { + case 'y': + return cont_->parse(typeStorage_, p, DBusTypeChar::instance_); + case 'b': + return cont_->parse(typeStorage_, p, DBusTypeBoolean::instance_); + case 'q': + return cont_->parse(typeStorage_, p, DBusTypeUint16::instance_); + case 'n': + return cont_->parse(typeStorage_, p, DBusTypeInt16::instance_); + case 'u': + return cont_->parse(typeStorage_, p, DBusTypeUint32::instance_); + case 'i': + return cont_->parse(typeStorage_, p, DBusTypeInt32::instance_); + case 't': + return cont_->parse(typeStorage_, p, DBusTypeUint64::instance_); + case 'x': + return cont_->parse(typeStorage_, p, DBusTypeInt64::instance_); + case 'd': + return cont_->parse(typeStorage_, p, DBusTypeDouble::instance_); + case 'h': + return cont_->parse(typeStorage_, p, DBusTypeUnixFD::instance_); + case 's': + return cont_->parse(typeStorage_, p, DBusTypeString::instance_); + case 'o': + return cont_->parse(typeStorage_, p, DBusTypePath::instance_); + case 'g': + return cont_->parse(typeStorage_, p, DBusTypeSignature::instance_); + case 'v': + return cont_->parse(typeStorage_, p, DBusTypeVariant::instance_); + case 'a': + return parseType( + typeStorage_, std::make_unique(std::move(cont_)) + ); + case '(': + return parseType( + typeStorage_, + std::make_unique( + std::vector>(), + std::move(cont_) + ) + ); + case ')': + return cont_->parseCloseParen(typeStorage_, p); + case '{': + return parseType( + typeStorage_, std::make_unique(std::move(cont_)) + ); + + default: + throw ParseError( + p.getPos(), _s("Invalid type character: ") + std::to_string(int(c)) + ); + } + } + }; + + return ParseChar::mk( + std::make_unique(typeStorage, std::move(cont)) + ); +} + +// Utility for parsing the correct number of alignment bytes. +static std::unique_ptr parse_alignment( + const Parse::State& p, + const DBusType& t, std::unique_ptr&& cont +) { + const size_t pos = p.getPos(); + const size_t padding = (t.alignment() - 1) & -pos; + return ParseZeros::mk(p, padding, std::move(cont)); +} + +template +std::unique_ptr DBusType::mkObjectParser( + const Parse::State& p, + std::unique_ptr>&& cont +) const { + // Continuation for parsing padding bytes. + class PaddingCont final : public ParseZeros::Cont { + // Reference to the current type. + const DBusType& t_; + + std::unique_ptr> cont_; + + public: + PaddingCont( + const DBusType& t, + std::unique_ptr>&& cont + ) : + t_(t), cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse(const Parse::State& p) override { + return t_.mkObjectParserImpl(p, std::move(cont_)); + } + }; + + return parse_alignment( + p, *this, std::make_unique(*this, std::move(cont)) + ); +} + +// Template instantiation. This is to make sure that the little-endian +// instantiation of mkObjectParser is included in the library. +template +std::unique_ptr DBusType::mkObjectParser( + const Parse::State& p, + std::unique_ptr>&& cont +) const; + +// Template instantiation. This is to make sure that the big-endian +// instantiation of mkObjectParser is included in the library. +template +std::unique_ptr DBusType::mkObjectParser( + const Parse::State& p, + std::unique_ptr>&& cont +) const; + +template +static std::unique_ptr DBusTypeChar_mkObjectParserImpl( + std::unique_ptr>&& cont +) { + class Cont final : public ParseChar::Cont { + const std::unique_ptr> cont_; + + public: + Cont(std::unique_ptr>&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, char c + ) override { + return cont_->parse(p, DBusObjectChar::mk(c)); + } + }; + + return ParseChar::mk( + std::make_unique(std::move(cont)) + ); +} + +std::unique_ptr DBusTypeChar::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeChar_mkObjectParserImpl(std::move(cont)); +} + +std::unique_ptr DBusTypeChar::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeChar_mkObjectParserImpl(std::move(cont)); +} + +template +static std::unique_ptr DBusTypeBoolean_mkObjectParserImpl( + std::unique_ptr>&& cont +) { + class Cont final : public ParseUint32::Cont { + const std::unique_ptr> cont_; + + public: + Cont(std::unique_ptr>&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, uint32_t b + ) override { + // The value of x must be either 0 or 1. + if (b > 1) { + throw ParseError( + p.getPos(), "Boolean value that is not 0 or 1." + ); + } + return cont_->parse(p, DBusObjectBoolean::mk(b)); + } + }; + + return ParseUint32::mk( + std::make_unique(std::move(cont)) + ); +} + +std::unique_ptr DBusTypeBoolean::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeBoolean_mkObjectParserImpl(std::move(cont)); +} + +std::unique_ptr DBusTypeBoolean::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeBoolean_mkObjectParserImpl(std::move(cont)); +} + +template +static std::unique_ptr DBusTypeUint16_mkObjectParserImpl( + std::unique_ptr>&& cont +) { + class Cont final : public ParseUint16::Cont { + const std::unique_ptr> cont_; + + public: + Cont(std::unique_ptr>&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, uint16_t x + ) override { + return cont_->parse(p, DBusObjectUint16::mk(x)); + } + }; + + return ParseUint16::mk( + std::make_unique(std::move(cont)) + ); +} + +std::unique_ptr DBusTypeUint16::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeUint16_mkObjectParserImpl(std::move(cont)); +} + +std::unique_ptr DBusTypeUint16::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeUint16_mkObjectParserImpl(std::move(cont)); +} + +template +static std::unique_ptr DBusTypeInt16_mkObjectParserImpl( + std::unique_ptr>&& cont +) { + class Cont final : public ParseUint16::Cont { + const std::unique_ptr> cont_; + + public: + Cont(std::unique_ptr>&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, uint16_t x + ) override { + return cont_->parse(p, DBusObjectInt16::mk(static_cast(x))); + } + }; + + return ParseUint16::mk( + std::make_unique(std::move(cont)) + ); +} + +std::unique_ptr DBusTypeInt16::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeInt16_mkObjectParserImpl(std::move(cont)); +} + +std::unique_ptr DBusTypeInt16::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeInt16_mkObjectParserImpl(std::move(cont)); +} + +template +static std::unique_ptr DBusTypeUint32_mkObjectParserImpl( + std::unique_ptr>&& cont +) { + class Cont final : public ParseUint32::Cont { + const std::unique_ptr> cont_; + + public: + Cont(std::unique_ptr>&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, uint32_t x + ) override { + return cont_->parse(p, DBusObjectUint32::mk(x)); + } + }; + + return ParseUint32::mk( + std::make_unique(std::move(cont)) + ); +} + +std::unique_ptr DBusTypeUint32::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeUint32_mkObjectParserImpl(std::move(cont)); +} + +std::unique_ptr DBusTypeUint32::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeUint32_mkObjectParserImpl(std::move(cont)); +} + +template +static std::unique_ptr DBusTypeInt32_mkObjectParserImpl( + std::unique_ptr>&& cont +) { + class Cont final : public ParseUint32::Cont { + const std::unique_ptr> cont_; + + public: + Cont(std::unique_ptr>&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, uint32_t x + ) override { + return cont_->parse(p, DBusObjectInt32::mk(static_cast(x))); + } + }; + + return ParseUint32::mk( + std::make_unique(std::move(cont)) + ); +} + +std::unique_ptr DBusTypeInt32::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeInt32_mkObjectParserImpl(std::move(cont)); +} + +std::unique_ptr DBusTypeInt32::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeInt32_mkObjectParserImpl(std::move(cont)); +} + +template +static std::unique_ptr DBusTypeUint64_mkObjectParserImpl( + std::unique_ptr>&& cont +) { + class Cont final : public ParseUint64::Cont { + const std::unique_ptr> cont_; + + public: + Cont(std::unique_ptr>&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, uint64_t x + ) override { + return cont_->parse(p, DBusObjectUint64::mk(x)); + } + }; + + return ParseUint64::mk( + std::make_unique(std::move(cont)) + ); +} + +std::unique_ptr DBusTypeUint64::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeUint64_mkObjectParserImpl(std::move(cont)); +} + +std::unique_ptr DBusTypeUint64::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeUint64_mkObjectParserImpl(std::move(cont)); +} + +template +static std::unique_ptr DBusTypeInt64_mkObjectParserImpl( + std::unique_ptr>&& cont +) { + class Cont final : public ParseUint64::Cont { + const std::unique_ptr> cont_; + + public: + Cont(std::unique_ptr>&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, uint64_t x + ) override { + return cont_->parse(p, DBusObjectInt64::mk(static_cast(x))); + } + }; + + return ParseUint64::mk( + std::make_unique(std::move(cont)) + ); +} + +std::unique_ptr DBusTypeInt64::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeInt64_mkObjectParserImpl(std::move(cont)); +} + +std::unique_ptr DBusTypeInt64::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeInt64_mkObjectParserImpl(std::move(cont)); +} + +template +static std::unique_ptr DBusTypeDouble_mkObjectParserImpl( + std::unique_ptr>&& cont +) { + class Cont final : public ParseUint64::Cont { + const std::unique_ptr> cont_; + + public: + Cont(std::unique_ptr>&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, uint64_t i + ) override { + union Cast { + double d_; + uint64_t x_; + }; + Cast c; + c.x_ = i; + return cont_->parse(p, DBusObjectDouble::mk(c.d_)); + } + }; + + return ParseUint64::mk( + std::make_unique(std::move(cont)) + ); +} + +std::unique_ptr DBusTypeDouble::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeDouble_mkObjectParserImpl(std::move(cont)); +} + +std::unique_ptr DBusTypeDouble::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeDouble_mkObjectParserImpl(std::move(cont)); +} + +template +static std::unique_ptr DBusTypeUnixFD_mkObjectParserImpl( + std::unique_ptr>&& cont +) { + class Cont final : public ParseUint32::Cont { + const std::unique_ptr> cont_; + + public: + Cont(std::unique_ptr>&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, uint32_t i + ) override { + return cont_->parse(p, DBusObjectUnixFD::mk(i)); + } + }; + + return ParseUint32::mk( + std::make_unique(std::move(cont)) + ); +} + +std::unique_ptr DBusTypeUnixFD::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeUnixFD_mkObjectParserImpl(std::move(cont)); +} + +std::unique_ptr DBusTypeUnixFD::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeUnixFD_mkObjectParserImpl(std::move(cont)); +} + +// Utility for parsing a string with a known length. +static std::unique_ptr parseString( + const Parse::State& p, + size_t len, + std::unique_ptr&& cont +) { + class ZerosCont final : public ParseZeros::Cont { + std::string str_; + const std::unique_ptr cont_; + + public: + ZerosCont(std::string&& str, std::unique_ptr&& cont) : + str_(std::move(str)), cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse(const Parse::State& p) override { + return cont_->parse(p, std::move(str_)); + } + }; + + class StringCont final : public ParseNChars::Cont { + std::unique_ptr cont_; + + public: + StringCont(std::unique_ptr&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, std::string&& str + ) override { + return ParseZeros::mk( + p, 1, std::make_unique(std::move(str), std::move(cont_)) + ); + } + }; + + return ParseNChars::mk( + p, std::string(), len, std::make_unique(std::move(cont)) + ); +} + +// Utility for parsing a string with a 32-bit size prefix. +template +static std::unique_ptr parseString32( + std::unique_ptr&& cont +) { + class LengthCont final : public ParseUint32::Cont { + std::unique_ptr cont_; + + public: + LengthCont(std::unique_ptr&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, uint32_t len + ) override { + return parseString(p, len, std::move(cont_)); + } + }; + + // Parse the size + return ParseUint32::mk( + std::make_unique(std::move(cont)) + ); +} + +// Utility for parsing a string with an 8-bit size prefix. +std::unique_ptr parseString8( + std::unique_ptr&& cont +) { + class LengthCont final : public ParseChar::Cont { + std::unique_ptr cont_; + + public: + LengthCont(std::unique_ptr&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, char c + ) override { + uint8_t len = (uint8_t)c; + return parseString(p, len, std::move(cont_)); + } + }; + + // Parse the size + return ParseChar::mk( + std::make_unique(std::move(cont)) + ); +} + +template +static std::unique_ptr DBusTypeString_mkObjectParserImpl( + std::unique_ptr>&& cont +) { + class Cont final : public ParseNChars::Cont { + const std::unique_ptr> cont_; + + public: + Cont(std::unique_ptr>&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, std::string&& str + ) override { + return cont_->parse(p, DBusObjectString::mk(std::move(str))); + } + }; + + return parseString32( + std::make_unique(std::move(cont)) + ); +} + +std::unique_ptr DBusTypeString::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeString_mkObjectParserImpl(std::move(cont)); +} + +std::unique_ptr DBusTypeString::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeString_mkObjectParserImpl(std::move(cont)); +} + +template +static std::unique_ptr DBusTypePath_mkObjectParserImpl( + std::unique_ptr>&& cont +) { + class Cont final : public ParseNChars::Cont { + const std::unique_ptr> cont_; + + public: + Cont(std::unique_ptr>&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, std::string&& str + ) override { + return cont_->parse(p, DBusObjectPath::mk(std::move(str))); + } + }; + + return parseString32( + std::make_unique(std::move(cont)) + ); +} + +std::unique_ptr DBusTypePath::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypePath_mkObjectParserImpl(std::move(cont)); +} + +std::unique_ptr DBusTypePath::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypePath_mkObjectParserImpl(std::move(cont)); +} + +template +static std::unique_ptr DBusTypeSignature_mkObjectParserImpl( + std::unique_ptr>&& cont +) { + class Cont final : public ParseNChars::Cont { + const std::unique_ptr> cont_; + + public: + Cont(std::unique_ptr>&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, std::string&& str + ) override { + return cont_->parse(p, DBusObjectSignature::mk(std::move(str))); + } + }; + + return parseString8( + std::make_unique(std::move(cont)) + ); +} + +std::unique_ptr DBusTypeSignature::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeSignature_mkObjectParserImpl(std::move(cont)); +} + +std::unique_ptr DBusTypeSignature::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeSignature_mkObjectParserImpl(std::move(cont)); +} + +template +static std::unique_ptr DBusTypeVariant_mkObjectParserImpl( + std::unique_ptr>&& cont +) { + class ObjectCont final : public DBusType::ParseObjectCont { + // We need to own `typeStorage_` until the object parsing is complete + // so that the type doesn't go out of scope too soon. + DBusTypeStorage typeStorage_; + + const std::unique_ptr> cont_; + + public: + ObjectCont( + std::unique_ptr>&& cont + ) : + cont_(std::move(cont)) + {} + + DBusTypeStorage& getTypeStorage() { return typeStorage_; } + + virtual std::unique_ptr parse( + const Parse::State& p, std::unique_ptr&& obj + ) override { + return cont_->parse(p, DBusObjectVariant::mk(std::move(obj))); + } + }; + + class ZerosCont final : public ParseZeros::Cont { + std::unique_ptr typeStorage_; + const DBusType& t_; + std::unique_ptr cont_; + + public: + ZerosCont( + const DBusType& t, + std::unique_ptr&& cont + ) : + t_(t), + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse(const Parse::State& p) override { + return t_.mkObjectParser(p, std::move(cont_)); + } + }; + + class TypeCont final : public DBusType::ParseTypeCont { + const size_t endpos_; // Byte position where the signature should end + std::unique_ptr cont_; + + public: + TypeCont( + size_t endpos, + std::unique_ptr&& cont + ) : + endpos_(endpos), + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + DBusTypeStorage&, + const Parse::State& p, + const DBusType& t + ) override { + const size_t pos = p.getPos(); + if (pos != endpos_) { + throw ParseError( + pos, "Incorrect variant signature length." + ); + } + + // Parse the terminating zero byte. + return ParseZeros::mk( + p, 1, + std::make_unique(t, std::move(cont_)) + ); + } + + virtual std::unique_ptr parseCloseParen( + DBusTypeStorage&, + const Parse::State& p + ) override { + throw ParseError( + p.getPos(), "Unexpected close paren while parsing variant signature." + ); + } + }; + + class LengthCont final : public ParseChar::Cont { + std::unique_ptr cont_; + + public: + LengthCont(std::unique_ptr&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, char c + ) override { + uint8_t len = (uint8_t)c; + + const size_t pos = p.getPos(); + size_t endpos = 0; + if (__builtin_add_overflow(pos, len, &endpos)) { + throw ParseError(pos, "Signature length integer overflow."); + } + + DBusTypeStorage& typeStorage = cont_->getTypeStorage(); + return parseType( + typeStorage, + std::make_unique(endpos, std::move(cont_)) + ); + } + }; + + // Parse the length of the signature. It isn't actually needed for + // parsing the signature because we know that signature contains exactly + // one type, so all we're going to do with it is verify that it's + // correct. + return ParseChar::mk( + std::make_unique( + std::make_unique(std::move(cont)) + ) + ); +} + +std::unique_ptr DBusTypeVariant::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeVariant_mkObjectParserImpl(std::move(cont)); +} + +std::unique_ptr DBusTypeVariant::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeVariant_mkObjectParserImpl(std::move(cont)); +} + +template +static std::unique_ptr DBusTypeDictEntry_mkObjectParserImpl( + const Parse::State& p, + const DBusType& keyType, + const DBusType& valueType, + std::unique_ptr>&& cont +) { + class ValueCont final : public DBusType::ParseObjectCont { + std::unique_ptr key_; + const std::unique_ptr> cont_; + + public: + ValueCont( + std::unique_ptr&& key, + std::unique_ptr>&& cont + ) : + key_(std::move(key)), + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, std::unique_ptr&& value + ) override { + return cont_->parse( + p, + DBusObjectDictEntry::mk(std::move(key_), std::move(value)) + ); + } + }; + + class KeyCont final : public DBusType::ParseObjectCont { + const DBusType& valueType_; + std::unique_ptr> cont_; + + public: + KeyCont( + const DBusType& valueType, + std::unique_ptr>&& cont + ) : + valueType_(valueType), + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, std::unique_ptr&& key + ) override { + return valueType_.mkObjectParser( + p, + std::make_unique( + std::move(key), std::move(cont_) + ) + ); + } + }; + + return keyType.mkObjectParser( + p, + std::make_unique( + valueType, std::move(cont) + ) + ); +} + +std::unique_ptr DBusTypeDictEntry::mkObjectParserImpl( + const Parse::State& p, + std::unique_ptr>&& cont +) const { + return DBusTypeDictEntry_mkObjectParserImpl( + p, keyType_, valueType_, std::move(cont) + ); +} + +std::unique_ptr DBusTypeDictEntry::mkObjectParserImpl( + const Parse::State& p, + std::unique_ptr>&& cont +) const { + return DBusTypeDictEntry_mkObjectParserImpl( + p, keyType_, valueType_, std::move(cont) + ); +} + +template +static std::unique_ptr parseArray( + const Parse::State& p, + const DBusType& elemType, + size_t endpos, + std::vector>&& elements, + std::unique_ptr>&& cont +) { + class Cont final : public DBusType::ParseObjectCont { + const DBusType& elemType_; + const size_t endpos_; + std::vector> elements_; + std::unique_ptr> cont_; + + public: + Cont( + const DBusType& elemType, + size_t endpos, + std::vector>&& elements, + std::unique_ptr>&& cont + ) : + elemType_(elemType), + endpos_(endpos), + elements_(std::move(elements)), + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, std::unique_ptr&& obj + ) override { + elements_.push_back(std::move(obj)); + return parseArray( + p, elemType_, endpos_, + std::move(elements_), + std::move(cont_) + ); + } + }; + + const size_t pos = p.getPos(); + if (pos < endpos) { + return elemType.mkObjectParser( + p, + std::make_unique( + elemType, endpos, + std::move(elements), + std::move(cont) + ) + ); + } else if (pos == endpos) { + return cont->parse( + p, DBusObjectArray::mk(elemType, std::move(elements)) + ); + } else { + throw ParseError(pos, "Incorrect array length."); + } +} + +template +static std::unique_ptr DBusTypeArray_mkObjectParserImpl( + const DBusType& baseType, + std::unique_ptr>&& cont +) { + // Continuation for parsing padding bytes. + class PaddingCont final : public ParseZeros::Cont { + const DBusType& elemType_; + const uint32_t len_; + std::unique_ptr> cont_; + + public: + PaddingCont( + const DBusType& elemType, + uint32_t len, + std::unique_ptr>&& cont + ) : + elemType_(elemType), + len_(len), + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse(const Parse::State& p) override { + const size_t pos = p.getPos(); + size_t endpos = 0; + if (__builtin_add_overflow(pos, len_, &endpos)) { + throw ParseError(pos, "Array length integer overflow."); + } + return parseArray( + p, elemType_, endpos, + std::vector>(), + std::move(cont_) + ); + } + }; + + class LengthCont final : public ParseUint32::Cont { + const DBusType& elemType_; + std::unique_ptr> cont_; + + public: + LengthCont( + const DBusType& elemType, + std::unique_ptr>&& cont + ) : + elemType_(elemType), + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, uint32_t len + ) override { + return parse_alignment( + p, elemType_, + std::make_unique( + elemType_, len, std::move(cont_) + ) + ); + } + }; + + // Parse the size + return ParseUint32::mk( + std::make_unique(baseType, std::move(cont)) + ); +} + +std::unique_ptr DBusTypeArray::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeArray_mkObjectParserImpl(baseType_, std::move(cont)); +} + +std::unique_ptr DBusTypeArray::mkObjectParserImpl( + const Parse::State&, + std::unique_ptr>&& cont +) const { + return DBusTypeArray_mkObjectParserImpl(baseType_, std::move(cont)); +} + +// Continuation argument to parseObjects. +template +class ParseObjectsCont { +protected: + std::vector> objects_; + +public: + virtual ~ParseObjectsCont() {} + + void addObject(std::unique_ptr&& obj) { + objects_.push_back(std::move(obj)); + } + + virtual std::unique_ptr parse(const Parse::State& p) = 0; +}; + +template +static std::unique_ptr parseObjects( + const Parse::State& p, + const std::vector>& types, + size_t i, + std::unique_ptr>&& cont +) { + class Cont final : public DBusType::ParseObjectCont { + const std::vector>& types_; + const size_t i_; + std::unique_ptr> cont_; + + public: + Cont( + const std::vector>& types, + size_t i, + std::unique_ptr>&& cont + ) : + types_(types), + i_(i), + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p, std::unique_ptr&& obj + ) override { + cont_->addObject(std::move(obj)); + return parseObjects(p, types_, i_ + 1, std::move(cont_)); + } + }; + + if (i < types.size()) { + const DBusType& t = types[i]; + return t.mkObjectParser( + p, + std::make_unique(types, i, std::move(cont)) + ); + } else { + return cont->parse(p); + } +} + +template +static std::unique_ptr parseStruct( + const Parse::State& p, + const DBusTypeStruct& structType, + std::unique_ptr>&& cont +) { + class Cont final : public ParseObjectsCont { + const std::unique_ptr> cont_; + + public: + Cont(std::unique_ptr>&& cont) : + cont_(std::move(cont)) + {} + + virtual std::unique_ptr parse( + const Parse::State& p + ) override { + return cont_->parse( + p, + DBusObjectStruct::mk( + std::move(ParseObjectsCont::objects_) + ) + ); + } + }; + + return parseObjects( + p, structType.getFieldTypes(), 0, + std::make_unique(std::move(cont)) + ); +} + +std::unique_ptr DBusTypeStruct::mkObjectParserImpl( + const Parse::State& p, + std::unique_ptr>&& cont +) const { + return parseStruct(p, *this, std::move(cont)); +} + +std::unique_ptr DBusTypeStruct::mkObjectParserImpl( + const Parse::State& p, + std::unique_ptr>&& cont +) const { + return parseStruct(p, *this, std::move(cont)); +} + +std::vector> +DBusObjectSignature::toTypes( + DBusTypeStorage& typeStorage // Type allocator +) const { + class TypeCont final : public DBusType::ParseTypeCont { + const size_t endpos_; + std::vector>& result_; + + public: + TypeCont( + size_t endpos, + std::vector>& result + ) : + endpos_(endpos), + result_(result) + {} + + virtual std::unique_ptr parse( + DBusTypeStorage& typeStorage, + const Parse::State& p, + const DBusType& t + ) override { + result_.push_back(t); + if (p.getPos() < endpos_) { + return parseType( + typeStorage, std::make_unique(endpos_, result_) + ); + } else { + return ParseStop::mk(); + } + } + + virtual std::unique_ptr parseCloseParen( + DBusTypeStorage&, + const Parse::State& p + ) override { + throw ParseError( + p.getPos(), "Unexpected close paren while parsing signature." + ); + } + }; + + std::vector> result; + const size_t endpos = str_.size(); + Parse p(parseType(typeStorage, std::make_unique(endpos, result))); + + while (true) { + const size_t required = p.maxRequiredBytes(); + const size_t pos = p.getPos(); + if (required == 0) { + assert(pos == endpos); + return result; + } + if (required > endpos - pos) { + throw ParseError( + pos, "DBusType::fromSignature not enough bytes" + ); + } + p.parse(str_.c_str() + pos, required); + } +} + +template +std::unique_ptr DBusMessage::parse( + std::unique_ptr& result +) { + class BodyCont final : public ParseObjectsCont { + // We need to own `typeStorage_` until the object parsing is complete + // so that the type doesn't go out of scope too soon. + DBusTypeStorage typeStorage_; + + std::unique_ptr& result_; + + // We need to own `bodyTypes_` until the object parsing is complete + // so that the vector doesn't go out of scope too soon. + const std::vector> bodyTypes_; + + // Utility for initializing `bodyTypes_` in the constructor. + static std::vector> + getBodyTypesFromHeader( + DBusTypeStorage& typeStorage, + std::unique_ptr& result + ) { + const uint32_t bodySize = result->getHeader_bodySize(); + if (bodySize == 0) { + // No message body, so return an empty vector. + return std::vector>(); + } + + const DBusObjectSignature& bodySig = + result->getHeader_lookupField(MSGHDR_SIGNATURE).getValue()->toSignature(); + + return bodySig.toTypes(typeStorage); + } + + public: + explicit BodyCont( + std::unique_ptr& result + ) : + result_(result), + bodyTypes_(getBodyTypesFromHeader(typeStorage_, result_)) + {} + + const std::vector>& + getBodyTypes() const { + return bodyTypes_; + } + + virtual std::unique_ptr parse( + const Parse::State& + ) override { + result_->body_ = DBusMessageBody::mk( + std::move(ParseObjectsCont::objects_) + ); + return ParseStop::mk(); + } + }; + + // Continuation for parsing padding bytes. + class PaddingCont final : public ParseZeros::Cont { + std::unique_ptr cont_; + + public: + PaddingCont(std::unique_ptr cont) : cont_(std::move(cont)) {} + + virtual std::unique_ptr parse(const Parse::State& p) override { + auto& bodyTypes = cont_->getBodyTypes(); + return parseObjects(p, bodyTypes, 0, std::move(cont_)); + } + }; + + class HeaderCont final : public DBusType::ParseObjectCont { + std::unique_ptr& result_; + + public: + HeaderCont(std::unique_ptr& result) : result_(result) {} + + virtual std::unique_ptr parse( + const Parse::State& p, std::unique_ptr&& header + ) override { + result_ = std::make_unique( + std::move(header), DBusMessageBody::mk0() + ); + + // The body is 8-byte aligned. + return parse_alignment( + p, + DBusTypeUint64::instance_, + std::make_unique(std::make_unique(result_)) + ); + } + }; + + return headerType.mkObjectParser( + Parse::State::initialState_, + std::make_unique(result) + ); +} + +std::unique_ptr DBusMessage::parseLE( + std::unique_ptr& result +) { + return parse(result); +} + +std::unique_ptr DBusMessage::parseBE( + std::unique_ptr& result +) { + return parse(result); +} diff --git a/src/DBusParse/dbus_print.cpp b/src/DBusParse/dbus_print.cpp new file mode 100644 index 0000000..5f70078 --- /dev/null +++ b/src/DBusParse/dbus_print.cpp @@ -0,0 +1,343 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#include "dbus_print.hpp" +#include "utils.hpp" +#include + +void DBusObjectChar::print(Printer& p, size_t) const { + p.printUint8(c_); +} + +void DBusObjectBoolean::print(Printer& p, size_t) const { + p.printUint32(static_cast(b_)); +} + +void DBusObjectUint16::print(Printer& p, size_t) const { + p.printUint16(x_); +} + +void DBusObjectInt16::print(Printer& p, size_t) const { + p.printInt16(x_); +} + +void DBusObjectUint32::print(Printer& p, size_t) const { + p.printUint32(x_); +} + +void DBusObjectInt32::print(Printer& p, size_t) const { + p.printInt32(x_); +} + +void DBusObjectUint64::print(Printer& p, size_t) const { + p.printUint64(x_); +} + +void DBusObjectInt64::print(Printer& p, size_t) const { + p.printInt64(x_); +} + +void DBusObjectDouble::print(Printer& p, size_t) const { + p.printDouble(d_); +} + +void DBusObjectUnixFD::print(Printer& p, size_t) const { + p.printUint32(i_); +} + +void DBusObjectString::print(Printer& p, size_t) const { + p.printString(str_); +} + +void DBusObjectSignature::print(Printer& p, size_t) const { + p.printString(str_); +} + +void DBusObjectVariant::print(Printer& p, size_t indent) const { + p.printString(_s("Variant ")); + signature_.print(p, indent); + p.printNewline(indent); + object_->print(p, indent); +} + +void DBusObjectDictEntry::print(Printer& p, size_t indent) const { + p.printChar('{'); + ++indent; + p.printNewline(indent); + key_->print(p, indent); + p.printChar(','); + p.printNewline(indent); + value_->print(p, indent); + --indent; + p.printNewline(indent); + p.printChar('}'); +} + +void DBusObjectSeq::print( + Printer& p, size_t indent, char lbracket, char rbracket +) const { + p.printChar(lbracket); + ++indent; + const size_t n = elements_.size(); + if (n > 0) { + p.printNewline(indent); + elements_[0]->print(p, indent); + for (size_t i = 1; i < n; i++) { + p.printChar(','); + p.printNewline(indent); + elements_[i]->print(p, indent); + } + } + --indent; + p.printNewline(indent); + p.printChar(rbracket); +} + +void DBusObjectArray::print(Printer& p, size_t indent) const { + seq_.print(p, indent, '[', ']'); +} + +void DBusObjectStruct::print(Printer& p, size_t indent) const { + seq_.print(p, indent, '(', ')'); +} + +static void printMessageType(Printer& p, MessageType t) { + switch (t) { + case MSGTYPE_INVALID: p.printString("INVALID"); break; + case MSGTYPE_METHOD_CALL: p.printString("METHOD_CALL"); break; + case MSGTYPE_METHOD_RETURN: p.printString("METHOD_RETURN"); break; + case MSGTYPE_ERROR: p.printString("ERROR"); break; + case MSGTYPE_SIGNAL: p.printString("SIGNAL"); break; + default: p.printString("UNKNOWN"); break; + } +} + +static void printMessageFlags(Printer& p, MessageFlags flags) { + if (flags & MSGFLAGS_NO_REPLY_EXPECTED) { + p.printString(" NO_REPLY_EXPECTED"); + } + if (flags & MSGFLAGS_NO_AUTO_START) { + p.printString(" NO_AUTO_START"); + } + if (flags & MSGFLAGS_ALLOW_INTERACTIVE_AUTHORIZATION) { + p.printString(" ALLOW_INTERACTIVE_AUTHORIZATION"); + } +} + +static void printHeaderFieldName(Printer& p, HeaderFieldName name) { + switch (name) { + case MSGHDR_INVALID: p.printString("INVALID"); break; + case MSGHDR_PATH: p.printString("PATH"); break; + case MSGHDR_INTERFACE: p.printString("INTERFACE"); break; + case MSGHDR_MEMBER: p.printString("MEMBER"); break; + case MSGHDR_ERROR_NAME: p.printString("ERROR_NAME"); break; + case MSGHDR_REPLY_SERIAL: p.printString("REPLY_SERIAL"); break; + case MSGHDR_DESTINATION: p.printString("DESTINATION"); break; + case MSGHDR_SENDER: p.printString("SENDER"); break; + case MSGHDR_SIGNATURE: p.printString("SIGNATURE"); break; + case MSGHDR_UNIX_FDS: p.printString("UNIX_FDS"); break; + default: p.printString("UNKNOWN"); break; + } +} + +void DBusMessageBody::print(Printer& p, size_t indent) const { + seq_.print(p, indent, '(', ')'); +} + +void DBusMessage::print(Printer& p, size_t indent) const { + p.printString(_s("Header:")); + indent++; + p.printNewline(indent); + + const DBusObjectStruct& header = getHeader(); + p.printString("endianness: "); + p.printChar(getHeader_endianness()); + p.printNewline(indent); + + p.printString("message type: "); + printMessageType(p, getHeader_messageType()); + p.printNewline(indent); + + p.printString("message flags:"); + printMessageFlags(p, getHeader_messageFlags()); + p.printNewline(indent); + + p.printString("major protocol version: "); + p.printUint8(getHeader_protocolVersion()); + p.printNewline(indent); + + p.printString("body size: "); + p.printUint32(getHeader_bodySize()); + p.printNewline(indent); + + p.printString("serial number: "); + p.printUint32(getHeader_replySerial()); + p.printNewline(indent); + + p.printString("header fields:"); + const DBusObjectArray& headerFields = header.getElement(6)->toArray(); + const size_t numHeaderFields = headerFields.numElements(); + for (size_t i = 0; i < numHeaderFields; i++) { + const DBusObjectStruct& headerField = headerFields.getElement(i)->toStruct(); + p.printNewline(2); + printHeaderFieldName( + p, static_cast(headerField.getElement(0)->toChar().getValue()) + ); + p.printChar(':'); + p.printNewline(3); + headerField.getElement(1)->print(p, 3); + } + + if (body_) { + p.printNewline(0); + p.printString(_s("Body:")); + p.printNewline(indent); + body_->print(p, indent); + } +} + +template +static size_t numberToString(char* buf, T x, size_t base) { + static const char digits[36] = + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z' + }; + size_t n = 0; + do { + const size_t digit = x % base; + x = x / base; + buf[n] = digits[digit]; + ++n; + } while (x != 0); + + // The digits came out in reverse order, so reverse them. + size_t i = 0; + size_t j = n-1; + for (; i < j; ++i, --j) { + std::swap(buf[i], buf[j]); + } + + return i+j+1; +} + +void PrinterFD::printBytes(const char* buf, size_t bufsize) { + const int r = write(fd_, buf, bufsize); + if (r < 0) { + throw ErrorWithErrno("Write failed during pretty printing."); + } +} + +void PrinterFD::printChar(char c) { + printBytes(&c, sizeof(c)); +} + +void PrinterFD::printUint8(uint8_t x) { + char buf[8]; + const size_t n = numberToString(buf, x, base_); + printBytes(buf, n); +} + +void PrinterFD::printInt8(int8_t x) { + if (x < 0) { + printChar('-'); + printUint16(-static_cast(x)); + } else { + printUint16(static_cast(x)); + } +} + +void PrinterFD::printUint16(uint16_t x) { + char buf[16]; + const size_t n = numberToString(buf, x, base_); + printBytes(buf, n); +} + +void PrinterFD::printInt16(int16_t x) { + if (x < 0) { + printChar('-'); + printUint16(-static_cast(x)); + } else { + printUint16(static_cast(x)); + } +} + +void PrinterFD::printUint32(uint32_t x) { + char buf[32]; + const size_t n = numberToString(buf, x, base_); + printBytes(buf, n); +} + +void PrinterFD::printInt32(int32_t x) { + if (x < 0) { + printChar('-'); + printUint32(-static_cast(x)); + } else { + printUint32(static_cast(x)); + } +} + +void PrinterFD::printUint64(uint64_t x) { + char buf[64]; + const size_t n = numberToString(buf, x, base_); + printBytes(buf, n); +} + +void PrinterFD::printInt64(int64_t x) { + if (x < 0) { + printChar('-'); + printUint64(-static_cast(x)); + } else { + printUint64(static_cast(x)); + } +} + +void PrinterFD::printDouble(double d) { + char buf[128]; + const int n = snprintf(buf, sizeof(buf), "%f", d); + if (0 <= n && static_cast(n) <= sizeof(buf)) { + printBytes(buf, n); + } +} + +void PrinterFD::printString(const std::string& str) { + printBytes(str.c_str(), str.size()); +} + +// Print a newline character, followed by `tabsize_ * indent` +// space chracters. +void PrinterFD::printNewline(size_t indent) { + static const char spaces[66] = + "\n "; + size_t nspaces = tabsize_ * indent; + // The indent will usually be relatively small, so the fast + // case just prints the first `nspaces+1` characters of `spaces`. + // (The +1 is for the newline character at the beginning.) + if (likely(nspaces <= sizeof(spaces)-2)) { + printBytes(spaces, nspaces+1); + return; + } + printBytes(spaces, sizeof(spaces)-1); + nspaces -= sizeof(spaces)-2; + while (nspaces > sizeof(spaces)-2) { + printBytes(&spaces[1], sizeof(spaces)-2); + nspaces -= sizeof(spaces)-2; + } + printBytes(&spaces[1], nspaces); +} diff --git a/src/DBusParse/dbus_random.cpp b/src/DBusParse/dbus_random.cpp new file mode 100644 index 0000000..0223a1d --- /dev/null +++ b/src/DBusParse/dbus_random.cpp @@ -0,0 +1,286 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#include +#include +#include "dbus_random.hpp" + +DBusRandomMersenne::DBusRandomMersenne( + std::uint_fast64_t seed, size_t maxsize +) : + gen_(seed), + maxsize_(maxsize) +{} + +char DBusRandomMersenne::randomType(const size_t maxdepth) { + static const char types[] = { + 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 'h', + 's', 'o', 'g', 'v', 'a', '(', '{' + }; + if (maxdepth == 0) { + // Skip the 4 non-trivial types (variant, array, struct, dict). + std::uniform_int_distribution<> dis(0, sizeof(types) - 5); + return types[dis(gen_)]; + } else { + std::uniform_int_distribution<> dis(0, sizeof(types) - 1); + return types[dis(gen_)]; + } +} + +size_t DBusRandomMersenne::randomNumFields() { + size_t numfields = std::min(size_t(8), maxsize_); + maxsize_ -= numfields; + std::uniform_int_distribution<> dis(0, numfields); + return dis(gen_); +} + +size_t DBusRandomMersenne::randomArraySize() { + size_t numelements = std::min(size_t(8), maxsize_); + maxsize_ -= numelements; + std::uniform_int_distribution<> dis(0, numelements); + return dis(gen_); +} + +char DBusRandomMersenne::randomChar() { + std::uniform_int_distribution<> dis( + std::numeric_limits::min(), std::numeric_limits::max() + ); + return dis(gen_); +} + +bool DBusRandomMersenne::randomBoolean() { + std::uniform_int_distribution<> dis(0, 1); + return dis(gen_); +} + +uint16_t DBusRandomMersenne::randomUint16() { + std::uniform_int_distribution<> dis( + std::numeric_limits::min(), std::numeric_limits::max() + ); + return dis(gen_); +} + +uint32_t DBusRandomMersenne::randomUint32() { + std::uniform_int_distribution<> dis( + std::numeric_limits::min(), std::numeric_limits::max() + ); + return dis(gen_); +} + +uint64_t DBusRandomMersenne::randomUint64() { + return gen_(); +} + +double DBusRandomMersenne::randomDouble() { + std::uniform_int_distribution<> switchdis(0, 11); + switch (switchdis(gen_)) { + case 0: return 0.0; + case 1: return 1.0; + case 2: return 2.0; + case 3: return 1.0/0.0; + case 4: return 0.0/0.0; + case 5: return -randomDouble(); + case 6: return randomDouble() * randomDouble(); + case 7: return randomDouble() / randomDouble(); + default: return static_cast(randomUint64()); + } +} + +std::string DBusRandomMersenne::randomString() { + std::uniform_int_distribution<> lendis(0, 32); + size_t len = lendis(gen_); + std::string result; + result.reserve(len); + for (size_t i = 0; i < len; i++) { + std::uniform_int_distribution<> chardis(1, 127); + result.push_back(chardis(gen_)); + } + return result; +} + +std::string DBusRandomMersenne::randomPath() { + // TODO: generate a valid path rather than just an arbitrary string. + return randomString(); +} + +static const DBusTypeStruct& randomStructType( + DBusRandom& r, + DBusTypeStorage& typeStorage, // Type allocator + const size_t maxdepth +) { + std::vector> fieldTypes; + const size_t numFields = r.randomNumFields(); + fieldTypes.reserve(numFields); + for (size_t i = 0; i < numFields; i++) { + fieldTypes.push_back(randomType(r, typeStorage, maxdepth)); + } + return typeStorage.allocStruct(std::move(fieldTypes)); +} + +const DBusType& randomType( + DBusRandom& r, + DBusTypeStorage& typeStorage, // Type allocator + const size_t maxdepth +) { + switch (r.randomType(maxdepth)) { + case 'y': return DBusTypeChar::instance_; + case 'b': return DBusTypeBoolean::instance_; + case 'q': return DBusTypeUint16::instance_; + case 'n': return DBusTypeInt16::instance_; + case 'u': return DBusTypeUint32::instance_; + case 'i': return DBusTypeInt32::instance_; + case 't': return DBusTypeUint64::instance_; + case 'x': return DBusTypeInt64::instance_; + case 'd': return DBusTypeDouble::instance_; + case 'h': return DBusTypeUnixFD::instance_; + case 's': return DBusTypeString::instance_; + case 'o': return DBusTypePath::instance_; + case 'g': return DBusTypeSignature::instance_; + case 'v': return DBusTypeVariant::instance_; + case 'a': + assert(maxdepth > 0); // Invariant of `DBusRandom::randomType()`. + return typeStorage.allocArray(randomType(r, typeStorage, maxdepth-1)); + case '(': + assert(maxdepth > 0); // Invariant of `DBusRandom::randomType()`. + return randomStructType(r, typeStorage, maxdepth-1); + case '{': + assert(maxdepth > 0); // Invariant of `DBusRandom::randomType()`. + return typeStorage.allocDictEntry( + randomType(r, typeStorage, 0), // key is required to be a basic type + randomType(r, typeStorage, maxdepth-1) + ); + default: + assert(false); // Invariant of `DBusRandom::randomType()`. + throw Error("Bad type in randomType."); + } +} + +std::unique_ptr randomObject( + DBusRandom& r, const DBusType& t, const size_t maxdepth +) { + class Visitor : public DBusType::Visitor { + DBusRandom& r_; + const size_t maxdepth_; + std::unique_ptr result_; + + public: + Visitor(DBusRandom& r, const size_t maxdepth) : + r_(r), maxdepth_(maxdepth) + {} + + virtual void visitChar(const DBusTypeChar&) { + result_ = DBusObjectChar::mk(r_.randomChar()); + } + + virtual void visitBoolean(const DBusTypeBoolean&) { + result_ = DBusObjectBoolean::mk(r_.randomBoolean()); + } + + virtual void visitUint16(const DBusTypeUint16&) { + result_ = DBusObjectUint16::mk(r_.randomUint16()); + } + + virtual void visitInt16(const DBusTypeInt16&) { + result_ = DBusObjectInt16::mk(static_cast(r_.randomUint16())); + } + + virtual void visitUint32(const DBusTypeUint32&) { + result_ = DBusObjectUint32::mk(r_.randomUint32()); + } + + virtual void visitInt32(const DBusTypeInt32&) { + result_ = DBusObjectInt32::mk(static_cast(r_.randomUint32())); + } + + virtual void visitUint64(const DBusTypeUint64&) { + result_ = DBusObjectUint64::mk(r_.randomUint64()); + } + + virtual void visitInt64(const DBusTypeInt64&) { + result_ = DBusObjectInt64::mk(static_cast(r_.randomUint64())); + } + + virtual void visitDouble(const DBusTypeDouble&) { + result_ = DBusObjectDouble::mk(r_.randomDouble()); + } + + virtual void visitUnixFD(const DBusTypeUnixFD&) { + result_ = DBusObjectUnixFD::mk(r_.randomUint32()); + } + + virtual void visitString(const DBusTypeString&) { + result_ = DBusObjectString::mk(r_.randomString()); + } + + virtual void visitPath(const DBusTypePath&) { + result_ = DBusObjectPath::mk(r_.randomPath()); + } + + virtual void visitSignature(const DBusTypeSignature&) { + DBusTypeStorage typeStorage; + const DBusType& t = randomType(r_, typeStorage, maxdepth_); + result_ = DBusObjectSignature::mk(t.toString()); + } + + virtual void visitVariant(const DBusTypeVariant&) { + size_t newdepth = maxdepth_ > 0 ? maxdepth_ - 1 : 0; + DBusTypeStorage typeStorage; + const DBusType& t = randomType(r_, typeStorage, newdepth); + result_ = DBusObjectVariant::mk(randomObject(r_, t, newdepth)); + } + + virtual void visitDictEntry(const DBusTypeDictEntry& dictType) { + size_t newdepth = maxdepth_ > 0 ? maxdepth_ - 1 : 0; + result_ = DBusObjectDictEntry::mk( + randomObject(r_, dictType.getKeyType(), 0), + randomObject(r_, dictType.getValueType(), newdepth) + ); + } + + virtual void visitArray(const DBusTypeArray& arrayType) { + size_t newdepth = maxdepth_ > 0 ? maxdepth_ - 1 : 0; + const DBusType& baseType = arrayType.getBaseType(); + const size_t n = r_.randomArraySize(); + std::vector> elements; + elements.reserve(n); + for (size_t i = 0; i < n; i++) { + elements.push_back(randomObject(r_, baseType, newdepth)); + } + result_ = DBusObjectArray::mk(baseType, std::move(elements)); + } + + virtual void visitStruct(const DBusTypeStruct& structType) { + size_t newdepth = maxdepth_ > 0 ? maxdepth_ - 1 : 0; + const std::vector>& fieldTypes = + structType.getFieldTypes(); + const size_t n = fieldTypes.size(); + std::vector> elements; + elements.reserve(n); + for (size_t i = 0; i < n; i++) { + elements.push_back(randomObject(r_, fieldTypes[i], newdepth)); + } + result_ = DBusObjectStruct::mk(std::move(elements)); + } + + std::unique_ptr getResult() { return std::move(result_); } + }; + + Visitor v(r, maxdepth); + t.accept(v); + return v.getResult(); +} diff --git a/src/DBusParse/dbus_serialize.cpp b/src/DBusParse/dbus_serialize.cpp new file mode 100644 index 0000000..e91d096 --- /dev/null +++ b/src/DBusParse/dbus_serialize.cpp @@ -0,0 +1,121 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#include +#include +#include "dbus_serialize.hpp" + +void SerializerDryRunBase::insertPadding(size_t alignment) { + pos_ = alignup(pos_, alignment); +} + +void SerializerDryRun::recordArraySize( + const std::function& f +) { + (void)f(0xDEADBEEF); + ++arrayCount_; +} + +void SerializerInitArraySizes::recordArraySize( + const std::function& f +) { + const size_t i = arraySizes_.size(); + // Create a slot in the array. When `f` returns, it will give us the + // value which we need to write into the slot. + arraySizes_.push_back(0xDEADBEEF); + // Run `f` and write the value that it returns into the slot + // that we just created. + arraySizes_[i] = f(0xDEADBEEF); +} + +void SerializeToBufferBase::recordArraySize( + const std::function& f +) { + (void)f(arraySizes_.at(arrayCount_++)); +} + + +std::string DBusType::toString() const { + SerializerDryRun s0; + serialize(s0); + size_t size = s0.getPos(); + + // arraySizes is not used, but it's required as an argument to + // SerializeToString. + std::vector arraySizes; + + std::string result; + result.reserve(size); + // Note: types only contain ASCII characters so the endianness of the + // serializer doesn't matter. So the choice of LittleEndian here is + // arbitrary. + SerializeToString s1(arraySizes, result); + serialize(s1); + return result; +} + +static void mkSignatureHelper(const DBusObjectSeq& seq, Serializer& s) { + const size_t n = seq.length(); + for (size_t i = 0; i < n; i++) { + seq.getElement(i)->getType().serialize(s); + } +} + +std::string DBusMessageBody::signature() const { + SerializerDryRun s0; + mkSignatureHelper(seq_, s0); + size_t size = s0.getPos(); + + // arraySizes is not used, but it's required as an argument to + // SerializeToString. + std::vector arraySizes; + + std::string result; + result.reserve(size); + // Note: types only contain ASCII characters so the endianness of the + // serializer doesn't matter. So the choice of LittleEndian here is + // arbitrary. + SerializeToString s1(arraySizes, result); + mkSignatureHelper(seq_, s1); + return result; +} + +void DBusMessageBody::serialize(Serializer& s) const { + seq_.serialize(s); +} + +size_t DBusMessageBody::serializedSize() const { + SerializerDryRun s; + serialize(s); + return s.getPos(); +} + +size_t DBusObject::serializedSize() const { + SerializerDryRun s; + serialize(s); + return s.getPos(); +} + +void DBusMessage::serialize(Serializer& s) const { + header_->serialize(s); + if (body_) { + // The body should be 8-byte aligned. + s.insertPadding(DBusTypeUint64::instance_.alignment()); + body_->serialize(s); + } +} diff --git a/src/DBusParse/dbus_utils.cpp b/src/DBusParse/dbus_utils.cpp new file mode 100644 index 0000000..7c43baf --- /dev/null +++ b/src/DBusParse/dbus_utils.cpp @@ -0,0 +1,373 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#include +#include +#include "dbus_serialize.hpp" +#include "dbus_print.hpp" +#include "utils.hpp" + +void send_dbus_message_with_fds( + const int fd, const DBusMessage& message, + const size_t nfds, const int* fds +) { + std::vector arraySizes; + SerializerInitArraySizes s0(arraySizes); + message.serialize(s0); + + const size_t size = s0.getPos(); + std::unique_ptr buf(new char[size]); + SerializeToBuffer s1(arraySizes, buf.get()); + message.serialize(s1); + + struct msghdr msg = {}; // Zero initialize. + struct iovec io = {}; + io.iov_base = buf.get(); + io.iov_len = size; + + const size_t fds_size = nfds * sizeof(int); + msg.msg_iov = &io; + msg.msg_iovlen = 1; + msg.msg_controllen = CMSG_SPACE(fds_size); + msg.msg_control = malloc(msg.msg_controllen); + + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(fds_size); + memcpy(CMSG_DATA(cmsg), &fds[0], fds_size); + + const ssize_t wr = sendmsg(fd, &msg, 0); + if (wr < 0) { + const int err = errno; + fprintf(stderr, "sendmsg failed: %s\n", strerror(err)); + } else if (static_cast(wr) != size) { + fprintf(stderr, "sendmsg incomplete: %ld < %lu\n", wr, size); + } + + free(msg.msg_control); +} + +void send_dbus_message(const int fd, const DBusMessage& message) { + std::vector arraySizes; + SerializerInitArraySizes s0(arraySizes); + message.serialize(s0); + + const size_t size = s0.getPos(); + std::unique_ptr buf(new char[size]); + SerializeToBuffer s1(arraySizes, buf.get()); + message.serialize(s1); + + const ssize_t wr = write(fd, buf.get(), size); + if (wr < 0) { + const int err = errno; + fprintf(stderr, "write failed: %s\n", strerror(err)); + } else if (static_cast(wr) != size) { + fprintf(stderr, "write incomplete: %ld < %lu\n", wr, size); + } +} + +// Note: this is a very simplistic implementation. It expects to loop until +// it has read the entire message. It is only designed to be used with a +// blocking socket. +std::unique_ptr receive_dbus_message(const int fd) { + std::unique_ptr message; + Parse p(DBusMessage::parseLE(message)); + + while (true) { + char buf[256]; + size_t required = p.maxRequiredBytes(); + if (required == 0) { + return message; + } + if (required > sizeof(buf)) { + required = sizeof(buf); + } + const ssize_t n = read(fd, buf, required); + if (n < 0 || size_t(n) < required) { + // Note: this error could happen by accident if `fd` is a + // non-blocking socket, because we might get a partial read. (See + // comment at top of function.) + throw ParseError(p.getPos(), _s("No more input. n=") + std::to_string(n)); + } + p.parse(buf, required); + } +} + +void print_dbus_object(const int fd, const DBusObject& obj) { + PrinterFD printFD(fd, 16, 2); + obj.print(printFD); + printFD.printNewline(0); +} + +void print_dbus_message(const int fd, const DBusMessage& message) { + PrinterFD printFD(fd, 16, 2); + message.print(printFD, 0); + printFD.printNewline(0); +} + +void dbus_method_call_with_fds( + const int fd, + const uint32_t serialNumber, + std::unique_ptr&& body, + std::string&& path, + std::string&& interface, + std::string&& destination, + std::string&& member, + const size_t nfds, + const int* fds +) { + const size_t bodySize = body->serializedSize(); + + std::unique_ptr header = + DBusObjectStruct::mk( + _vec>( + // Header + DBusObjectChar::mk('l'), // Little endian + DBusObjectChar::mk(MSGTYPE_METHOD_CALL), + DBusObjectChar::mk(MSGFLAGS_EMPTY), + DBusObjectChar::mk(1), // Major protocol version + DBusObjectUint32::mk(bodySize), // body_len_unsigned + DBusObjectUint32::mk(serialNumber), // serial number + DBusObjectArray::mk1( + _vec>( + DBusHeaderField::mk( + MSGHDR_PATH, + DBusObjectVariant::mk( + DBusObjectPath::mk(std::move(path)) + ) + ), + DBusHeaderField::mk( + MSGHDR_INTERFACE, + DBusObjectVariant::mk( + DBusObjectString::mk(std::move(interface)) + ) + ), + DBusHeaderField::mk( + MSGHDR_DESTINATION, + DBusObjectVariant::mk( + DBusObjectString::mk(std::move(destination)) + ) + ), + DBusHeaderField::mk( + MSGHDR_MEMBER, + DBusObjectVariant::mk( + DBusObjectString::mk(std::move(member)) + ) + ), + DBusHeaderField::mk( + MSGHDR_SIGNATURE, + DBusObjectVariant::mk( + DBusObjectSignature::mk(body->signature()) + ) + ), + DBusHeaderField::mk( + MSGHDR_UNIX_FDS, + DBusObjectVariant::mk( + DBusObjectUint32::mk(nfds) + ) + ) + ) + ) + ) + ); + + const DBusMessage message(std::move(header), std::move(body)); + + send_dbus_message_with_fds(fd, message, nfds, fds); +} + +void dbus_method_call( + const int fd, + const uint32_t serialNumber, + std::unique_ptr&& body, + std::string&& path, + std::string&& interface, + std::string&& destination, + std::string&& member +) { + const size_t bodySize = body->serializedSize(); + + std::unique_ptr header = + DBusObjectStruct::mk( + _vec>( + // Header + DBusObjectChar::mk('l'), // Little endian + DBusObjectChar::mk(MSGTYPE_METHOD_CALL), + DBusObjectChar::mk(MSGFLAGS_EMPTY), + DBusObjectChar::mk(1), // Major protocol version + DBusObjectUint32::mk(bodySize), // body_len_unsigned + DBusObjectUint32::mk(serialNumber), // serial number + DBusObjectArray::mk1( + _vec>( + DBusHeaderField::mk( + MSGHDR_PATH, + DBusObjectVariant::mk( + DBusObjectPath::mk(std::move(path)) + ) + ), + DBusHeaderField::mk( + MSGHDR_INTERFACE, + DBusObjectVariant::mk( + DBusObjectString::mk(std::move(interface)) + ) + ), + DBusHeaderField::mk( + MSGHDR_DESTINATION, + DBusObjectVariant::mk( + DBusObjectString::mk(std::move(destination)) + ) + ), + DBusHeaderField::mk( + MSGHDR_MEMBER, + DBusObjectVariant::mk( + DBusObjectString::mk(std::move(member)) + ) + ), + DBusHeaderField::mk( + MSGHDR_SIGNATURE, + DBusObjectVariant::mk( + DBusObjectSignature::mk(body->signature()) + ) + ) + ) + ) + ) + ); + + const DBusMessage message(std::move(header), std::move(body)); + + send_dbus_message(fd, message); +} + +void dbus_method_reply( + const int fd, + const uint32_t serialNumber, + const uint32_t replySerialNumber, // serial number that we are replying to + std::unique_ptr&& body, + std::string&& destination +) { + const size_t bodySize = body->serializedSize(); + + std::unique_ptr header = + DBusObjectStruct::mk( + _vec>( + // Header + DBusObjectChar::mk('l'), // Little endian + DBusObjectChar::mk(MSGTYPE_METHOD_RETURN), + DBusObjectChar::mk(MSGFLAGS_EMPTY), + DBusObjectChar::mk(1), // Major protocol version + DBusObjectUint32::mk(bodySize), // body_len_unsigned + DBusObjectUint32::mk(serialNumber), // serial number + DBusObjectArray::mk1( + _vec>( + DBusHeaderField::mk( + MSGHDR_DESTINATION, + DBusObjectVariant::mk( + DBusObjectString::mk(std::move(destination)) + ) + ), + DBusHeaderField::mk( + MSGHDR_SIGNATURE, + DBusObjectVariant::mk( + DBusObjectSignature::mk(body->signature()) + ) + ), + DBusHeaderField::mk( + MSGHDR_REPLY_SERIAL, + DBusObjectVariant::mk( + DBusObjectUint32::mk(replySerialNumber) + ) + ) + ) + ) + ) + ); + + const DBusMessage message(std::move(header), std::move(body)); + + send_dbus_message(fd, message); +} + +void dbus_send_hello(const int fd) { + dbus_method_call( + fd, + 0x1001, + DBusMessageBody::mk0(), + _s("/org/freedesktop/DBus"), + _s("org.freedesktop.DBus"), + _s("org.freedesktop.DBus"), + _s("Hello") + ); + +#if 0 + std::unique_ptr body = DBusMessageBody::mk0(); + const size_t bodySize = body->serializedSize(); + + std::unique_ptr header = + DBusObjectStruct::mk( + _vec>( + // Header + DBusObjectChar::mk('l'), // Little endian + DBusObjectChar::mk(MSGTYPE_METHOD_CALL), + DBusObjectChar::mk(MSGFLAGS_EMPTY), + DBusObjectChar::mk(1), // Major protocol version + DBusObjectUint32::mk(bodySize), // body_len_unsigned + DBusObjectUint32::mk(0x1001), // serial number + DBusObjectArray::mk1( + _vec>( + DBusHeaderField::mk( + MSGHDR_PATH, + DBusObjectVariant::mk( + DBusObjectPath::mk(_s("/org/freedesktop/DBus")) + ) + ), + DBusHeaderField::mk( + MSGHDR_INTERFACE, + DBusObjectVariant::mk( + DBusObjectString::mk(_s("org.freedesktop.DBus")) + ) + ), + DBusHeaderField::mk( + MSGHDR_DESTINATION, + DBusObjectVariant::mk( + DBusObjectString::mk(_s("org.freedesktop.DBus")) + ) + ), + DBusHeaderField::mk( + MSGHDR_MEMBER, + DBusObjectVariant::mk( + DBusObjectString::mk(_s("Hello")) + ) + ), + DBusHeaderField::mk( + MSGHDR_REPLY_SERIAL, + DBusObjectVariant::mk( + DBusObjectUint32::mk(0x2001) + ) + ) + ) + ) + ) + ); + + const DBusMessage message(std::move(header), std::move(body)); + + send_dbus_message(fd, message); +#endif +} diff --git a/src/DBusParseUtils/CMakeLists.txt b/src/DBusParseUtils/CMakeLists.txt new file mode 100644 index 0000000..89176a0 --- /dev/null +++ b/src/DBusParseUtils/CMakeLists.txt @@ -0,0 +1,45 @@ +# Copyright 2020 Kevin Backhouse. +# +# This file is part of DBusParse. +# +# DBusParse 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. +# +# DBusParse 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 DBusParse. If not, see . + + +add_library(DBusParseUtils SHARED) + +target_sources( + DBusParseUtils PRIVATE + ../../include/DBusParseUtils/endianness.hpp + ../../include/DBusParseUtils/error.hpp + parse.cpp + ../../include/DBusParseUtils/parse.hpp + utils.cpp + ../../include/DBusParseUtils/utils.hpp) + +target_include_directories(DBusParseUtils PUBLIC + $ +) + +write_basic_package_version_file(DBusParseUtilsConfigVersion.cmake COMPATIBILITY ExactVersion) + +install(TARGETS DBusParseUtils EXPORT DBusParseUtilsConfig + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install(EXPORT DBusParseUtilsConfig DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/DBusParseUtils") + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/DBusParseUtilsConfigVersion.cmake + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/DBusParseUtils") diff --git a/src/DBusParseUtils/parse.cpp b/src/DBusParseUtils/parse.cpp new file mode 100644 index 0000000..f0d31e3 --- /dev/null +++ b/src/DBusParseUtils/parse.cpp @@ -0,0 +1,124 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#include "parse.hpp" +#include + +static_assert( + !std::is_polymorphic::value, + "Parse::State does not have any virtual methods" +); + +static_assert( + !std::is_polymorphic::value, + "Parse does not have any virtual methods" +); + +const Parse::State Parse::State::initialState_(0); + +void Parse::parse(const char* buf, size_t bufsize) { + assert(minRequiredBytes() <= bufsize); + assert(bufsize <= maxRequiredBytes()); + if (__builtin_add_overflow(bufsize, state_.pos_, &state_.pos_)) { + throw ParseError(state_.pos_, "Integer overflow in Parse::parse"); + } + cont_ = cont_->parse(state_, buf, bufsize); +} + +uint8_t Parse::minRequiredBytes() const { + return cont_->minRequiredBytes(); +} + +size_t Parse::maxRequiredBytes() const { + return cont_->maxRequiredBytes(); +} + +std::unique_ptr ParseStop::parse( + const Parse::State&, const char*, size_t +) { + // Parsing is finished, so this function is never called. + assert(false); + return ParseStop::mk(); +} + +std::unique_ptr ParseStop::mk() { + return std::make_unique(); +} + +std::unique_ptr ParseChar::parse( + const Parse::State& p, const char* buf, size_t bufsize +) { + (void)bufsize; + assert(bufsize == sizeof(char)); + return cont_->parse(p, buf[0]); +} + +std::unique_ptr ParseChar::mk(std::unique_ptr&& cont) { + return std::make_unique(std::move(cont)); +} + +std::unique_ptr ParseNChars::parse( + const Parse::State& p, const char* buf, size_t bufsize +) { + (void)bufsize; + assert(bufsize <= n_); + str_.append(buf, bufsize); + + // Parse remaining bytes. + return mk(p, std::move(str_), n_ - bufsize, std::move(cont_)); +} + +std::unique_ptr ParseNChars::mk( + const Parse::State& p, std::string&& str, size_t n, + std::unique_ptr&& cont +) { + if (n == 0) { + // There's nothing to parse, so invoke the next continuation immediately. + return cont->parse(p, std::move(str)); + } + + return std::make_unique(std::move(str), n, std::move(cont)); +} + +std::unique_ptr ParseZeros::parse( + const Parse::State& p, const char* buf, size_t bufsize +) { + assert(bufsize <= n_); + + // Check that the bytes are zero. + for (size_t i = 0; i < bufsize; i++) { + if (buf[i] != '\0') { + throw ParseError(p.getPos() + i, "Unexpected non-zero byte."); + } + } + + // Parse remaining bytes. + return mk(p, n_ - bufsize, std::move(cont_)); +} + +std::unique_ptr ParseZeros::mk( + const Parse::State& p, size_t n, std::unique_ptr&& cont +) { + if (n == 0) { + // There's nothing to parse, so invoke the next continuation + // immediately. + return cont->parse(p); + } + + return std::make_unique(n, std::move(cont)); +} diff --git a/src/DBusParseUtils/utils.cpp b/src/DBusParseUtils/utils.cpp new file mode 100644 index 0000000..95401f8 --- /dev/null +++ b/src/DBusParseUtils/utils.cpp @@ -0,0 +1,73 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#include +#include +#include +#include +#include +#include +#include "utils.hpp" + +AutoCloseFD::~AutoCloseFD() { + close(fd_); +} + +// Get the process's start time by reading /proc/[pid]/stat. +uint64_t process_start_time(pid_t pid) { + char filename[64]; + char buf[1024]; + snprintf(filename, sizeof(filename), "/proc/%d/stat", pid); + AutoCloseFD fd(open(filename, O_RDONLY | O_CLOEXEC, 0)); + const ssize_t n = read(fd.get(), buf, sizeof(buf)-1); + if (n < 0) { + const int err = errno; + fprintf(stderr, "could not open %s: %s\n", filename, strerror(err)); + return ~size_t(0); + } + buf[n] = '\0'; + + // Search backwards for ')' character. + ssize_t i = n; + do { + if (i == 0) { + fprintf(stderr, "could not find ')' character in %s.\n", filename); + return ~size_t(0); + } + --i; + } while (buf[i] != ')'); + + ++i; + size_t j = 0; + while (true) { + if (i >= n || buf[i] != ' ') { + fprintf(stderr, "unexpected character in %s.\n", filename); + return ~size_t(0); + } + ++i; + ++j; + if (j == 20) { + break; + } + while (i < n && buf[i] != ' ') { + ++i; + } + } + + return strtoull(&buf[i], 0, 10); +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..a143040 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright 2020 Kevin Backhouse. +# +# This file is part of DBusParse. +# +# DBusParse 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. +# +# DBusParse 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 DBusParse. If not, see . + + +add_subdirectory(DBusParseUnitTests) diff --git a/tests/DBusParseUnitTests/CMakeLists.txt b/tests/DBusParseUnitTests/CMakeLists.txt new file mode 100644 index 0000000..0169abe --- /dev/null +++ b/tests/DBusParseUnitTests/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright 2020 Kevin Backhouse. +# +# This file is part of DBusParse. +# +# DBusParse 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. +# +# DBusParse 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 DBusParse. If not, see . + + +add_executable(DBusParseUnitTests dbus_unit_tests.cpp) + +target_link_libraries(DBusParseUnitTests PUBLIC DBusParse DBusParseUtils) +target_include_directories( + DBusParseUnitTests PRIVATE + $ +) + +add_test(DBusParseUnitTests DBusParseUnitTests) diff --git a/tests/DBusParseUnitTests/dbus_unit_tests.cpp b/tests/DBusParseUnitTests/dbus_unit_tests.cpp new file mode 100644 index 0000000..3b2db4b --- /dev/null +++ b/tests/DBusParseUnitTests/dbus_unit_tests.cpp @@ -0,0 +1,152 @@ +// Copyright 2020 Kevin Backhouse. +// +// This file is part of DBusParse. +// +// DBusParse 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. +// +// DBusParse 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 DBusParse. If not, see . + + +#include +#include +#include "endianness.hpp" +#include "dbus.hpp" +#include "dbus_print.hpp" +#include "dbus_random.hpp" +#include "dbus_serialize.hpp" + +template +std::unique_ptr dbus_object_to_buffer( + const DBusObject& object, size_t& size +) { + std::vector arraySizes; + SerializerInitArraySizes s0(arraySizes); + object.serialize(s0); + size = s0.getPos(); + + std::unique_ptr result(new char[size]); + SerializeToBuffer s1(arraySizes, result.get()); + object.serialize(s1); + + return result; +} + +template +std::unique_ptr parse_dbus_object_from_buffer( + const DBusType& t, + const char* buf, + const size_t buflen +) { + class Cont final : public DBusType::ParseObjectCont { + std::unique_ptr& result_; + + public: + explicit Cont(std::unique_ptr& result) : + result_(result) + {} + + virtual std::unique_ptr parse( + const Parse::State&, std::unique_ptr&& object + ) override { + result_ = std::move(object); + return ParseStop::mk(); + } + }; + + std::unique_ptr result; + Parse p( + t.mkObjectParser( + Parse::State::initialState_, + std::make_unique(result) + ) + ); + + while (true) { + const size_t required = p.maxRequiredBytes(); + const size_t pos = p.getPos(); + if (required == 0) { + assert(pos == buflen); + return result; + } + if (required > buflen - pos) { + throw ParseError( + pos, "parse_dbus_object_from_buffer: not enough bytes" + ); + } + p.parse(buf + pos, required); + } + + return result; +} + +#define DEBUGPRINT 0 + +// This function checks the serializer and parser for consistency. +// It does that by running the following steps: +// +// 1. Serialize `object` to a buffer named `buf0`. +// 2. Parse `buf0`. The new object is called `parsedObject`. +// 3. Serialize `parsedObject` to a buffer named `buf1`. +// 4. Check that `buf0` and `buf1` are identical. +// +// It would also be valuable to compare `object` and `parsedObject` for +// equality, but `DBusObject` doesn't currently have an `operator==`. +template +void check_serialize_and_parse( + const DBusType& t, const DBusObject& object +) { + if (DEBUGPRINT) { + PrinterFD printFD(STDOUT_FILENO, 16, 2); + t.print(printFD); + printFD.printNewline(0); + object.print(printFD); + printFD.printNewline(0); + } + + size_t size0 = 0; + std::unique_ptr buf0 = + dbus_object_to_buffer(object, size0); + + std::unique_ptr parsedObject = + parse_dbus_object_from_buffer(t, buf0.get(), size0); + + if (DEBUGPRINT) { + PrinterFD printFD(STDOUT_FILENO, 16, 2); + parsedObject->print(printFD); + printFD.printNewline(0); + } + + size_t size1 = 0; + std::unique_ptr buf1 = + dbus_object_to_buffer(*parsedObject, size1); + + // Check that the two serialized buffers are identical. + if (size0 != size1) { + throw Error("Serialized string sizes don't match."); + } + if (memcmp(buf0.get(), buf1.get(), size0) != 0) { + throw Error("Serialized strings don't match."); + } +} + +int main() { + for (size_t i = 0; i < 100000; i++) { + DBusRandomMersenne r(i, 1000); + DBusTypeStorage typeStorage; + const size_t maxdepth = 20; + const DBusType& t = randomType(r, typeStorage, maxdepth); + std::unique_ptr object = randomObject(r, t, maxdepth); + check_serialize_and_parse(t, *object); + check_serialize_and_parse(t, *object); + } + return 0; +}