[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non: [push, pull_request]\n\njobs:\n  build:\n    name: Build MiniDNS\n\n    runs-on: ubuntu-24.04\n    strategy:\n      matrix:\n        java:\n          - 17\n          - 21\n    env:\n      PRIMARY_JAVA_VERSION: 21\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n      - name: Set up JDK ${{ matrix.java }}\n        uses: actions/setup-java@v1\n        with:\n          java-version: ${{ matrix.java }}\n\n      # Caches\n      - name: Cache Maven\n        uses: actions/cache@v2\n        with:\n          path: ~/.m2/repository\n          key: maven-${{ hashFiles('**/build.gradle') }}\n          restore-keys: |\n            maven-\n      - name: Cache Gradle\n        uses: actions/cache@v2\n        with:\n          path: ~/.gradle/caches\n          key: gradle-caches-${{ hashFiles('**/build.gradle') }}\n          restore-keys:\n            gradle-caches\n      - name: Cache Gradle Binary\n        uses: actions/cache@v2\n        with:\n          path: |\n            ~/gradle-bin-${GRADLE_VERSION}/\n          key: gradle-bin-${GRADLE_VERSION}\n      - name: Cache Android SDK\n        uses: actions/cache@v2\n        with:\n          path: |\n            ~/.android/sdk\n          key: android-${{ hashFiles('build.gradle') }}\n          restore-keys: |\n            android-\n\n      # Pre-reqs\n      - name: Install Android SDK Manager\n        uses: android-actions/setup-android@v2\n      - name: Install Android SDK\n        run: |\n          sdkmanager \\\n          \t\"platforms;android-19\" \\\n            \"platforms;android-23\"\n\n      # Testing\n      - name: Gradle Check\n        run: ./gradlew check --stacktrace\n\n      # Test local publish\n      - name: Gradle publish\n        run: ./gradlew publishToMavenLocal --stacktrace\n\n      # Javadoc\n      - name: Javadoc\n        if: ${{ matrix.java == env.PRIMARY_JAVA_VERSION }}\n        run: ./gradlew javadocAll --stacktrace\n\n      # Test Coverage Report\n      - name: Jacoco Test Coverage\n        run: ./gradlew minidns-hla:testCodeCoverageReport\n\n      # Coveralls\n      - name: Report coverage stats to Coveralls\n        if: ${{ matrix.java == env.PRIMARY_JAVA_VERSION }}\n        uses: coverallsapp/github-action@v2\n        with:\n          format: jacoco\n          file: minidns-hla/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml\n\n      # Upload build artifacts\n      - name: Upload build artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: smack-java-${{ matrix.java }}\n          path: |\n            minidns-*/build/libs/*.jar\n            !**/*-test-fixtures.jar\n            !**/*-tests.jar\n"
  },
  {
    "path": ".gitignore",
    "content": "# From https://github.com/github/gitignore\n\n# # # # # # # # # # # #\n# Android  gitignore  #\n# # # # # # # # # # # #\n\n# Built application files\n*.apk\n*.ap_\n\n# Files for the Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\n\n# Gradle files\n.gradle/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\ngradle.properties\n\n# Proguard folder generated by Eclipse\nproguard/\n\n# # # # # # # #\n# VIM / Linux #\n# # # # # # # #\n\n[._]*.s[a-w][a-z]\n[._]s[a-w][a-z]\n*.un~\nSession.vim\n.netrwhist\n*~\n.directory\n\n# # # # # #\n# Eclipse #\n# # # # # #\n\n*.pydevproject\n.metadata\n.gradle\nbin/\ntmp/\n*.tmp\n*.bak\n*.swp\n*~.nib\nlocal.properties\n.settings/\n.loadpath\n.classpath\n.project\n\n# External tool builders\n.externalToolBuilders/\n\n# Locally stored \"Eclipse launch configurations\"\n*.launch\n\n# CDT-specific\n.cproject\n\n# PDT-specific\n.buildpath\n\n# sbteclipse plugin\n.target\n\n# TeXlipse plugin\n.texlipse\n\n# # # # # # # # #\n# IntelliJ IDEA #\n# # # # # # # # #\n\n.idea/\n*.iml\n\n# # # # #\n# OS X  #\n# # # # #\n\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must ends with two \\r.\nIcon\n\n\n# Thumbnails\n._*\n\n# Files that might appear on external disk\n.Spotlight-V100\n.Trashes\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n# Auto generated version file\n/minidns-core/src/main/resources/org.minidns/version\n"
  },
  {
    "path": "LICENCE",
    "content": "This software may be used under the terms of (at your choice)\n- LGPL version 2 (or later) (see LICENCE_LGPL2.1 for details)\n- Apache Software licence (see LICENCE_APACHE for details)\n- WTFPL (see LICENCE_WTFPL for details)\n"
  },
  {
    "path": "LICENCE_APACHE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n"
  },
  {
    "path": "LICENCE_LGPL2.1",
    "content": "                  GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 2.1, February 1999\n\n Copyright (C) 1991, 1999 Free Software Foundation, Inc.\n 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n[This is the first released version of the Lesser GPL.  It also counts\n as the successor of the GNU Library Public License, version 2, hence\n the version number 2.1.]\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicenses are intended to guarantee your freedom to share and change\nfree software--to make sure the software is free for all its users.\n\n  This license, the Lesser General Public License, applies to some\nspecially designated software packages--typically libraries--of the\nFree Software Foundation and other authors who decide to use it.  You\ncan use it too, but we suggest you first think carefully about whether\nthis license or the ordinary General Public License is the better\nstrategy to use in any particular case, based on the explanations below.\n\n  When we speak of free software, we are referring to freedom of use,\nnot price.  Our General Public Licenses are designed to make sure that\nyou have the freedom to distribute copies of free software (and charge\nfor this service if you wish); that you receive source code or can get\nit if you want it; that you can change the software and use pieces of\nit in new free programs; and that you are informed that you can do\nthese things.\n\n  To protect your rights, we need to make restrictions that forbid\ndistributors to deny you these rights or to ask you to surrender these\nrights.  These restrictions translate to certain responsibilities for\nyou if you distribute copies of the library or if you modify it.\n\n  For example, if you distribute copies of the library, whether gratis\nor for a fee, you must give the recipients all the rights that we gave\nyou.  You must make sure that they, too, receive or can get the source\ncode.  If you link other code with the library, you must provide\ncomplete object files to the recipients, so that they can relink them\nwith the library after making changes to the library and recompiling\nit.  And you must show them these terms so they know their rights.\n\n  We protect your rights with a two-step method: (1) we copyright the\nlibrary, and (2) we offer you this license, which gives you legal\npermission to copy, distribute and/or modify the library.\n\n  To protect each distributor, we want to make it very clear that\nthere is no warranty for the free library.  Also, if the library is\nmodified by someone else and passed on, the recipients should know\nthat what they have is not the original version, so that the original\nauthor's reputation will not be affected by problems that might be\nintroduced by others.\n\n  Finally, software patents pose a constant threat to the existence of\nany free program.  We wish to make sure that a company cannot\neffectively restrict the users of a free program by obtaining a\nrestrictive license from a patent holder.  Therefore, we insist that\nany patent license obtained for a version of the library must be\nconsistent with the full freedom of use specified in this license.\n\n  Most GNU software, including some libraries, is covered by the\nordinary GNU General Public License.  This license, the GNU Lesser\nGeneral Public License, applies to certain designated libraries, and\nis quite different from the ordinary General Public License.  We use\nthis license for certain libraries in order to permit linking those\nlibraries into non-free programs.\n\n  When a program is linked with a library, whether statically or using\na shared library, the combination of the two is legally speaking a\ncombined work, a derivative of the original library.  The ordinary\nGeneral Public License therefore permits such linking only if the\nentire combination fits its criteria of freedom.  The Lesser General\nPublic License permits more lax criteria for linking other code with\nthe library.\n\n  We call this license the \"Lesser\" General Public License because it\ndoes Less to protect the user's freedom than the ordinary General\nPublic License.  It also provides other free software developers Less\nof an advantage over competing non-free programs.  These disadvantages\nare the reason we use the ordinary General Public License for many\nlibraries.  However, the Lesser license provides advantages in certain\nspecial circumstances.\n\n  For example, on rare occasions, there may be a special need to\nencourage the widest possible use of a certain library, so that it becomes\na de-facto standard.  To achieve this, non-free programs must be\nallowed to use the library.  A more frequent case is that a free\nlibrary does the same job as widely used non-free libraries.  In this\ncase, there is little to gain by limiting the free library to free\nsoftware only, so we use the Lesser General Public License.\n\n  In other cases, permission to use a particular library in non-free\nprograms enables a greater number of people to use a large body of\nfree software.  For example, permission to use the GNU C Library in\nnon-free programs enables many more people to use the whole GNU\noperating system, as well as its variant, the GNU/Linux operating\nsystem.\n\n  Although the Lesser General Public License is Less protective of the\nusers' freedom, it does ensure that the user of a program that is\nlinked with the Library has the freedom and the wherewithal to run\nthat program using a modified version of the Library.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.  Pay close attention to the difference between a\n\"work based on the library\" and a \"work that uses the library\".  The\nformer contains code derived from the library, whereas the latter must\nbe combined with the library in order to run.\n\n                  GNU LESSER GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License Agreement applies to any software library or other\nprogram which contains a notice placed by the copyright holder or\nother authorized party saying it may be distributed under the terms of\nthis Lesser General Public License (also called \"this License\").\nEach licensee is addressed as \"you\".\n\n  A \"library\" means a collection of software functions and/or data\nprepared so as to be conveniently linked with application programs\n(which use some of those functions and data) to form executables.\n\n  The \"Library\", below, refers to any such software library or work\nwhich has been distributed under these terms.  A \"work based on the\nLibrary\" means either the Library or any derivative work under\ncopyright law: that is to say, a work containing the Library or a\nportion of it, either verbatim or with modifications and/or translated\nstraightforwardly into another language.  (Hereinafter, translation is\nincluded without limitation in the term \"modification\".)\n\n  \"Source code\" for a work means the preferred form of the work for\nmaking modifications to it.  For a library, complete source code means\nall the source code for all modules it contains, plus any associated\ninterface definition files, plus the scripts used to control compilation\nand installation of the library.\n\n  Activities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning a program using the Library is not restricted, and output from\nsuch a program is covered only if its contents constitute a work based\non the Library (independent of the use of the Library in a tool for\nwriting it).  Whether that is true depends on what the Library does\nand what the program that uses the Library does.\n\n  1. You may copy and distribute verbatim copies of the Library's\ncomplete source code as you receive it, in any medium, provided that\nyou conspicuously and appropriately publish on each copy an\nappropriate copyright notice and disclaimer of warranty; keep intact\nall the notices that refer to this License and to the absence of any\nwarranty; and distribute a copy of this License along with the\nLibrary.\n\n  You may charge a fee for the physical act of transferring a copy,\nand you may at your option offer warranty protection in exchange for a\nfee.\n\n  2. You may modify your copy or copies of the Library or any portion\nof it, thus forming a work based on the Library, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) The modified work must itself be a software library.\n\n    b) You must cause the files modified to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    c) You must cause the whole of the work to be licensed at no\n    charge to all third parties under the terms of this License.\n\n    d) If a facility in the modified Library refers to a function or a\n    table of data to be supplied by an application program that uses\n    the facility, other than as an argument passed when the facility\n    is invoked, then you must make a good faith effort to ensure that,\n    in the event an application does not supply such function or\n    table, the facility still operates, and performs whatever part of\n    its purpose remains meaningful.\n\n    (For example, a function in a library to compute square roots has\n    a purpose that is entirely well-defined independent of the\n    application.  Therefore, Subsection 2d requires that any\n    application-supplied function or table used by this function must\n    be optional: if the application does not supply it, the square\n    root function must still compute square roots.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Library,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Library, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote\nit.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Library.\n\nIn addition, mere aggregation of another work not based on the Library\nwith the Library (or with a work based on the Library) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may opt to apply the terms of the ordinary GNU General Public\nLicense instead of this License to a given copy of the Library.  To do\nthis, you must alter all the notices that refer to this License, so\nthat they refer to the ordinary GNU General Public License, version 2,\ninstead of to this License.  (If a newer version than version 2 of the\nordinary GNU General Public License has appeared, then you can specify\nthat version instead if you wish.)  Do not make any other change in\nthese notices.\n\n  Once this change is made in a given copy, it is irreversible for\nthat copy, so the ordinary GNU General Public License applies to all\nsubsequent copies and derivative works made from that copy.\n\n  This option is useful when you wish to copy part of the code of\nthe Library into a program that is not a library.\n\n  4. You may copy and distribute the Library (or a portion or\nderivative of it, under Section 2) in object code or executable form\nunder the terms of Sections 1 and 2 above provided that you accompany\nit with the complete corresponding machine-readable source code, which\nmust be distributed under the terms of Sections 1 and 2 above on a\nmedium customarily used for software interchange.\n\n  If distribution of object code is made by offering access to copy\nfrom a designated place, then offering equivalent access to copy the\nsource code from the same place satisfies the requirement to\ndistribute the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  5. A program that contains no derivative of any portion of the\nLibrary, but is designed to work with the Library by being compiled or\nlinked with it, is called a \"work that uses the Library\".  Such a\nwork, in isolation, is not a derivative work of the Library, and\ntherefore falls outside the scope of this License.\n\n  However, linking a \"work that uses the Library\" with the Library\ncreates an executable that is a derivative of the Library (because it\ncontains portions of the Library), rather than a \"work that uses the\nlibrary\".  The executable is therefore covered by this License.\nSection 6 states terms for distribution of such executables.\n\n  When a \"work that uses the Library\" uses material from a header file\nthat is part of the Library, the object code for the work may be a\nderivative work of the Library even though the source code is not.\nWhether this is true is especially significant if the work can be\nlinked without the Library, or if the work is itself a library.  The\nthreshold for this to be true is not precisely defined by law.\n\n  If such an object file uses only numerical parameters, data\nstructure layouts and accessors, and small macros and small inline\nfunctions (ten lines or less in length), then the use of the object\nfile is unrestricted, regardless of whether it is legally a derivative\nwork.  (Executables containing this object code plus portions of the\nLibrary will still fall under Section 6.)\n\n  Otherwise, if the work is a derivative of the Library, you may\ndistribute the object code for the work under the terms of Section 6.\nAny executables containing that work also fall under Section 6,\nwhether or not they are linked directly with the Library itself.\n\n  6. As an exception to the Sections above, you may also combine or\nlink a \"work that uses the Library\" with the Library to produce a\nwork containing portions of the Library, and distribute that work\nunder terms of your choice, provided that the terms permit\nmodification of the work for the customer's own use and reverse\nengineering for debugging such modifications.\n\n  You must give prominent notice with each copy of the work that the\nLibrary is used in it and that the Library and its use are covered by\nthis License.  You must supply a copy of this License.  If the work\nduring execution displays copyright notices, you must include the\ncopyright notice for the Library among them, as well as a reference\ndirecting the user to the copy of this License.  Also, you must do one\nof these things:\n\n    a) Accompany the work with the complete corresponding\n    machine-readable source code for the Library including whatever\n    changes were used in the work (which must be distributed under\n    Sections 1 and 2 above); and, if the work is an executable linked\n    with the Library, with the complete machine-readable \"work that\n    uses the Library\", as object code and/or source code, so that the\n    user can modify the Library and then relink to produce a modified\n    executable containing the modified Library.  (It is understood\n    that the user who changes the contents of definitions files in the\n    Library will not necessarily be able to recompile the application\n    to use the modified definitions.)\n\n    b) Use a suitable shared library mechanism for linking with the\n    Library.  A suitable mechanism is one that (1) uses at run time a\n    copy of the library already present on the user's computer system,\n    rather than copying library functions into the executable, and (2)\n    will operate properly with a modified version of the library, if\n    the user installs one, as long as the modified version is\n    interface-compatible with the version that the work was made with.\n\n    c) Accompany the work with a written offer, valid for at\n    least three years, to give the same user the materials\n    specified in Subsection 6a, above, for a charge no more\n    than the cost of performing this distribution.\n\n    d) If distribution of the work is made by offering access to copy\n    from a designated place, offer equivalent access to copy the above\n    specified materials from the same place.\n\n    e) Verify that the user has already received a copy of these\n    materials or that you have already sent this user a copy.\n\n  For an executable, the required form of the \"work that uses the\nLibrary\" must include any data and utility programs needed for\nreproducing the executable from it.  However, as a special exception,\nthe materials to be distributed need not include anything that is\nnormally distributed (in either source or binary form) with the major\ncomponents (compiler, kernel, and so on) of the operating system on\nwhich the executable runs, unless that component itself accompanies\nthe executable.\n\n  It may happen that this requirement contradicts the license\nrestrictions of other proprietary libraries that do not normally\naccompany the operating system.  Such a contradiction means you cannot\nuse both them and the Library together in an executable that you\ndistribute.\n\n  7. You may place library facilities that are a work based on the\nLibrary side-by-side in a single library together with other library\nfacilities not covered by this License, and distribute such a combined\nlibrary, provided that the separate distribution of the work based on\nthe Library and of the other library facilities is otherwise\npermitted, and provided that you do these two things:\n\n    a) Accompany the combined library with a copy of the same work\n    based on the Library, uncombined with any other library\n    facilities.  This must be distributed under the terms of the\n    Sections above.\n\n    b) Give prominent notice with the combined library of the fact\n    that part of it is a work based on the Library, and explaining\n    where to find the accompanying uncombined form of the same work.\n\n  8. You may not copy, modify, sublicense, link with, or distribute\nthe Library except as expressly provided under this License.  Any\nattempt otherwise to copy, modify, sublicense, link with, or\ndistribute the Library is void, and will automatically terminate your\nrights under this License.  However, parties who have received copies,\nor rights, from you under this License will not have their licenses\nterminated so long as such parties remain in full compliance.\n\n  9. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Library or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Library (or any work based on the\nLibrary), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Library or works based on it.\n\n  10. Each time you redistribute the Library (or any work based on the\nLibrary), the recipient automatically receives a license from the\noriginal licensor to copy, distribute, link with or modify the Library\nsubject to these terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties with\nthis License.\n\f\n  11. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Library at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Library by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Library.\n\nIf any portion of this section is held invalid or unenforceable under any\nparticular circumstance, the balance of the section is intended to apply,\nand the section as a whole is intended to apply in other circumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  12. If the distribution and/or use of the Library is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Library under this License may add\nan explicit geographical distribution limitation excluding those countries,\nso that distribution is permitted only in or among countries not thus\nexcluded.  In such case, this License incorporates the limitation as if\nwritten in the body of this License.\n\n  13. The Free Software Foundation may publish revised and/or new\nversions of the Lesser General Public License from time to time.\nSuch new versions will be similar in spirit to the present version,\nbut may differ in detail to address new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Library\nspecifies a version number of this License which applies to it and\n\"any later version\", you have the option of following the terms and\nconditions either of that version or of any later version published by\nthe Free Software Foundation.  If the Library does not specify a\nlicense version number, you may choose any version ever published by\nthe Free Software Foundation.\n\f\n  14. If you wish to incorporate parts of the Library into other free\nprograms whose distribution conditions are incompatible with these,\nwrite to the author to ask for permission.  For software which is\ncopyrighted by the Free Software Foundation, write to the Free\nSoftware Foundation; we sometimes make exceptions for this.  Our\ndecision will be guided by the two goals of preserving the free status\nof all derivatives of our free software and of promoting the sharing\nand reuse of software generally.\n\n                            NO WARRANTY\n\n  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO\nWARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.\nEXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR\nOTHER PARTIES PROVIDE THE LIBRARY \"AS IS\" WITHOUT WARRANTY OF ANY\nKIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE\nLIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME\nTHE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN\nWRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY\nAND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU\nFOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR\nCONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE\nLIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING\nRENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A\nFAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF\nSUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH\nDAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n           How to Apply These Terms to Your New Libraries\n\n  If you develop a new library, and you want it to be of the greatest\npossible use to the public, we recommend making it free software that\neveryone can redistribute and change.  You can do so by permitting\nredistribution under these terms (or, alternatively, under the terms of the\nordinary General Public License).\n\n  To apply these terms, attach the following notices to the library.  It is\nsafest to attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least the\n\"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the library's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This library is free software; you can redistribute it and/or\n    modify it under the terms of the GNU Lesser General Public\n    License as published by the Free Software Foundation; either\n    version 2.1 of the License, or (at your option) any later version.\n\n    This library is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n    Lesser General Public License for more details.\n\n    You should have received a copy of the GNU Lesser General Public\n    License along with this library; if not, write to the Free Software\n    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n\nAlso add information on how to contact you by electronic and paper mail.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the library, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the\n  library `Frob' (a library for tweaking knobs) written by James Random Hacker.\n\n  <signature of Ty Coon>, 1 April 1990\n  Ty Coon, President of Vice\n\nThat's all there is to it!\n\n"
  },
  {
    "path": "LICENCE_WTFPL",
    "content": "        DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE \n                    Version 2, December 2004 \n\n Copyright (C) 2014 Rene Treffer <treffer+wtfpl@measite.de> \n\n Everyone is permitted to copy and distribute verbatim or modified \n copies of this license document, and changing it is allowed as long \n as the name is changed. \n\n            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE \n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION \n\n  0. You just DO WHAT THE FUCK YOU WANT TO.\n"
  },
  {
    "path": "Makefile",
    "content": "GRADLE ?= ./gradlew\n\n.PHONY: all\nall: check codecov eclipse javadocAll inttest\n\n.PHONY: codecov\ncodecov:\n\t$(GRADLE) minidns-hla:testCodeCoverageReport\n\techo \"Code coverage report available at $(PWD)/minidns-hla/build/reports/jacoco/testCodeCoverageReport/html/index.html\"\n\n.PHONY: check\ncheck:\n\t$(GRADLE) $@\n\n.PHONY: eclipse\neclipse:\n\t$(GRADLE) $@\n\n.PHONY: inttest\ninttest:\n\t$(GRADLE) $@\n\n.PHONY: javadocAll\njavadocAll:\n\t$(GRADLE) $@\n\techo \"javadoc available at $(PWD)/build/javadoc/index.html\"\n"
  },
  {
    "path": "README.md",
    "content": "MiniDNS - A DNSSEC enabled DNS library\n======================================\n\n[![Build Status](https://github.com/MiniDNS/minidns/workflows/CI/badge.svg)](https://github.com/MiniDNS/minidns/actions?query=workflow%3A%22CI%22)  [![Coverage Status](https://coveralls.io/repos/MiniDNS/minidns/badge.svg)](https://coveralls.io/r/MiniDNS/minidns)\n\nMiniDNS (\"**M**odular **I**nternet **N**ame **I**nformer for **DNS**\") is a DNS library for Android and Java SE. It can parse resource records (A, AAAA, NS, SRV, …) and is easy to use and extend. MiniDNS aims to be secure, modular, efficient and as simple as possible. It also provides support for **DNSSEC** and **DANE**, and is thus the ideal resolver if you want to bring DNSSEC close to your application.\n\nIt comes with a pluggable cache mechanism, a pre-configured cache and an easy to use high-level API (`minidns-hla`) for those who just want to perform a reliable lookup of a domain name.\n\n**Notice:** DNSSEC/DANE support has not yet undergo a security audit.\nIf you find the project useful and if you are able to provide the resources for a security audit, then please contact us.\n\nIf you are looking for a DNSSEC-enabled resolver in C (and/or Lua) then hava a look at the [Knot Resolver](https://www.knot-resolver.cz/). Also this library is not intended to be used as a DNS server. You might want to\nlook into [dnsjava](http://dnsjava.org/) for such functionality.\n\n**MiniDNS release resources** (javadoc, …) an be found at https://minidns.org/releases\n\nQuickstart\n----------\n\nThe easiest way to use MiniDNS is by its high-level API provided by the minidns-hla Maven artifact. Simply add the artifact to your projects dependencies. For example with gradle\n\n```groovy\ncompile \"org.minidns:minidns-hla:$minidnsVersion\"\n```\n\nThen you can use the `ResolverApi` or `DnssecResolverApi` class to perform DNS lookups and check if the result was authenticated via DNSSEC. The following example shows a lookup of A records of 'verteiltesysteme.net'.\n\n```java\nResolverResult<A> result = DnssecResolverApi.INSTANCE.resolve(\"verteiltesysteme.net\", A.class);\nif (!result.wasSuccessful()) {\n\tRESPONSE_CODE responseCode = result.getResponseCode();\n\t// Perform error handling.\n\t…\n\treturn;\n}\nif (!result.isAuthenticData()) {\n\t// Response was not secured with DNSSEC.\n\t…\n\treturn;\n}\nSet<A> answers = result.getAnswers();\nfor (A a : answers) {\n  InetAddress inetAddress = a.getInetAddress();\n  // Do someting with the InetAddress, e.g. connect to.\n  …\n}\n```\n\nMiniDNS also provides full support for SRV resource records and their handling.\n\n```java\nSrvResolverResult result = DnssecResolverApi.INSTANCE.resolveSrv(SrvType.xmpp_client, \"example.org\")\nif (!result.wasSuccessful()) {\n\tRESPONSE_CODE responseCode = result.getResponseCode();\n\t// Perform error handling.\n\t…\n\treturn;\n}\nif (!result.isAuthenticData()) {\n\t// Response was not secured with DNSSEC.\n\t…\n\treturn;\n}\nList<ResolvedSrvRecord> srvRecords = result.getSortedSrvResolvedAddresses();\n// Loop over the domain names pointed by the SRV RR. MiniDNS will return the list\n// correctly sorted by the priority and weight of the related SRV RR.\nfor (ResolvedSrvRecord srvRecord : srvRecord) {\n\t// Loop over the Internet Address RRs resolved for the SRV RR. The order of\n\t// the list depends on the prefered IP version setting of MiniDNS.\n\tfor (InternetAddressRR inetAddressRR : srvRecord.addresses) {\n\t\tInetAddress inetAddress = inetAddressRR.getInetAddress();\n\t\tint port = srvAddresses.port;\n\t\t// Try to connect to inetAddress at port.\n\t\t…\n\t}\n}\n```\n\nREPL\n----\n\nMiniDNS comes with a REPL which can be used to perform DNS lookups and to test the library. Simple use `./repl` to start the REPL. The loaded REPL comes with some predefined variables that you can use to perform lookups. For example `c` is a simple DNS client. See `minidns-repl/scala.repl` for more.\n\n```text\nminidns $ ./repl\n...\nscala> c query (\"measite.de\", TYPE.A)\nres0: dnsqueryresult.DnsQueryResult = DnsMessage@54653(QUERY NO_ERROR qr rd ra) { \\\n  [Q: measite.de.\tIN\tA] \\\n  [A: measite.de.\t3599\tIN\tA\t85.10.226.249] \\\n  [X: EDNS: version: 0, flags:; udp: 512]\n}\n```\n"
  },
  {
    "path": "build-logic/build.gradle",
    "content": "plugins {\n\tid 'groovy-gradle-plugin'\n}\n\nrepositories {\n\tgradlePluginPortal()\n}\n\ndependencies {\n\timplementation \"biz.aQute.bnd:biz.aQute.bnd.gradle:7.0.0\"\n\timplementation \"net.ltgt.gradle:gradle-errorprone-plugin:4.0.1\"\n\timplementation \"ru.vyarus:gradle-animalsniffer-plugin:1.7.1\"\n}\n"
  },
  {
    "path": "build-logic/settings.gradle",
    "content": "rootProject.name = 'minidns-build-logic'\n"
  },
  {
    "path": "build-logic/src/main/groovy/org.minidns.android-boot-classpath-conventions.gradle",
    "content": "compileJava {\n\toptions.bootstrapClasspath = files(androidBootClasspath)\n}\njavadoc {\n\tclasspath += files(androidBootClasspath)\n}\n"
  },
  {
    "path": "build-logic/src/main/groovy/org.minidns.android-conventions.gradle",
    "content": "plugins {\n\tid 'ru.vyarus.animalsniffer'\n\tid 'org.minidns.common-conventions'\n}\ndependencies {\n\tsignature \"net.sf.androidscents.signature:android-api-level-${minAndroidSdk}:4.4.2_r4@signature\"\n}\nanimalsniffer {\n\tsourceSets = [sourceSets.main]\n}\n"
  },
  {
    "path": "build-logic/src/main/groovy/org.minidns.application-conventions.gradle",
    "content": "plugins {\n\tid 'application'\n}\n\napplication {\n\tapplicationDefaultJvmArgs = [\"-enableassertions\"]\n}\n\nrun {\n\t// Pass all system properties down to the \"application\" run\n\tsystemProperties System.getProperties()\n}\n"
  },
  {
    "path": "build-logic/src/main/groovy/org.minidns.common-conventions.gradle",
    "content": "ext {\n\tjavaVersion = JavaVersion.VERSION_11\n\tjavaMajor = javaVersion.getMajorVersion()\n\tminAndroidSdk = 19\n\n\tandroidBootClasspath = getAndroidRuntimeJar(minAndroidSdk)\n\n\t// Export the function by turning it into a closure.\n\t// https://stackoverflow.com/a/23290820/194894\n\tgetAndroidRuntimeJar = this.&getAndroidRuntimeJar\n}\n\nrepositories {\n\tmavenLocal()\n\tmavenCentral()\n}\n\ndef getAndroidRuntimeJar(androidApiLevel) {\n\tdef androidHome = getAndroidHome()\n\tdef androidJar = new File(\"$androidHome/platforms/android-${androidApiLevel}/android.jar\")\n\tif (androidJar.isFile()) {\n\t\treturn androidJar\n\t} else {\n\t\tthrow new Exception(\"Can't find android.jar for API level ${androidApiLevel}. Please install corresponding SDK platform package\")\n\t}\n}\n\ndef getAndroidHome() {\n\tdef androidHomeEnv = System.getenv(\"ANDROID_HOME\")\n\tif (androidHomeEnv == null) {\n\t\tthrow new Exception(\"ANDROID_HOME environment variable is not set\")\n\t}\n\tdef androidHome = new File(androidHomeEnv)\n\tif (!androidHome.isDirectory()) throw new Exception(\"Environment variable ANDROID_HOME is not pointing to a directory\")\n\treturn androidHome\n}\n"
  },
  {
    "path": "build-logic/src/main/groovy/org.minidns.java-conventions.gradle",
    "content": "plugins {\n\tid 'biz.aQute.bnd.builder'\n\tid 'checkstyle'\n\tid 'eclipse'\n\tid 'idea'\n\tid 'jacoco'\n\tid 'java'\n\tid 'java-library'\n\tid 'java-test-fixtures'\n\tid 'maven-publish'\n\tid 'net.ltgt.errorprone'\n\tid 'signing'\n\n\tid 'jacoco-report-aggregation'\n\tid 'test-report-aggregation'\n\n\tid 'org.minidns.common-conventions'\n\tid 'org.minidns.javadoc-conventions'\n}\n\nversion readVersionFile()\n\next {\n\tisSnapshot = version.endsWith('-SNAPSHOT')\n\tgitCommit = getGitCommit()\n\trootConfigDir = new File(rootDir, 'config')\n\tsonatypeCredentialsAvailable = project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePassword')\n\tisReleaseVersion = !isSnapshot\n\tisContinuousIntegrationEnvironment = Boolean.parseBoolean(System.getenv('CI'))\n\tsigningRequired = !(isSnapshot || isContinuousIntegrationEnvironment)\n\tsonatypeSnapshotUrl = 'https://oss.sonatype.org/content/repositories/snapshots'\n\tsonatypeStagingUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2'\n\tbuiltDate = (new java.text.SimpleDateFormat(\"yyyy-MM-dd\")).format(new Date())\n\n\tjunitVersion = '5.9.2'\n\n\tif (project.hasProperty(\"useSonatype\")) {\n\t\tuseSonatype = project.getProperty(\"useSonatype\").toBoolean()\n\t} else {\n\t\t// Default to true\n\t\tuseSonatype = true\n\t}\n}\n\ngroup = 'org.minidns'\n\njava {\n\tsourceCompatibility = javaVersion\n\ttargetCompatibility = sourceCompatibility\n}\n\neclipse {\n\tclasspath {\n\t\tdownloadJavadoc = true\n\t}\n}\n\n// Make all project's 'test' target depend on javadoc, so that\n// javadoc is also linted.\ntest.dependsOn javadoc\n\ntasks.withType(JavaCompile) {\n\t// Some systems may not have set their platform default\n\t// converter to 'utf8', but we use unicode in our source\n\t// files. Therefore ensure that javac uses unicode\n\toptions.encoding = \"utf8\"\n\toptions.compilerArgs = [\n\t\t'-Xlint:all',\n\t\t// Set '-options' because a non-java7 javac will emit a\n\t\t// warning if source/target is set to 1.7 and\n\t\t// bootclasspath is *not* set.\n\t\t'-Xlint:-options',\n\t\t// TODO: Enable xlint serial\n\t\t'-Xlint:-serial',\n\t\t'-Werror',\n\t]\n\toptions.release = Integer.valueOf(javaMajor)\n}\n\njacoco {\n\ttoolVersion = \"0.8.12\"\n}\n\njacocoTestReport {\n\tdependsOn test\n\treports {\n\t\txml.required = true\n\t}\n}\n\ndependencies {\n\ttestImplementation \"org.junit.jupiter:junit-jupiter-api:$junitVersion\"\n\ttestRuntimeOnly \"org.junit.jupiter:junit-jupiter-engine:$junitVersion\"\n\n\ttestFixturesApi \"org.junit.jupiter:junit-jupiter-api:$junitVersion\"\n\ttestImplementation \"org.junit.jupiter:junit-jupiter-params:$junitVersion\"\n\ttestRuntimeOnly \"org.junit.jupiter:junit-jupiter-engine:$junitVersion\"\n\t// https://stackoverflow.com/a/77274251/194894\n\ttestRuntimeOnly \"org.junit.platform:junit-platform-launcher:1.11.0\"\n\n\terrorprone 'com.google.errorprone:error_prone_core:2.32.0'\n}\n\ntest {\n\tuseJUnitPlatform()\n\n\tmaxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1\n\n\t// Enable full stacktraces of failed tests. Especially handy\n\t// for CI environments.\n\ttestLogging {\n\t\tevents \"failed\"\n\t\texceptionFormat \"full\"\n\t}\n}\n\njar {\n\tmanifest {\n\t\tattributes(\n\t\t\t'Implementation-Version': version,\n\t\t\t'Implementation-GitRevision': gitCommit,\n\t\t\t'Built-JDK': System.getProperty('java.version'),\n\t\t\t'Built-Gradle': gradle.gradleVersion,\n\t\t\t'Built-By': System.getProperty('user.name')\n\t\t)\n\t}\n\n\tbundle {\n\t\tbnd(\n\t\t\t\t'-removeheaders': 'Tool, Bnd-*',\n\t\t\t\t'-exportcontents': 'org.minidns.*',\n\t\t\t\t'Import-Package': '!android,*'\n\t\t)\n\t}\n}\n\ncheckstyle {\n\ttoolVersion = '10.18.2'\n\n\tconfigProperties.checkstyleLicenseHeader = \"header\"\n}\ntask sourcesJar(type: Jar, dependsOn: classes) {\n\tarchiveClassifier = 'sources'\n\tfrom sourceSets.main.allSource\n}\ntask javadocJar(type: Jar, dependsOn: javadoc) {\n\tarchiveClassifier = 'javadoc'\n\tfrom javadoc.destinationDir\n}\ntask testsJar(type: Jar) {\n\tarchiveClassifier = 'tests'\n\tfrom sourceSets.test.output\n}\nconfigurations {\n\ttestRuntime\n}\nartifacts {\n\t// Add a 'testRuntime' configuration including the tests so that\n\t// it can be consumed by other projects. See\n\t// http://stackoverflow.com/a/21946676/194894\n\ttestRuntime testsJar\n}\n\npublishing {\n\tpublications {\n\t\tmavenJava(MavenPublication) {\n\t\t\tfrom components.java\n\t\t\tartifact sourcesJar\n\t\t\tartifact javadocJar\n\t\t\tartifact testsJar\n\t\t\tpom {\n\t\t\t\tname = 'MiniDNS'\n\t\t\t\tpackaging = 'jar'\n\t\t\t\tinceptionYear = '2014'\n\t\t\t\turl = 'https://github.com/minidns/minidns'\n\t\t\t\tafterEvaluate {\n\t\t\t\t\tdescription = project.description\n\t\t\t\t}\n\n\t\t\t\tscm {\n\t\t\t\t\turl = 'https://github.com/minidns/minidns'\n\t\t\t\t\tconnection = 'scm:https://github.com/minidns/minidns'\n\t\t\t\t\tdeveloperConnection = 'scm:git://github.com/minidns/minidns.git'\n\t\t\t\t}\n\n\t\t\t\tlicenses {\n\t\t\t\t\tlicense {\n\t\t\t\t\t\tname = 'The Apache Software License, Version 2.0'\n\t\t\t\t\t\turl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'\n\t\t\t\t\t\tdistribution = 'repo'\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tdevelopers {\n\t\t\t\t\tdeveloper {\n\t\t\t\t\t\tid = 'rtreffer'\n\t\t\t\t\t\tname = 'Rene Treffer'\n\t\t\t\t\t\temail = 'treffer@measite.de'\n\t\t\t\t\t}\n\t\t\t\t\tdeveloper {\n\t\t\t\t\t\tid = 'flow'\n\t\t\t\t\t\tname = 'Florian Schmaus'\n\t\t\t\t\t\temail = 'flow@geekplace.eu'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\trepositories {\n\t\tif (sonatypeCredentialsAvailable && useSonatype) {\n\t\t\tmaven {\n\t\t\t\turl isSnapshot ? sonatypeSnapshotUrl : sonatypeStagingUrl\n\t\t\t\tcredentials {\n\t\t\t\t\tusername = sonatypeUsername\n\t\t\t\t\tpassword = sonatypePassword\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Use\n\t\t// gradle publish -P customRepoUrl=https://www.igniterealtime.org/archiva/repository/maven -P customRepoUsername=bamboo -P customRepoPassword=hidden -P useSonatype=false\n\t\t// to deploy to this repo.\n\t\tif (project.hasProperty(\"customRepoUrl\")) {\n\t\t\tmaven {\n\t\t\t\tname 'customRepo'\n\t\t\t\turl customRepoUrl\n\t\t\t\tif (project.hasProperty(\"customRepoUsername\")) {\n\t\t\t\t\tcredentials {\n\t\t\t\t\t\tusername customRepoUsername\n\t\t\t\t\t\tpassword customRepoPassword\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Workaround for gpg signatory not supporting the 'required' option\n// See https://github.com/gradle/gradle/issues/5064#issuecomment-381924984\n// Note what we use 'signing.gnupg.keyName' instead of 'signing.keyId'.\ntasks.withType(Sign) {\n\tonlyIf {\n\t\tproject.hasProperty('signing.gnupg.keyName')\n\t}\n}\nsigning {\n\trequired { signingRequired }\n\tuseGpgCmd()\n\tsign publishing.publications.mavenJava\n}\n\ntasks.withType(JavaCompile) {\n\toptions.errorprone {\n\t\tdisableWarningsInGeneratedCode = true\n\t\texcludedPaths = \".*/jmh_generated/.*\"\n\t\terror(\n\t\t\t\"UnusedVariable\",\n\t\t\t\"UnusedMethod\",\n\t\t\t\"MethodCanBeStatic\",\n\t\t)\n\t\terrorproneArgs = [\n\t\t\t// Disable MissingCasesInEnumSwitch error prone check\n\t\t\t// because this check is already done by javac as incomplete-switch.\n\t\t\t'-Xep:MissingCasesInEnumSwitch:OFF',\n\t\t\t'-Xep:StringSplitter:OFF',\n\t\t\t'-Xep:JavaTimeDefaultTimeZone:OFF',\n\t\t\t'-Xep:InlineMeSuggester:OFF',\n\t\t]\n\t}\n}\n\n// Work around https://github.com/gradle/gradle/issues/4046\ntask copyJavadocDocFiles(type: Copy) {\n\tfrom('src/javadoc')\n\tinto 'build/docs/javadoc'\n\tinclude '**/doc-files/*.*'\n}\njavadoc.dependsOn copyJavadocDocFiles\n\ndef getGitCommit() {\n\tdef projectDirFile = new File(\"$projectDir\")\n\n\tdef cmd = 'git describe --always --tags --dirty=+'\n\tdef proc = cmd.execute(null, projectDirFile)\n\n\tdef exitStatus = proc.waitFor()\n\tif (exitStatus != 0) return \"non-git build\"\n\n\tdef gitCommit = proc.text.trim()\n\tassert !gitCommit.isEmpty()\n\tgitCommit\n}\n\ndef readVersionFile() {\n\tdef versionFile = new File(rootDir, 'version')\n\tif (!versionFile.isFile()) {\n\t\tthrow new Exception(\"Could not find version file\")\n\t}\n\tif (versionFile.text.isEmpty()) {\n\t\tthrow new Exception(\"Version file does not contain a version\")\n\t}\n\tversionFile.text.trim()\n}\n"
  },
  {
    "path": "build-logic/src/main/groovy/org.minidns.javadoc-conventions.gradle",
    "content": "plugins {\n\t// Javadoc linking requires repositories to bet configured. And\n\t// those are declared in common-conventions, hence we add it here.\n\tid 'org.minidns.common-conventions'\n}\n\n\ntasks.withType(Javadoc) {\n\t// The '-quiet' as second argument is actually a hack,\n\t// since the one parameter addStringOption doesn't seem to\n\t// work, we extra add '-quiet', which is added anyway by\n\t// gradle.\n\t// We disable 'missing' as we do most of javadoc checking via checkstyle.\n\toptions.addStringOption('Xdoclint:all,-missing', '-quiet')\n\t// Abort on javadoc warnings.\n\t// See JDK-8200363 (https://bugs.openjdk.java.net/browse/JDK-8200363)\n\t// for information about the -Xwerror option.\n\toptions.addStringOption('Xwerror', '-quiet')\n\toptions.addStringOption('-release', javaMajor)\n}\n\ntasks.withType(Javadoc) {\n\toptions.charSet = \"UTF-8\"\n}\n"
  },
  {
    "path": "build.gradle",
    "content": "plugins {\n\t// The scalastyle plugin of smack-repl wants the root project to\n\t// have a ideaProject task, so let's add one.\n\tid 'idea'\n\n\tid 'org.minidns.javadoc-conventions'\n}\n\next {\n\tjavadocAllDir = new File(buildDir, 'javadoc')\n\tnonJavadocAllProjects = [\n\t\t':minidns-integration-test',\n\t\t':minidns-repl',\n\t].collect{ project(it) }\n\tjavadocAllProjects = subprojects - nonJavadocAllProjects\n}\n\nevaluationDependsOnChildren()\ntask javadocAll(type: Javadoc) {\n\tsource javadocAllProjects.collect {project ->\n\t\tproject.sourceSets.main.allJava.findAll {\n\t\t\t// Filter out symbolic links to avoid\n\t\t\t// \"warning: a package-info.java file has already been seen for package\"\n\t\t\t// javadoc warnings.\n\t\t\t!java.nio.file.Files.isSymbolicLink(it.toPath())\n\t\t}\n\t}\n\tdestinationDir = javadocAllDir\n\t// Might need a classpath\n\tclasspath = files(subprojects.collect {project ->\n\t\tproject.sourceSets.main.compileClasspath})\n\tclasspath += files(androidBootClasspath)\n\toptions {\n\t\tlinkSource = true\n\t\tuse = true\n\t\tlinks = [\n\t\t\"https://docs.oracle.com/en/java/javase/${javaMajor}/docs/api/\",\n\t\t] as String[]\n\t\toverview = \"$projectDir/resources/javadoc-overview.html\"\n\t}\n\n\t// Finally copy the javadoc doc-files from the subprojects, which\n\t// are potentially generated, to the javadocAll directory. Note\n\t// that we use a copy *method* and not a *task* because the inputs\n\t// of copy tasks is determined within the configuration phase. And\n\t// since some of the inputs are generated, they will not get\n\t// picked up if we used a copy method. See also\n\t// https://stackoverflow.com/a/40518516/194894\n\tdoLast {\n\t\tcopy {\n\t\t\tjavadocAllProjects.each {\n\t\t\t\tfrom (\"${it.projectDir}/src/javadoc\") {\n\t\t\t\t\tinclude '**/doc-files/*.*'\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tinto javadocAllDir\n\t\t}\n\t}\n}\n\ntask inttest {\n\tdescription 'Verify correct functionality by running some integration tests.'\n\tdependsOn project(':minidns-integration-test').tasks.run\n}\n"
  },
  {
    "path": "config/checkstyle/checkstyle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE module PUBLIC\n    \"-//Puppy Crawl//DTD Check Configuration 1.3//EN\"\n     \"http://www.puppycrawl.com/dtds/configuration_1_3.dtd\">\n<module name=\"Checker\">\n\t<module name=\"SuppressionFilter\">\n\t\t<property name=\"file\" value=\"${config_loc}/suppressions.xml\"/>\n\t</module>\n\t<module name=\"Header\">\n\t\t<property name=\"headerFile\" value=\"${config_loc}/header.txt\"/>\n\t\t<property name=\"ignoreLines\" value=\"3\"/>\n\t\t<property name=\"fileExtensions\" value=\"java\"/>\n\t</module>\n\t<module name=\"NewlineAtEndOfFile\">\n\t\t<property name=\"lineSeparator\" value=\"lf\"/>\n\t</module>\n\t<module name=\"RegexpSingleline\">\n\t\t<property name=\"format\" value=\"MXParser\"/>\n\t\t<property name=\"message\" value=\"Must not use MXParser, use XmlPullParserFactory instead\"/>\n\t</module>\n\t<module name=\"RegexpSingleline\">\n\t\t<property name=\"format\" value=\"^\\s+$\"/>\n\t\t<property name=\"message\" value=\"Line containing only whitespace character(s)\"/>\n\t</module>\n\t<module name=\"RegexpSingleline\">\n\t\t<property name=\"format\" value=\"^ +\\t+\"/>\n\t\t<property name=\"message\" value=\"Line containing tab(s) after space\"/>\n\t</module>\n\t<module name=\"RegexpSingleline\">\n\t\t<!-- We use {2,} instead of + here to address the typical case where a file was written\n\t\t   with tabs but javadoc is causing '\\t *' -->\n\t\t<property name=\"format\" value=\"^\\t+ {2,}\"/>\n\t\t<property name=\"message\" value=\"Line containing space(s) after tab(s)\"/>\n\t</module>\n\t<module name=\"RegexpSingleline\">\n\t\t<!--\n\t\t\tExplaining the following Regex\n\n\t\t\t^   \\s*   [\\S && [^  \\*/]]+   \\s+   $\n\t\t\t|    |        |           |    |    +- End of Line (6)\n\t\t\t|    |        |           |    +- At least one whitespace (5)\n\t\t\t|    |        |           +- At least one or more of the previous character class (4)\n\t\t\t|    |        +- All non-whitespace characters except '*' and '/' (to exclude javadoc) (3)\n\t\t\t|    +- Zero or more space characters (2)\n\t\t\t+- Start of Line (1)\n\n\t\t\tRationale:\n\t\t\tMatches trailing whitespace (5) in lines containing at least one (4) non-whitespace character\n\t\t\tthat is not one of the characters used by javadoc (3).\n\t\t-->\n\t\t<property name=\"format\" value=\"^\\s*[\\S&amp;&amp;[^\\*/]]+\\s+$\"/>\n\t\t<property name=\"message\" value=\"Line containing trailing whitespace character(s)\"/>\n\t</module>\n\t<module name=\"RegexpSingleline\">\n\t  <property name=\"format\" value=\"^\\s*//[^\\s]\"/>\n\t  <property name=\"message\" value=\"Comment start ('//') followed by non-space character. You would not continue after a punctuation without a space, would you?\"/>\n\t</module>\n\t<!-- <module name=\"JavadocPackage\"/> -->\n\t<module name=\"TreeWalker\">\n\t\t<module name=\"SuppressionCommentFilter\"/>\n\t\t<module name=\"FinalClass\"/>\n\t\t<module name=\"UnusedImports\">\n\t\t\t<property name=\"processJavadoc\" value=\"true\"/>\n\t\t</module>\n\t\t<module name=\"AvoidStarImport\"/>\n\t\t<module name=\"IllegalImport\"/>\n\t\t<module name=\"RedundantImport\"/>\n\t\t<module name=\"RedundantModifier\"/>\n\t\t<module name=\"ModifierOrder\"/>\n\t\t<module name=\"UpperEll\"/>\n\t\t<module name=\"ArrayTypeStyle\"/>\n\t\t<module name=\"GenericWhitespace\"/>\n\t\t<module name=\"EmptyStatement\"/>\n\t\t<module name=\"PackageDeclaration\"/>\n\t\t<module name=\"LeftCurly\"/>\n\t\t<module name=\"RegexpSinglelineJava\">\n\t\t\t<property name=\"format\" value=\"printStackTrace\"/>\n\t\t\t<property name=\"message\" value=\"Usage of printStackTrace\"/>\n\t\t\t<property name=\"ignoreComments\" value=\"true\"/>\n\t\t</module>\n\t\t<module name=\"RegexpSinglelineJava\">\n\t\t\t<property name=\"format\" value=\"println\"/>\n\t\t\t<property name=\"message\" value=\"Usage of println\"/>\n\t\t\t<property name=\"ignoreComments\" value=\"true\"/>\n\t\t</module>\n\t\t<module name=\"JavadocMethod\">\n\t\t\t<!-- TODO stricten those checks -->\n\t\t</module>\n\t\t<module name=\"JavadocStyle\">\n\t\t\t<property name=\"scope\" value=\"public\"/>\n\t\t\t<property name=\"checkEmptyJavadoc\" value=\"true\"/>\n\t\t\t<property name=\"checkHtml\" value=\"false\"/>\n\t\t</module>\n\t\t<module name=\"ParenPad\">\n\t\t</module>\n\t\t<module name=\"NoWhitespaceAfter\">\n\t\t  <property name=\"tokens\" value=\"INC\n\t\t\t\t\t\t\t\t\t\t , DEC\n\t\t\t\t\t\t\t\t\t\t , UNARY_MINUS\n\t\t\t\t\t\t\t\t\t\t , UNARY_PLUS\n\t\t\t\t\t\t\t\t\t\t , BNOT, LNOT\n\t\t\t\t\t\t\t\t\t\t , DOT\n\t\t\t\t\t\t\t\t\t\t , ARRAY_DECLARATOR\n\t\t\t\t\t\t\t\t\t\t , INDEX_OP\n\t\t\t\t\t\t\t\t\t\t \"/>\n\t\t</module>\n\t\t<module name=\"WhitespaceAfter\">\n\t\t  <property name=\"tokens\" value=\"TYPECAST\n\t\t\t\t\t\t\t\t\t\t , LITERAL_IF\n\t\t\t\t\t\t\t\t\t\t , LITERAL_ELSE\n\t\t\t\t\t\t\t\t\t\t , LITERAL_WHILE\n\t\t\t\t\t\t\t\t\t\t , LITERAL_DO\n\t\t\t\t\t\t\t\t\t\t , LITERAL_FOR\n\t\t\t\t\t\t\t\t\t\t , DO_WHILE\n\t\t\t\t\t\t\t\t\t\t , COMMA\n\t\t\t\t\t\t\t\t\t\t \"/>\n\t\t</module>\n\t\t<module name=\"WhitespaceAround\">\n\t\t  <property\n\t\t\t  name=\"ignoreEnhancedForColon\"\n\t\t\t  value=\"false\"\n\t\t\t  />\n\t\t  <!-- Currently disabled tokens: LCURLY, RCURLY, WILDCARD_TYPE, GENERIC_START, GENERIC_END -->\n\t\t  <property\n\t\t\t  name=\"tokens\"\n\t\t\t  value=\"ASSIGN\n\t\t\t\t\t , ARRAY_INIT\n\t\t\t\t\t , BAND\n\t\t\t\t\t , BAND_ASSIGN\n\t\t\t\t\t , BOR\n\t\t\t\t\t , BOR_ASSIGN\n\t\t\t\t\t , BSR\n\t\t\t\t\t , BSR_ASSIGN\n\t\t\t\t\t , BXOR\n\t\t\t\t\t , BXOR_ASSIGN\n\t\t\t\t\t , COLON\n\t\t\t\t\t , DIV\n\t\t\t\t\t , DIV_ASSIGN\n\t\t\t\t\t , DO_WHILE\n\t\t\t\t\t , EQUAL\n\t\t\t\t\t , GE\n\t\t\t\t\t , GT\n\t\t\t\t\t , LAMBDA\n\t\t\t\t\t , LAND\n\t\t\t\t\t , LE\n\t\t\t\t\t , LITERAL_CATCH\n\t\t\t\t\t , LITERAL_DO\n\t\t\t\t\t , LITERAL_ELSE\n\t\t\t\t\t , LITERAL_FINALLY\n\t\t\t\t\t , LITERAL_FOR\n\t\t\t\t\t , LITERAL_IF\n\t\t\t\t\t , LITERAL_RETURN\n\t\t\t\t\t , LITERAL_SWITCH\n\t\t\t\t\t , LITERAL_SYNCHRONIZED\n\t\t\t\t\t , LITERAL_TRY\n\t\t\t\t\t , LITERAL_WHILE\n\t\t\t\t\t , LOR\n\t\t\t\t\t , LT\n\t\t\t\t\t , MINUS\n\t\t\t\t\t , MINUS_ASSIGN\n\t\t\t\t\t , MOD\n\t\t\t\t\t , MOD_ASSIGN\n\t\t\t\t\t , NOT_EQUAL\n\t\t\t\t\t , PLUS\n\t\t\t\t\t , PLUS_ASSIGN\n\t\t\t\t\t , QUESTION\n\t\t\t\t\t , SL\n\t\t\t\t\t , SLIST\n\t\t\t\t\t , SL_ASSIGN\n\t\t\t\t\t , SR\n\t\t\t\t\t , SR_ASSIGN\n\t\t\t\t\t , STAR\n\t\t\t\t\t , STAR_ASSIGN\n\t\t\t\t\t , LITERAL_ASSERT\n\t\t\t\t\t , TYPE_EXTENSION_AND\n\t\t\t\t\t \"/>\n\t\t</module>\n\t\t<!-- TODO: Enable this check\n\t\t<module name=\"CustomImportOrder\">\n\t\t\t<property name=\"customImportOrderRules\"\n\t\t\t\t\t  value=\"STATIC###STANDARD_JAVA_PACKAGE###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE\"/>\n\t\t\t<property name=\"specialImportsRegExp\" value=\"^org\\.minidns\"/>\n\t\t\t<property name=\"sortImportsInGroupAlphabetically\" value=\"true\"/>\n\t\t\t<property name=\"separateLineBetweenGroups\" value=\"true\"/>\n\t\t</module>\n\t\t-->\n\t\t<module name=\"MissingJavadocPackage\"/>\n\t\t<module name=\"UnnecessaryParentheses\"/>\n\t\t<module name=\"UnnecessarySemicolonInEnumeration\"/>\n\t\t<module name=\"UnnecessarySemicolonInTryWithResources\"/>\n\t</module>\n</module>\n"
  },
  {
    "path": "config/checkstyle/header.txt",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\n"
  },
  {
    "path": "config/checkstyle/suppressions.xml",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE suppressions PUBLIC\n    \"-//Puppy Crawl//DTD Suppressions 1.1//EN\"\n    \"http://www.puppycrawl.com/dtds/suppressions_1_1.dtd\">\n<suppressions>\n\t<!-- Suppress JavadocPackage in the test packages -->\n\t<suppress checks=\"JavadocPackage\" files=\"[\\\\/]test[\\\\/]\"/>\n</suppressions>\n"
  },
  {
    "path": "config/scalaStyle.xml",
    "content": "<scalastyle commentFilter=\"enabled\">\n <name>Scalastyle standard configuration</name>\n <check level=\"error\" class=\"org.scalastyle.file.FileTabChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.file.FileLengthChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"maxFileLength\"><![CDATA[800]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.file.HeaderMatchesChecker\" enabled=\"true\">\n   <parameters>\n    <parameter name=\"regex\">true</parameter>\n    <parameter name=\"header\"><![CDATA[/\\*\\*\n \\*\n \\* Copyright 2.*\n \\*\n \\* Licensed under the Apache License, Version 2\\.0 \\(the \"License\"\\);\n \\* you may not use this file except in compliance with the License.\n \\* You may obtain a copy of the License at\n \\*\n \\*     http://www.apache.org/licenses/LICENSE-2\\.0\n \\*\n \\* Unless required by applicable law or agreed to in writing, software\n \\* distributed under the License is distributed on an \"AS IS\" BASIS,\n \\* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n \\* See the License for the specific language governing permissions and\n \\* limitations under the License.\n \\*/]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.SpacesAfterPlusChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.file.WhitespaceEndOfLineChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.SpacesBeforePlusChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.file.FileLineLengthChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"maxLineLength\"><![CDATA[160]]></parameter>\n   <parameter name=\"tabSize\"><![CDATA[4]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.ClassNamesChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"regex\"><![CDATA[[A-Z][A-Za-z]*]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.ObjectNamesChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"regex\"><![CDATA[[A-Z][A-Za-z]*]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.PackageObjectNamesChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"regex\"><![CDATA[^[a-z][A-Za-z]*$]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.EqualsHashCodeChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.IllegalImportsChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"illegalImports\"><![CDATA[sun._,java.awt._]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.ParameterNumberChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"maxParameters\"><![CDATA[8]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.MagicNumberChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"ignore\"><![CDATA[-1,0,1,2,3]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.NoWhitespaceBeforeLeftBracketChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.NoWhitespaceAfterLeftBracketChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.ReturnChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.NullChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.NoCloneChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.NoFinalizeChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.CovariantEqualsChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.StructuralTypeChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.file.RegexChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"regex\"><![CDATA[println]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.NumberOfTypesChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"maxTypes\"><![CDATA[30]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.CyclomaticComplexityChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"maximum\"><![CDATA[10]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.UppercaseLChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.SimplifyBooleanExpressionChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.IfBraceChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"singleLineAllowed\"><![CDATA[true]]></parameter>\n   <parameter name=\"doubleLineAllowed\"><![CDATA[false]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.MethodLengthChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"maxLength\"><![CDATA[50]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.MethodNamesChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"regex\"><![CDATA[^[a-z][A-Za-z0-9]*$]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.NumberOfMethodsInTypeChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"maxMethods\"><![CDATA[30]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.PublicMethodsHaveTypeChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.file.NewLineAtEofChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.file.NoNewLineAtEofChecker\" enabled=\"false\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.WhileChecker\" enabled=\"false\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.VarFieldChecker\" enabled=\"false\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.VarLocalChecker\" enabled=\"false\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.RedundantIfChecker\" enabled=\"false\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.TokenChecker\" enabled=\"false\">\n  <parameters>\n   <parameter name=\"regex\"><![CDATA[println]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.DeprecatedJavaChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.EmptyClassChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.ClassTypeParameterChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"regex\"><![CDATA[^[A-Z_]$]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.UnderscoreImportChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.LowercasePatternMatchChecker\" enabled=\"true\"></check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.MultipleStringLiteralsChecker\" enabled=\"true\">\n  <parameters>\n   <parameter name=\"allowed\"><![CDATA[2]]></parameter>\n   <parameter name=\"ignoreRegex\"><![CDATA[^\"\"$]]></parameter>\n  </parameters>\n </check>\n <check level=\"error\" class=\"org.scalastyle.scalariform.ImportGroupingChecker\" enabled=\"true\"></check>\n</scalastyle>\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.10.2-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties.example",
    "content": "# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n#\n# GPG settings\n#\n\n# gpg key id\n#signing.keyId=DEADBEEF\n# the gpg key passphrase\n#signing.password=correcthorsebatterystaple\n# gpg keyring (this is the default gnupg keyring containing private keys)\n#signing.secretKeyRingFile=/home/ubuntu/.gnupg/secring.gpg\n\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n#\n# nexus settings\n#\n\n# the nexus username used for log in\n#nexusUsername=ubuntu\n# the nexus password\n#nexusPassword=correcthorsebatterystaple\n"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "minidns-android23/build.gradle",
    "content": "plugins {\n\tid 'org.minidns.java-conventions'\n\tid 'org.minidns.android-conventions'\n}\n\next {\n\tmyAndroidSdkApi = 23\n\tandroidBootClasspath = getAndroidRuntimeJar(myAndroidSdkApi)\n}\n\ndescription = \"MiniDNS code that requires an Android SDK API of ${myAndroidSdkApi} or higher\"\n\ndependencies {\n    api project(':minidns-client')\n    testImplementation project(path: \":minidns-client\", configuration: \"testRuntime\")\n\n\t// Add the Android jar to the Eclipse .classpath.\n\timplementation files(androidBootClasspath)\n\n\t// For AnimalSniffer\n\tsignature \"net.sf.androidscents.signature:android-api-level-${myAndroidSdkApi}:6.0_r3@signature\"\n}\n"
  },
  {
    "path": "minidns-android23/src/main/java/org/minidns/dnsserverlookup/android21/AndroidUsingLinkProperties.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsserverlookup.android21;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.net.ConnectivityManager;\nimport android.net.LinkProperties;\nimport android.net.Network;\nimport android.net.RouteInfo;\nimport android.os.Build;\n\nimport java.net.InetAddress;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.minidns.DnsClient;\nimport org.minidns.dnsserverlookup.AbstractDnsServerLookupMechanism;\nimport org.minidns.dnsserverlookup.AndroidUsingExec;\n\n/**\n * A DNS server lookup mechanism using Android's Link Properties method available on Android API 21 or higher. Use\n * {@link #setup(Context)} to setup this mechanism.\n * <p>\n * Requires the ACCESS_NETWORK_STATE permission.\n * </p>\n */\npublic class AndroidUsingLinkProperties extends AbstractDnsServerLookupMechanism {\n\n    private final ConnectivityManager connectivityManager;\n\n    /**\n     * Setup this DNS server lookup mechanism. You need to invoke this method only once, ideally before you do your\n     * first DNS lookup.\n     *\n     * @param context a Context instance.\n     * @return the instance of the newly setup mechanism\n     */\n    public static AndroidUsingLinkProperties setup(Context context) {\n        AndroidUsingLinkProperties androidUsingLinkProperties = new AndroidUsingLinkProperties(context);\n        DnsClient.addDnsServerLookupMechanism(androidUsingLinkProperties);\n        return androidUsingLinkProperties;\n    }\n\n    /**\n     * Construct this DNS server lookup mechanism.\n     *\n     * @param context an Android context.\n     */\n    public AndroidUsingLinkProperties(Context context) {\n        super(AndroidUsingLinkProperties.class.getSimpleName(), AndroidUsingExec.PRIORITY - 1);\n        connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;\n    }\n\n    @TargetApi(Build.VERSION_CODES.M)\n    private List<String> getDnsServerAddressesOfActiveNetwork() {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            return null;\n        }\n\n        // ConnectivityManager.getActiveNetwork() is API 23.\n        Network activeNetwork = connectivityManager.getActiveNetwork();\n        if (activeNetwork == null) {\n            return null;\n        }\n\n        LinkProperties linkProperties = connectivityManager.getLinkProperties(activeNetwork);\n        if (linkProperties == null) {\n            return null;\n        }\n\n        List<InetAddress> dnsServers = linkProperties.getDnsServers();\n        return toListOfStrings(dnsServers);\n    }\n\n    @Override\n    @TargetApi(21)\n    public List<String> getDnsServerAddresses() {\n        // First, try the API 23 approach using ConnectivityManager.getActiveNetwork().\n        List<String> servers = getDnsServerAddressesOfActiveNetwork();\n        if (servers != null) {\n            return servers;\n        }\n\n        Network[] networks = connectivityManager.getAllNetworks();\n        if (networks == null) {\n            return null;\n        }\n\n        servers = new ArrayList<>(networks.length * 2);\n        for (Network network : networks) {\n            LinkProperties linkProperties = connectivityManager.getLinkProperties(network);\n            if (linkProperties == null) {\n                continue;\n            }\n\n            // Prioritize the DNS servers of links which have a default route\n            if (hasDefaultRoute(linkProperties)) {\n                servers.addAll(0, toListOfStrings(linkProperties.getDnsServers()));\n            } else {\n                servers.addAll(toListOfStrings(linkProperties.getDnsServers()));\n            }\n        }\n\n        if (servers.isEmpty()) {\n            return null;\n        }\n\n        return servers;\n    }\n\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    private static boolean hasDefaultRoute(LinkProperties linkProperties) {\n        for (RouteInfo route : linkProperties.getRoutes()) {\n            if (route.isDefaultRoute()) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "minidns-android23/src/test/java/org/minidns/dnsserverlookup/android21/AndroidUsingLinkPropertiesTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsserverlookup.android21;\n\nimport org.junit.jupiter.api.Test;\n\npublic class AndroidUsingLinkPropertiesTest {\n\n    /**\n     * Dummy test to make jacocoRootReport happy.\n     */\n    @Test\n    public void nopTest() {\n    }\n\n}\n"
  },
  {
    "path": "minidns-async/build.gradle",
    "content": "plugins {\n\tid 'org.minidns.java-conventions'\n\tid 'org.minidns.android-conventions'\n}\n\ndescription = \"Asynchronous DNS lookups using Java NIO\"\n\ndependencies {\n    api project(':minidns-client')\n    testImplementation project(path: \":minidns-client\", configuration: \"testRuntime\")\n}\n"
  },
  {
    "path": "minidns-async/src/main/java/org/minidns/source/async/AsyncDnsRequest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.source.async;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.nio.ByteBuffer;\nimport java.nio.channels.Channel;\nimport java.nio.channels.ClosedChannelException;\nimport java.nio.channels.DatagramChannel;\nimport java.nio.channels.SelectableChannel;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.SocketChannel;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.Future;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\nimport org.minidns.MiniDnsException;\nimport org.minidns.MiniDnsFuture;\nimport org.minidns.MiniDnsFuture.InternalMiniDnsFuture;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.dnsqueryresult.DnsQueryResult.QueryMethod;\nimport org.minidns.dnsqueryresult.StandardDnsQueryResult;\nimport org.minidns.source.DnsDataSource.OnResponseCallback;\nimport org.minidns.source.AbstractDnsDataSource.QueryMode;\nimport org.minidns.util.MultipleIoException;\n\n/**\n * A DNS request that is performed asynchronously.\n */\npublic class AsyncDnsRequest {\n\n    private static final Logger LOGGER = Logger.getLogger(AsyncDnsRequest.class.getName());\n\n    private final InternalMiniDnsFuture<DnsQueryResult, IOException> future = new InternalMiniDnsFuture<DnsQueryResult, IOException>() {\n        @SuppressWarnings(\"UnsynchronizedOverridesSynchronized\")\n        @Override\n        public boolean cancel(boolean mayInterruptIfRunning) {\n            boolean res = super.cancel(mayInterruptIfRunning);\n            cancelAsyncDnsRequest();\n            return res;\n        }\n    };\n\n    private final DnsMessage request;\n\n    private final int udpPayloadSize;\n\n    private final InetSocketAddress socketAddress;\n\n    private final AsyncNetworkDataSource asyncNds;\n\n    private final OnResponseCallback onResponseCallback;\n\n    private final boolean skipUdp;\n\n    private ByteBuffer writeBuffer;\n\n    private List<IOException> exceptions;\n\n    private SelectionKey selectionKey;\n\n    final long deadline;\n\n    /**\n     * Creates a new AsyncDnsRequest instance.\n     *\n     * @param request the DNS message of the request.\n     * @param inetAddress The IP address of the DNS server to ask.\n     * @param port The port of the DNS server to ask.\n     * @param udpPayloadSize The configured UDP payload size.\n     * @param asyncNds A reference to the {@link AsyncNetworkDataSource} instance manageing the requests.\n     * @param onResponseCallback the optional callback when a response was received.\n     */\n    AsyncDnsRequest(DnsMessage request, InetAddress inetAddress, int port, int udpPayloadSize, AsyncNetworkDataSource asyncNds, OnResponseCallback onResponseCallback) {\n        this.request = request;\n        this.udpPayloadSize = udpPayloadSize;\n        this.asyncNds = asyncNds;\n        this.onResponseCallback = onResponseCallback;\n\n        final QueryMode queryMode = asyncNds.getQueryMode();\n        switch (queryMode) {\n        case dontCare:\n        case udpTcp:\n            skipUdp = false;\n            break;\n        case tcp:\n            skipUdp = true;\n            break;\n        default:\n            throw new IllegalStateException(\"Unsupported query mode: \" + queryMode);\n\n        }\n        deadline = System.currentTimeMillis() + asyncNds.getTimeout();\n        socketAddress = new InetSocketAddress(inetAddress, port);\n    }\n\n    private void ensureWriteBufferIsInitialized() {\n        if (writeBuffer != null) {\n            if (!writeBuffer.hasRemaining()) {\n                ((java.nio.Buffer) writeBuffer).rewind();\n            }\n            return;\n        }\n        writeBuffer = request.getInByteBuffer();\n    }\n\n    private synchronized void cancelAsyncDnsRequest() {\n        if (selectionKey != null) {\n            selectionKey.cancel();\n        }\n        asyncNds.cancelled(this);\n    }\n\n    private synchronized void registerWithSelector(SelectableChannel channel, int ops, ChannelSelectedHandler handler)\n            throws ClosedChannelException {\n        if (future.isCancelled()) {\n            return;\n        }\n        selectionKey = asyncNds.registerWithSelector(channel, ops, handler);\n    }\n\n    private void addException(IOException e) {\n        if (exceptions == null) {\n            exceptions = new ArrayList<>(4);\n        }\n        exceptions.add(e);\n    }\n\n    private void gotResult(DnsQueryResult result) {\n        if (onResponseCallback != null) {\n            onResponseCallback.onResponse(request, result);\n        }\n        asyncNds.finished(this);\n        future.setResult(result);\n    }\n\n    MiniDnsFuture<DnsQueryResult, IOException> getFuture() {\n        return future;\n    }\n\n    boolean wasDeadlineMissedAndFutureNotified() {\n        if (System.currentTimeMillis() < deadline) {\n            return false;\n        }\n\n        future.setException(new IOException(\"Timeout\"));\n        return true;\n    }\n\n    void startHandling() {\n        if (!skipUdp) {\n            startUdpRequest();\n        } else {\n            startTcpRequest();\n        }\n    }\n\n    private void abortRequestAndCleanup(Channel channel, String errorMessage, IOException exception) {\n        if (exception == null) {\n            // TODO: Can this case be removed? Is 'exception' ever null?\n            LOGGER.info(\"Exception was null in abortRequestAndCleanup()\");\n            exception = new IOException(errorMessage);\n        }\n        LOGGER.log(Level.SEVERE, \"Error connecting \" + channel + \": \" + errorMessage, exception);\n        addException(exception);\n\n        if (selectionKey != null) {\n            selectionKey.cancel();\n        }\n\n        if (channel != null && channel.isOpen()) {\n            try {\n                channel.close();\n            } catch (IOException e) {\n                LOGGER.log(Level.SEVERE, \"Exception closing socket channel\", e);\n                addException(e);\n            }\n        }\n    }\n\n    private void abortUdpRequestAndCleanup(DatagramChannel datagramChannel, String errorMessage, IOException exception) {\n        abortRequestAndCleanup(datagramChannel, errorMessage, exception);\n        startTcpRequest();\n    }\n\n    private void startUdpRequest() {\n        if (future.isCancelled()) {\n            return;\n        }\n\n        DatagramChannel datagramChannel;\n        try {\n            datagramChannel = DatagramChannel.open();\n        } catch (IOException e) {\n            LOGGER.log(Level.SEVERE, \"Exception opening datagram channel\", e);\n            addException(e);\n            startTcpRequest();\n            return;\n        }\n\n        try {\n            datagramChannel.configureBlocking(false);\n        } catch (IOException e) {\n            abortUdpRequestAndCleanup(datagramChannel, \"Exception configuring datagram channel\", e);\n            return;\n        }\n\n        try {\n            datagramChannel.connect(socketAddress);\n        } catch (IOException e) {\n            abortUdpRequestAndCleanup(datagramChannel, \"Exception connecting datagram channel to \" + socketAddress, e);\n            return;\n        }\n\n        try {\n            registerWithSelector(datagramChannel, SelectionKey.OP_WRITE, new UdpWritableChannelSelectedHandler(future));\n        } catch (ClosedChannelException e) {\n            abortUdpRequestAndCleanup(datagramChannel, \"Exception registering datagram channel for OP_WRITE\", e);\n            return;\n        }\n    }\n\n    class UdpWritableChannelSelectedHandler extends ChannelSelectedHandler {\n\n        UdpWritableChannelSelectedHandler(Future<?> future) {\n            super(future);\n        }\n\n        @Override\n        public void handleChannelSelectedAndNotCancelled(SelectableChannel channel, SelectionKey selectionKey) {\n            DatagramChannel datagramChannel = (DatagramChannel) channel;\n\n            ensureWriteBufferIsInitialized();\n\n            try {\n                datagramChannel.write(writeBuffer);\n            } catch (IOException e) {\n                abortUdpRequestAndCleanup(datagramChannel, \"Exception writing to datagram channel\", e);\n                return;\n            }\n\n            if (writeBuffer.hasRemaining()) {\n                try {\n                    registerWithSelector(datagramChannel, SelectionKey.OP_WRITE, this);\n                } catch (ClosedChannelException e) {\n                    abortUdpRequestAndCleanup(datagramChannel, \"Exception registering datagram channel for OP_WRITE\", e);\n                }\n                return;\n            }\n\n            try {\n                registerWithSelector(datagramChannel, SelectionKey.OP_READ, new UdpReadableChannelSelectedHandler(future));\n            } catch (ClosedChannelException e) {\n                abortUdpRequestAndCleanup(datagramChannel, \"Exception registering datagram channel for OP_READ\", e);\n                return;\n            }\n        }\n\n    }\n\n    class UdpReadableChannelSelectedHandler extends ChannelSelectedHandler {\n\n        UdpReadableChannelSelectedHandler(Future<?> future) {\n            super(future);\n        }\n\n        final ByteBuffer byteBuffer = ByteBuffer.allocate(udpPayloadSize);\n\n        @Override\n        public void handleChannelSelectedAndNotCancelled(SelectableChannel channel, SelectionKey selectionKey) {\n            DatagramChannel datagramChannel = (DatagramChannel) channel;\n\n            try {\n                datagramChannel.read(byteBuffer);\n            } catch (IOException e) {\n                abortUdpRequestAndCleanup(datagramChannel, \"Exception reading from datagram channel\", e);\n                return;\n            }\n\n            selectionKey.cancel();\n            try {\n                datagramChannel.close();\n            } catch (IOException e) {\n                LOGGER.log(Level.SEVERE, \"Exception closing datagram channel\", e);\n                addException(e);\n            }\n\n            DnsMessage response;\n            try {\n                response = new DnsMessage(byteBuffer.array());\n            } catch (IOException e) {\n                abortUdpRequestAndCleanup(datagramChannel, \"Exception constructing dns message from datagram channel\", e);\n                return;\n            }\n\n            if (response.id != request.id) {\n                addException(new MiniDnsException.IdMismatch(request, response));\n                startTcpRequest();\n                return;\n            }\n\n            if (response.truncated) {\n                startTcpRequest();\n                return;\n            }\n\n            DnsQueryResult result = new StandardDnsQueryResult(socketAddress.getAddress(), socketAddress.getPort(),\n                    QueryMethod.asyncUdp, request, response);\n            gotResult(result);\n        }\n    }\n\n    private void abortTcpRequestAndCleanup(SocketChannel socketChannel, String errorMessage, IOException exception) {\n        abortRequestAndCleanup(socketChannel, errorMessage, exception);\n        future.setException(MultipleIoException.toIOException(exceptions));\n    }\n\n    private void startTcpRequest() {\n        SocketChannel socketChannel = null;\n        try {\n            socketChannel = SocketChannel.open();\n        } catch (IOException e) {\n            abortTcpRequestAndCleanup(socketChannel, \"Exception opening socket channel\", e);\n            return;\n        }\n\n        try {\n            socketChannel.configureBlocking(false);\n        } catch (IOException e) {\n            abortTcpRequestAndCleanup(socketChannel, \"Exception configuring socket channel\", e);\n            return;\n        }\n\n        try {\n            registerWithSelector(socketChannel, SelectionKey.OP_CONNECT, new TcpConnectedChannelSelectedHandler(future));\n        } catch (ClosedChannelException e) {\n            abortTcpRequestAndCleanup(socketChannel, \"Exception registering socket channel\", e);\n            return;\n        }\n\n        try {\n            socketChannel.connect(socketAddress);\n        } catch (IOException e) {\n            abortTcpRequestAndCleanup(socketChannel, \"Exception connecting socket channel to \" + socketAddress, e);\n            return;\n        }\n    }\n\n    class TcpConnectedChannelSelectedHandler extends ChannelSelectedHandler {\n\n        TcpConnectedChannelSelectedHandler(Future<?> future) {\n            super(future);\n        }\n\n        @Override\n        public void handleChannelSelectedAndNotCancelled(SelectableChannel channel, SelectionKey selectionKey) {\n            SocketChannel socketChannel = (SocketChannel) channel;\n\n            boolean connected;\n            try {\n                connected = socketChannel.finishConnect();\n            } catch (IOException e) {\n                abortTcpRequestAndCleanup(socketChannel, \"Exception finish connecting socket channel\", e);\n                return;\n            }\n\n            assert connected;\n\n            try {\n                registerWithSelector(socketChannel, SelectionKey.OP_WRITE, new TcpWritableChannelSelectedHandler(future));\n            } catch (ClosedChannelException e) {\n                abortTcpRequestAndCleanup(socketChannel, \"Exception registering socket channel for OP_WRITE\", e);\n                return;\n            }\n        }\n\n    }\n\n    class TcpWritableChannelSelectedHandler extends ChannelSelectedHandler {\n\n        TcpWritableChannelSelectedHandler(Future<?> future) {\n            super(future);\n        }\n\n        /**\n         * ByteBuffer array of length 2. First buffer is for the length of the DNS message, second one is the actual DNS message.\n         */\n        private ByteBuffer[] writeBuffers;\n\n        @Override\n        public void handleChannelSelectedAndNotCancelled(SelectableChannel channel, SelectionKey selectionKey) {\n            SocketChannel socketChannel = (SocketChannel) channel;\n\n            if (writeBuffers == null) {\n                ensureWriteBufferIsInitialized();\n\n                ByteBuffer messageLengthByteBuffer = ByteBuffer.allocate(2);\n                int messageLength = writeBuffer.capacity();\n                assert messageLength <= Short.MAX_VALUE;\n                messageLengthByteBuffer.putShort((short) (messageLength & 0xffff));\n                ((java.nio.Buffer) messageLengthByteBuffer).rewind();\n\n                writeBuffers = new ByteBuffer[2];\n                writeBuffers[0] = messageLengthByteBuffer;\n                writeBuffers[1] = writeBuffer;\n            }\n\n            try {\n                socketChannel.write(writeBuffers);\n            } catch (IOException e) {\n                abortTcpRequestAndCleanup(socketChannel, \"Exception writing to socket channel\", e);\n                return;\n            }\n\n            if (moreToWrite()) {\n                try {\n                    registerWithSelector(socketChannel, SelectionKey.OP_WRITE, this);\n                } catch (ClosedChannelException e) {\n                    abortTcpRequestAndCleanup(socketChannel, \"Exception registering socket channel for OP_WRITE\", e);\n                }\n                return;\n            }\n\n            try {\n                registerWithSelector(socketChannel, SelectionKey.OP_READ, new TcpReadableChannelSelectedHandler(future));\n            } catch (ClosedChannelException e) {\n                abortTcpRequestAndCleanup(socketChannel, \"Exception registering socket channel for OP_READ\", e);\n                return;\n            }\n        }\n\n        private boolean moreToWrite() {\n            for (int i = 0; i < writeBuffers.length; i++) {\n                if (writeBuffers[i].hasRemaining()) {\n                    return true;\n                }\n            }\n            return false;\n        }\n    }\n\n    class TcpReadableChannelSelectedHandler extends ChannelSelectedHandler {\n\n        TcpReadableChannelSelectedHandler(Future<?> future) {\n            super(future);\n        }\n\n        final ByteBuffer messageLengthByteBuffer = ByteBuffer.allocate(2);\n\n        ByteBuffer byteBuffer;\n\n        @Override\n        public void handleChannelSelectedAndNotCancelled(SelectableChannel channel, SelectionKey selectionKey) {\n            SocketChannel socketChannel = (SocketChannel) channel;\n\n            int bytesRead;\n            if (byteBuffer == null) {\n                try {\n                    bytesRead = socketChannel.read(messageLengthByteBuffer);\n                } catch (IOException e) {\n                    abortTcpRequestAndCleanup(socketChannel, \"Exception reading from socket channel\", e);\n                    return;\n                }\n\n                if (bytesRead < 0) {\n                    abortTcpRequestAndCleanup(socketChannel, \"Socket closed by remote host \" + socketAddress, null);\n                    return;\n                }\n\n                if (messageLengthByteBuffer.hasRemaining()) {\n                    try {\n                        registerWithSelector(socketChannel, SelectionKey.OP_READ, this);\n                    } catch (ClosedChannelException e) {\n                        abortTcpRequestAndCleanup(socketChannel, \"Exception registering socket channel for OP_READ\", e);\n                    }\n                    return;\n                }\n\n                ((java.nio.Buffer) messageLengthByteBuffer).rewind();\n                short messageLengthSignedShort = messageLengthByteBuffer.getShort();\n                int messageLength = messageLengthSignedShort & 0xffff;\n                byteBuffer = ByteBuffer.allocate(messageLength);\n            }\n\n            try {\n                bytesRead = socketChannel.read(byteBuffer);\n            } catch (IOException e) {\n                throw new Error(e);\n            }\n\n            if (bytesRead < 0) {\n                abortTcpRequestAndCleanup(socketChannel, \"Socket closed by remote host \" + socketAddress, null);\n                return;\n            }\n\n            if (byteBuffer.hasRemaining()) {\n                try {\n                    registerWithSelector(socketChannel, SelectionKey.OP_READ, this);\n                } catch (ClosedChannelException e) {\n                    abortTcpRequestAndCleanup(socketChannel, \"Exception registering socket channel for OP_READ\", e);\n                }\n                return;\n            }\n\n            selectionKey.cancel();\n            try {\n                socketChannel.close();\n            } catch (IOException e) {\n                addException(e);\n            }\n\n            DnsMessage response;\n            try {\n                response = new DnsMessage(byteBuffer.array());\n            } catch (IOException e) {\n                abortTcpRequestAndCleanup(socketChannel, \"Exception creating DNS message form socket channel bytes\", e);\n                return;\n            }\n\n            if (request.id != response.id) {\n                MiniDnsException idMismatchException = new MiniDnsException.IdMismatch(request, response);\n                addException(idMismatchException);\n                AsyncDnsRequest.this.future.setException(MultipleIoException.toIOException(exceptions));\n                return;\n            }\n\n            DnsQueryResult result = new StandardDnsQueryResult(socketAddress.getAddress(), socketAddress.getPort(),\n                    QueryMethod.asyncTcp, request, response);\n            gotResult(result);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "minidns-async/src/main/java/org/minidns/source/async/AsyncNetworkDataSource.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.source.async;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.nio.channels.ClosedChannelException;\nimport java.nio.channels.SelectableChannel;\nimport java.nio.channels.SelectionKey;\nimport java.nio.channels.Selector;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.PriorityQueue;\nimport java.util.Queue;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentLinkedQueue;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\nimport org.minidns.MiniDnsFuture;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.source.AbstractDnsDataSource;\n\n/**\n * A DNS data sources that resolves requests via the network asynchronously.\n */\npublic class AsyncNetworkDataSource extends AbstractDnsDataSource {\n\n    /**\n     * The logger of this data source.\n     */\n    protected static final Logger LOGGER = Logger.getLogger(AsyncNetworkDataSource.class.getName());\n\n    private static final int REACTOR_THREAD_COUNT = 1;\n\n    private static final Queue<AsyncDnsRequest> INCOMING_REQUESTS = new ConcurrentLinkedQueue<>();\n\n    private static final Selector SELECTOR;\n\n    private static final Lock REGISTRATION_LOCK = new ReentrantLock();\n\n    private static final Queue<SelectionKey> PENDING_SELECTION_KEYS = new ConcurrentLinkedQueue<>();\n\n    private static final Thread[] REACTOR_THREADS = new Thread[REACTOR_THREAD_COUNT];\n\n    private static final PriorityQueue<AsyncDnsRequest> DEADLINE_QUEUE = new PriorityQueue<>(16, new Comparator<AsyncDnsRequest>() {\n        @Override\n        public int compare(AsyncDnsRequest o1, AsyncDnsRequest o2) {\n            if (o1.deadline > o2.deadline) {\n                return 1;\n            } else if (o1.deadline < o2.deadline) {\n                return -1;\n            }\n            return 0;\n        }\n    });\n\n    static {\n        try {\n            SELECTOR = Selector.open();\n        } catch (IOException e) {\n            throw new IllegalStateException(e);\n        }\n\n        for (int i = 0; i < REACTOR_THREAD_COUNT; i++) {\n            Thread reactorThread = new Thread(new Reactor());\n            reactorThread.setDaemon(true);\n            reactorThread.setName(\"MiniDNS Reactor Thread #\" + i);\n            reactorThread.start();\n            REACTOR_THREADS[i] = reactorThread;\n        }\n    }\n\n    @Override\n    public MiniDnsFuture<DnsQueryResult, IOException> queryAsync(DnsMessage message, InetAddress address, int port, OnResponseCallback onResponseCallback) {\n        AsyncDnsRequest asyncDnsRequest = new AsyncDnsRequest(message, address, port, udpPayloadSize, this, onResponseCallback);\n        INCOMING_REQUESTS.add(asyncDnsRequest);\n        synchronized (DEADLINE_QUEUE) {\n            DEADLINE_QUEUE.add(asyncDnsRequest);\n        }\n        SELECTOR.wakeup();\n        return asyncDnsRequest.getFuture();\n    }\n\n    @Override\n    public DnsQueryResult query(DnsMessage message, InetAddress address, int port) throws IOException {\n        MiniDnsFuture<DnsQueryResult, IOException> future = queryAsync(message, address, port, null);\n        try {\n            return future.get();\n        } catch (InterruptedException e) {\n            // This should never happen.\n            throw new AssertionError(e);\n        } catch (ExecutionException e) {\n            Throwable wrappedThrowable = e.getCause();\n            if (wrappedThrowable instanceof IOException) {\n                throw (IOException) wrappedThrowable;\n            }\n            // This should never happen.\n            throw new AssertionError(e);\n        }\n    }\n\n    SelectionKey registerWithSelector(SelectableChannel channel, int ops, Object attachment) throws ClosedChannelException {\n        REGISTRATION_LOCK.lock();\n        try {\n            SELECTOR.wakeup();\n            return channel.register(SELECTOR, ops, attachment);\n        } finally {\n            REGISTRATION_LOCK.unlock();\n        }\n    }\n\n    void finished(AsyncDnsRequest asyncDnsRequest) {\n        synchronized (DEADLINE_QUEUE) {\n            DEADLINE_QUEUE.remove(asyncDnsRequest);\n        }\n    }\n\n    void cancelled(AsyncDnsRequest asyncDnsRequest) {\n        finished(asyncDnsRequest);\n        // Wakeup since the async DNS request was removed from the deadline queue.\n        SELECTOR.wakeup();\n    }\n\n    private static final class Reactor implements Runnable {\n        @Override\n        public void run() {\n            while (!Thread.interrupted()) {\n                Collection<SelectionKey> mySelectedKeys = performSelect();\n                handleSelectedKeys(mySelectedKeys);\n\n                handlePendingSelectionKeys();\n\n                handleIncomingRequests();\n            }\n        }\n\n        private static void handleSelectedKeys(Collection<SelectionKey> selectedKeys) {\n            for (SelectionKey selectionKey : selectedKeys) {\n                ChannelSelectedHandler channelSelectedHandler = (ChannelSelectedHandler) selectionKey.attachment();\n                SelectableChannel channel = selectionKey.channel();\n                channelSelectedHandler.handleChannelSelected(channel, selectionKey);\n            }\n        }\n\n        @SuppressWarnings({\"LockNotBeforeTry\", \"MixedMutabilityReturnType\"})\n        private static Collection<SelectionKey> performSelect() {\n            AsyncDnsRequest nearestDeadline = null;\n            AsyncDnsRequest nextInQueue;\n\n            synchronized (DEADLINE_QUEUE) {\n                while ((nextInQueue = DEADLINE_QUEUE.peek()) != null) {\n                    if (nextInQueue.wasDeadlineMissedAndFutureNotified()) {\n                        // We notified the future, associated with the AsyncDnsRequest nearestDeadline,\n                        // that the deadline has passed, hence remove it from the queue.\n                        DEADLINE_QUEUE.poll();\n                    } else {\n                        // We found a nearest deadline that has not yet passed, break out of the loop.\n                        nearestDeadline = nextInQueue;\n                        break;\n                    }\n                }\n\n            }\n\n            long selectWait;\n            if (nearestDeadline == null) {\n                // There is no deadline, wait indefinitely in select().\n                selectWait = 0;\n            } else {\n                // There is a deadline in the future, only block in select() until the deadline.\n                selectWait = nextInQueue.deadline - System.currentTimeMillis();\n                if (selectWait < 0) {\n                    // We already have a missed deadline. Do not call select() and handle the tasks which are past their\n                    // deadline.\n                    return Collections.emptyList();\n                }\n            }\n\n            List<SelectionKey> selectedKeys;\n            int newSelectedKeysCount;\n            synchronized (SELECTOR) {\n                // Ensure that a wakeup() in registerWithSelector() gives the corresponding\n                // register() in the same method the chance to actually register the channel. In\n                // other words: This construct ensure that there is never another select()\n                // between a corresponding wakeup() and register() calls.\n                // See also https://stackoverflow.com/a/1112809/194894\n                REGISTRATION_LOCK.lock();\n                REGISTRATION_LOCK.unlock();\n\n                try {\n                    newSelectedKeysCount = SELECTOR.select(selectWait);\n                } catch (IOException e) {\n                    LOGGER.log(Level.WARNING, \"IOException while using select()\", e);\n                    return Collections.emptyList();\n                }\n\n                if (newSelectedKeysCount == 0) {\n                    return Collections.emptyList();\n                }\n\n                Set<SelectionKey> selectedKeySet = SELECTOR.selectedKeys();\n                for (SelectionKey selectionKey : selectedKeySet) {\n                    selectionKey.interestOps(0);\n                }\n\n                selectedKeys = new ArrayList<>(selectedKeySet.size());\n                selectedKeys.addAll(selectedKeySet);\n                selectedKeySet.clear();\n            }\n\n            int selectedKeysCount = selectedKeys.size();\n\n            final Level LOG_LEVEL = Level.FINER;\n            if (LOGGER.isLoggable(LOG_LEVEL)) {\n                LOGGER.log(LOG_LEVEL, \"New selected key count: \" + newSelectedKeysCount + \". Total selected key count \"\n                        + selectedKeysCount);\n            }\n\n            int myKeyCount = selectedKeysCount / REACTOR_THREAD_COUNT;\n            Collection<SelectionKey> mySelectedKeys = new ArrayList<>(myKeyCount);\n            Iterator<SelectionKey> it = selectedKeys.iterator();\n            for (int i = 0; i < myKeyCount; i++) {\n                SelectionKey selectionKey = it.next();\n                mySelectedKeys.add(selectionKey);\n            }\n            while (it.hasNext()) {\n                // Drain to PENDING_SELECTION_KEYS\n                SelectionKey selectionKey = it.next();\n                PENDING_SELECTION_KEYS.add(selectionKey);\n            }\n            return mySelectedKeys;\n        }\n\n        private static void handlePendingSelectionKeys() {\n            int pendingSelectionKeysSize = PENDING_SELECTION_KEYS.size();\n            if (pendingSelectionKeysSize == 0) {\n                return;\n            }\n\n            int myKeyCount = pendingSelectionKeysSize / REACTOR_THREAD_COUNT;\n            Collection<SelectionKey> selectedKeys = new ArrayList<>(myKeyCount);\n            for (int i = 0; i < myKeyCount; i++) {\n                SelectionKey selectionKey = PENDING_SELECTION_KEYS.poll();\n                if (selectionKey == null) {\n                    // We lost a race :)\n                    break;\n                }\n                selectedKeys.add(selectionKey);\n            }\n\n            if (!PENDING_SELECTION_KEYS.isEmpty()) {\n                // There is more work in the pending selection keys queue, wakeup another thread to handle it.\n                SELECTOR.wakeup();\n            }\n\n            handleSelectedKeys(selectedKeys);\n        }\n\n        private static void handleIncomingRequests() {\n            int incomingRequestsSize = INCOMING_REQUESTS.size();\n            if (incomingRequestsSize == 0) {\n                return;\n            }\n\n            int myRequestsCount = incomingRequestsSize / REACTOR_THREAD_COUNT;\n            // The division could result in myRequestCount being zero despite pending incoming\n            // requests. Therefore, ensure this thread tries to get at least one incoming\n            // request by invoking poll(). Otherwise, we might end up in a busy loop\n            // where myRequestCount is zero, and this thread invokes a selector.wakeup() below\n            // because incomingRequestsSize is not empty, but the woken-up reactor thread\n            // will end up with myRequestCount being zero again, restarting the busy-loop cycle.\n            if (myRequestsCount == 0) myRequestsCount = 1;\n            Collection<AsyncDnsRequest> requests = new ArrayList<>(myRequestsCount);\n            for (int i = 0; i < myRequestsCount; i++) {\n                AsyncDnsRequest asyncDnsRequest = INCOMING_REQUESTS.poll();\n                if (asyncDnsRequest == null) {\n                    // We lost a race :)\n                    break;\n                }\n                requests.add(asyncDnsRequest);\n            }\n\n            if (!INCOMING_REQUESTS.isEmpty()) {\n                SELECTOR.wakeup();\n            }\n\n            for (AsyncDnsRequest asyncDnsRequest : requests) {\n                asyncDnsRequest.startHandling();\n            }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "minidns-async/src/main/java/org/minidns/source/async/ChannelSelectedHandler.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.source.async;\n\nimport java.io.IOException;\nimport java.nio.channels.SelectableChannel;\nimport java.nio.channels.SelectionKey;\nimport java.util.concurrent.Future;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\nabstract class ChannelSelectedHandler {\n\n    private static final Logger LOGGER = Logger.getLogger(ChannelSelectedHandler.class.getName());\n\n    final Future<?> future;\n\n    ChannelSelectedHandler(Future<?> future) {\n        this.future = future;\n    }\n\n    void handleChannelSelected(SelectableChannel channel, SelectionKey selectionKey) {\n        if (future.isCancelled()) {\n            try {\n                channel.close();\n            } catch (IOException e) {\n                LOGGER.log(Level.INFO, \"Could not close channel\", e);\n            }\n            return;\n        }\n        handleChannelSelectedAndNotCancelled(channel, selectionKey);\n    }\n\n    protected abstract void handleChannelSelectedAndNotCancelled(SelectableChannel channel, SelectionKey selectionKey);\n\n}\n"
  },
  {
    "path": "minidns-async/src/test/java/org/minidns/source/async/AsyncNetworkDataSourceTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.source.async;\n\nimport org.junit.jupiter.api.Test;\n\npublic class AsyncNetworkDataSourceTest {\n\n    /**\n     * Dummy test to make jacocoRootReport happy.\n     */\n    @Test\n    public void nopTest() {\n    }\n\n}\n"
  },
  {
    "path": "minidns-client/build.gradle",
    "content": "plugins {\n\tid 'org.minidns.java-conventions'\n\tid 'org.minidns.android-conventions'\n}\n\ndescription = \"A small non-recursing DNS client\"\n\ndependencies {\n\tapi project(':minidns-core')\n    testImplementation project(path: \":minidns-core\", configuration: \"testRuntime\")\n}\n\njar {\n\tbundle {\n\t\tbnd(\n\t\t\t// minidns-client invokes Class.forName(\"android.os.…\")\n\t\t\t// which causes OSGi to import android.os, because OSGi's\n\t\t\t// bnd scans for `Class.forName` usages per default.\n\t\t\t// See also https://github.com/openhab/openhab-addons/pull/11670#issuecomment-982539016\n\t\t\t'-noclassforname': 'true',\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/AbstractDnsClient.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns;\n\nimport org.minidns.MiniDnsFuture.InternalMiniDnsFuture;\nimport org.minidns.cache.LruCache;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.record.A;\nimport org.minidns.record.AAAA;\nimport org.minidns.record.Data;\nimport org.minidns.record.NS;\nimport org.minidns.record.Record;\nimport org.minidns.record.Record.CLASS;\nimport org.minidns.record.Record.TYPE;\nimport org.minidns.source.DnsDataSource;\nimport org.minidns.source.NetworkDataSource;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.SecureRandom;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Random;\nimport java.util.Set;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\n/**\n * A minimal DNS client for SRV/A/AAAA/NS and CNAME lookups, with IDN support.\n * This circumvents the missing javax.naming package on android.\n */\npublic abstract class AbstractDnsClient {\n\n    protected static final LruCache DEFAULT_CACHE = new LruCache();\n\n    protected static final Logger LOGGER = Logger.getLogger(AbstractDnsClient.class.getName());\n\n    /**\n     * This callback is used by the synchronous query() method <b>and</b> by the asynchronous queryAync() method in order to update the\n     * cache. In the asynchronous case, hand this callback into the async call, so that it can get called once the result is available.\n     */\n    private final DnsDataSource.OnResponseCallback onResponseCallback = new DnsDataSource.OnResponseCallback() {\n        @Override\n        public void onResponse(DnsMessage requestMessage, DnsQueryResult responseMessage) {\n            final Question q = requestMessage.getQuestion();\n            if (cache != null && isResponseCacheable(q, responseMessage)) {\n                cache.put(requestMessage.asNormalizedVersion(), responseMessage);\n            }\n        }\n    };\n\n    /**\n     * The internal random class for sequence generation.\n     */\n    protected final Random random;\n\n    protected final Random insecureRandom = new Random();\n\n    /**\n     * The internal DNS cache.\n     */\n    protected final DnsCache cache;\n\n    protected DnsDataSource dataSource = new NetworkDataSource();\n\n    public enum IpVersionSetting {\n\n        v4only(true, false),\n        v6only(false, true),\n        v4v6(true, true),\n        v6v4(true, true),\n        ;\n\n        public final boolean v4;\n        public final boolean v6;\n\n        IpVersionSetting(boolean v4, boolean v6) {\n            this.v4 = v4;\n            this.v6 = v6;\n        }\n\n    }\n\n    protected static IpVersionSetting DEFAULT_IP_VERSION_SETTING = IpVersionSetting.v4v6;\n\n    public static void setDefaultIpVersion(IpVersionSetting preferedIpVersion) {\n        if (preferedIpVersion == null) {\n            throw new IllegalArgumentException();\n        }\n        AbstractDnsClient.DEFAULT_IP_VERSION_SETTING = preferedIpVersion;\n    }\n\n    protected IpVersionSetting ipVersionSetting = DEFAULT_IP_VERSION_SETTING;\n\n    public void setPreferedIpVersion(IpVersionSetting preferedIpVersion) {\n        if (preferedIpVersion == null) {\n            throw new IllegalArgumentException();\n        }\n        ipVersionSetting = preferedIpVersion;\n    }\n\n    public IpVersionSetting getPreferedIpVersion() {\n        return ipVersionSetting;\n    }\n\n    /**\n     * Create a new DNS client with the given DNS cache.\n     *\n     * @param cache The backend DNS cache.\n     */\n    protected AbstractDnsClient(DnsCache cache) {\n        Random random;\n        try {\n            random = SecureRandom.getInstance(\"SHA1PRNG\");\n        } catch (NoSuchAlgorithmException e1) {\n            random = new SecureRandom();\n        }\n        this.random = random;\n        this.cache = cache;\n    }\n\n    /**\n     * Create a new DNS client using the global default cache.\n     */\n    protected AbstractDnsClient() {\n        this(DEFAULT_CACHE);\n    }\n\n    /**\n     * Query the system nameservers for a single entry of any class.\n     *\n     * This can be used to determine the name server version, if name\n     * is version.bind, type is TYPE.TXT and clazz is CLASS.CH.\n     *\n     * @param name  The DNS name to request.\n     * @param type  The DNS type to request (SRV, A, AAAA, ...).\n     * @param clazz The class of the request (usually IN for Internet).\n     * @return The response (or null on timeout/error).\n     * @throws IOException if an IO error occurs.\n     */\n    public final DnsQueryResult query(String name, TYPE type, CLASS clazz) throws IOException {\n        Question q = new Question(name, type, clazz);\n        return query(q);\n    }\n\n    /**\n     * Query the system nameservers for a single entry of the class IN\n     * (which is used for MX, SRV, A, AAAA and most other RRs).\n     *\n     * @param name The DNS name to request.\n     * @param type The DNS type to request (SRV, A, AAAA, ...).\n     * @return The response (or null on timeout/error).\n     * @throws IOException if an IO error occurs.\n     */\n    public final DnsQueryResult query(DnsName name, TYPE type) throws IOException {\n        Question q = new Question(name, type, CLASS.IN);\n        return query(q);\n    }\n\n    /**\n     * Query the system nameservers for a single entry of the class IN\n     * (which is used for MX, SRV, A, AAAA and most other RRs).\n     *\n     * @param name The DNS name to request.\n     * @param type The DNS type to request (SRV, A, AAAA, ...).\n     * @return The response (or null on timeout/error).\n     * @throws IOException if an IO error occurs.\n     */\n    public final DnsQueryResult query(CharSequence name, TYPE type) throws IOException {\n        Question q = new Question(name, type, CLASS.IN);\n        return query(q);\n    }\n\n    public DnsQueryResult query(Question q) throws IOException {\n        DnsMessage.Builder query = buildMessage(q);\n        return query(query);\n    }\n\n    /**\n     * Send a query request to the DNS system.\n     *\n     * @param query The query to send to the server.\n     * @return The response (or null).\n     * @throws IOException if an IO error occurs.\n     */\n    protected abstract DnsQueryResult query(DnsMessage.Builder query) throws IOException;\n\n    public final MiniDnsFuture<DnsQueryResult, IOException> queryAsync(CharSequence name, TYPE type) {\n        Question q = new Question(name, type, CLASS.IN);\n        return queryAsync(q);\n    }\n\n    public final MiniDnsFuture<DnsQueryResult, IOException> queryAsync(Question q) {\n        DnsMessage.Builder query = buildMessage(q);\n        return queryAsync(query);\n    }\n\n    /**\n     * Default implementation of an asynchronous DNS query which just wraps the synchronous case.\n     * <p>\n     * Subclasses override this method to support true asynchronous queries.\n     * </p>\n     *\n     * @param query the query.\n     * @return a future for this query.\n     */\n    protected MiniDnsFuture<DnsQueryResult, IOException> queryAsync(DnsMessage.Builder query) {\n        InternalMiniDnsFuture<DnsQueryResult, IOException> future = new InternalMiniDnsFuture<>();\n        DnsQueryResult result;\n        try {\n            result = query(query);\n        } catch (IOException e) {\n            future.setException(e);\n            return future;\n        }\n        future.setResult(result);\n        return future;\n    }\n\n    public final DnsQueryResult query(Question q, InetAddress server, int port) throws IOException {\n        DnsMessage query = getQueryFor(q);\n        return query(query, server, port);\n    }\n\n    public final DnsQueryResult query(DnsMessage requestMessage, InetAddress address, int port) throws IOException {\n        // See if we have the answer to this question already cached\n        DnsQueryResult responseMessage = (cache == null) ? null : cache.get(requestMessage);\n        if (responseMessage != null) {\n            return responseMessage;\n        }\n\n        final Question q = requestMessage.getQuestion();\n\n        final Level TRACE_LOG_LEVEL = Level.FINE;\n        LOGGER.log(TRACE_LOG_LEVEL, \"Asking {0} on {1} for {2} with:\\n{3}\", new Object[] { address, port, q, requestMessage });\n\n        try {\n            responseMessage = dataSource.query(requestMessage, address, port);\n        } catch (IOException e) {\n            LOGGER.log(TRACE_LOG_LEVEL, \"IOException {0} on {1} while resolving {2}: {3}\", new Object[] { address, port, q, e});\n            throw e;\n        }\n\n        LOGGER.log(TRACE_LOG_LEVEL, \"Response from {0} on {1} for {2}:\\n{3}\", new Object[] { address, port, q, responseMessage });\n\n        onResponseCallback.onResponse(requestMessage, responseMessage);\n\n        return responseMessage;\n    }\n\n    public final MiniDnsFuture<DnsQueryResult, IOException> queryAsync(DnsMessage requestMessage, InetAddress address, int port) {\n        // See if we have the answer to this question already cached\n        DnsQueryResult responseMessage = (cache == null) ? null : cache.get(requestMessage);\n        if (responseMessage != null) {\n            return MiniDnsFuture.from(responseMessage);\n        }\n\n        final Question q = requestMessage.getQuestion();\n\n        final Level TRACE_LOG_LEVEL = Level.FINE;\n        LOGGER.log(TRACE_LOG_LEVEL, \"Asynchronusly asking {0} on {1} for {2} with:\\n{3}\", new Object[] { address, port, q, requestMessage });\n\n        return dataSource.queryAsync(requestMessage, address, port, onResponseCallback);\n    }\n\n    /**\n     * Whether a response from the DNS system should be cached or not.\n     *\n     * @param q          The question the response message should answer.\n     * @param result The DNS query result.\n     * @return True, if the response should be cached, false otherwise.\n     */\n    protected boolean isResponseCacheable(Question q, DnsQueryResult result) {\n        DnsMessage dnsMessage = result.response;\n        for (Record<? extends Data> record : dnsMessage.answerSection) {\n            if (record.isAnswer(q)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Builds a {@link DnsMessage} object carrying the given Question.\n     *\n     * @param question {@link Question} to be put in the DNS request.\n     * @return A {@link DnsMessage} requesting the answer for the given Question.\n     */\n    final DnsMessage.Builder buildMessage(Question question) {\n        DnsMessage.Builder message = DnsMessage.builder();\n        message.setQuestion(question);\n        message.setId(random.nextInt());\n        message = newQuestion(message);\n        return message;\n    }\n\n    protected abstract DnsMessage.Builder newQuestion(DnsMessage.Builder questionMessage);\n\n    /**\n     * Query a nameserver for a single entry.\n     *\n     * @param name    The DNS name to request.\n     * @param type    The DNS type to request (SRV, A, AAAA, ...).\n     * @param clazz   The class of the request (usually IN for Internet).\n     * @param address The DNS server address.\n     * @param port    The DNS server port.\n     * @return The response (or null on timeout / failure).\n     * @throws IOException On IO Errors.\n     */\n    public DnsQueryResult query(String name, TYPE type, CLASS clazz, InetAddress address, int port)\n            throws IOException {\n        Question q = new Question(name, type, clazz);\n        return query(q, address, port);\n    }\n\n    /**\n     * Query a nameserver for a single entry.\n     *\n     * @param name    The DNS name to request.\n     * @param type    The DNS type to request (SRV, A, AAAA, ...).\n     * @param clazz   The class of the request (usually IN for Internet).\n     * @param address The DNS server host.\n     * @return The response (or null on timeout / failure).\n     * @throws IOException On IO Errors.\n     */\n    public DnsQueryResult query(String name, TYPE type, CLASS clazz, InetAddress address)\n            throws IOException {\n        Question q = new Question(name, type, clazz);\n        return query(q, address);\n    }\n\n    /**\n     * Query a nameserver for a single entry of class IN.\n     *\n     * @param name    The DNS name to request.\n     * @param type    The DNS type to request (SRV, A, AAAA, ...).\n     * @param address The DNS server host.\n     * @return The response (or null on timeout / failure).\n     * @throws IOException On IO Errors.\n     */\n    public DnsQueryResult query(String name, TYPE type, InetAddress address)\n            throws IOException {\n        Question q = new Question(name, type, CLASS.IN);\n        return query(q, address);\n    }\n\n    public final DnsQueryResult query(DnsMessage query, InetAddress host) throws IOException {\n        return query(query, host, 53);\n    }\n\n    /**\n     * Query a specific server for one entry.\n     *\n     * @param q       The question section of the DNS query.\n     * @param address The dns server address.\n     * @return The a DNS query result.\n     * @throws IOException On IOErrors.\n     */\n    public DnsQueryResult query(Question q, InetAddress address) throws IOException {\n        return query(q, address, 53);\n    }\n\n    public final MiniDnsFuture<DnsQueryResult, IOException> queryAsync(DnsMessage query, InetAddress dnsServer) {\n        return queryAsync(query, dnsServer, 53);\n    }\n\n    /**\n     * Returns the currently used {@link DnsDataSource}. See {@link #setDataSource(DnsDataSource)} for details.\n     *\n     * @return The currently used {@link DnsDataSource}\n     */\n    public DnsDataSource getDataSource() {\n        return dataSource;\n    }\n\n    /**\n     * Set a {@link DnsDataSource} to be used by the DnsClient.\n     * The default implementation will direct all queries directly to the Internet.\n     *\n     * This can be used to define a non-default handling for outgoing data. This can be useful to redirect the requests\n     * to a proxy or to modify requests after or responses before they are handled by the DnsClient implementation.\n     *\n     * @param dataSource An implementation of DNSDataSource that shall be used.\n     */\n    public void setDataSource(DnsDataSource dataSource) {\n        if (dataSource == null) {\n            throw new IllegalArgumentException();\n        }\n        this.dataSource = dataSource;\n    }\n\n    /**\n     * Get the cache used by this DNS client.\n     *\n     * @return the cached used by this DNS client or <code>null</code>.\n     */\n    public DnsCache getCache() {\n        return cache;\n    }\n\n    protected DnsMessage getQueryFor(Question q) {\n        DnsMessage.Builder messageBuilder = buildMessage(q);\n        DnsMessage query = messageBuilder.build();\n        return query;\n    }\n\n    private <D extends Data> Set<D> getCachedRecordsFor(DnsName dnsName, TYPE type) {\n        if (cache == null)\n            return Collections.emptySet();\n\n        Question dnsNameNs = new Question(dnsName, type);\n        DnsMessage queryDnsNameNs = getQueryFor(dnsNameNs);\n        DnsQueryResult cachedResult = cache.get(queryDnsNameNs);\n\n        if (cachedResult == null)\n            return Collections.emptySet();\n\n        return cachedResult.response.getAnswersFor(dnsNameNs);\n    }\n\n    public Set<NS> getCachedNameserverRecordsFor(DnsName dnsName) {\n        return getCachedRecordsFor(dnsName, TYPE.NS);\n    }\n\n    public Set<A> getCachedIPv4AddressesFor(DnsName dnsName) {\n        return getCachedRecordsFor(dnsName, TYPE.A);\n    }\n\n    public Set<AAAA> getCachedIPv6AddressesFor(DnsName dnsName) {\n        return getCachedRecordsFor(dnsName, TYPE.AAAA);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private <D extends Data> Set<D> getCachedIPNameserverAddressesFor(DnsName dnsName, TYPE type) {\n        Set<NS> nsSet = getCachedNameserverRecordsFor(dnsName);\n        if (nsSet.isEmpty())\n            return Collections.emptySet();\n\n        Set<D> res = new HashSet<>(3 * nsSet.size());\n        for (NS ns : nsSet) {\n            Set<D> addresses;\n            switch (type) {\n            case A:\n                addresses = (Set<D>) getCachedIPv4AddressesFor(ns.target);\n                break;\n            case AAAA:\n                addresses = (Set<D>) getCachedIPv6AddressesFor(ns.target);\n                break;\n            default:\n                throw new AssertionError();\n            }\n            res.addAll(addresses);\n        }\n\n        return res;\n    }\n\n    public Set<A> getCachedIPv4NameserverAddressesFor(DnsName dnsName) {\n        return getCachedIPNameserverAddressesFor(dnsName, TYPE.A);\n    }\n\n    public Set<AAAA> getCachedIPv6NameserverAddressesFor(DnsName dnsName) {\n        return getCachedIPNameserverAddressesFor(dnsName, TYPE.AAAA);\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/DnsCache.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns;\n\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnsqueryresult.CachedDnsQueryResult;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\n\n/**\n * Cache for DNS Entries. Implementations must be thread safe.\n */\npublic abstract class DnsCache {\n\n    public static final int DEFAULT_CACHE_SIZE = 512;\n\n    /**\n     * Add an an dns answer/response for a given dns question. Implementations\n     * should honor the ttl / receive timestamp.\n     * @param query The query message containing a question.\n     * @param result The DNS query result.\n     */\n    public final void put(DnsMessage query, DnsQueryResult result) {\n        putNormalized(query.asNormalizedVersion(), result);\n    }\n\n    protected abstract void putNormalized(DnsMessage normalizedQuery, DnsQueryResult result);\n\n    public abstract void offer(DnsMessage query, DnsQueryResult result, DnsName authoritativeZone);\n\n    /**\n     * Request a cached dns response.\n     * @param query The query message containing a question.\n     * @return The dns message.\n     */\n    public final CachedDnsQueryResult get(DnsMessage query) {\n        return getNormalized(query.asNormalizedVersion());\n    }\n\n    protected abstract CachedDnsQueryResult getNormalized(DnsMessage normalizedQuery);\n\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/DnsClient.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns;\n\nimport org.minidns.MiniDnsException.ErrorResponseException;\nimport org.minidns.MiniDnsException.NoQueryPossibleException;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.dnsserverlookup.AndroidUsingExec;\nimport org.minidns.dnsserverlookup.AndroidUsingReflection;\nimport org.minidns.dnsserverlookup.DnsServerLookupMechanism;\nimport org.minidns.dnsserverlookup.UnixUsingEtcResolvConf;\nimport org.minidns.record.Record.TYPE;\nimport org.minidns.util.CollectionsUtil;\nimport org.minidns.util.InetAddressUtil;\nimport org.minidns.util.MultipleIoException;\n\nimport java.io.IOException;\nimport java.net.Inet4Address;\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.CopyOnWriteArraySet;\nimport java.util.logging.Level;\n\n/**\n * A minimal DNS client for SRV/A/AAAA/NS and CNAME lookups, with IDN support.\n * This circumvents the missing javax.naming package on android.\n */\npublic class DnsClient extends AbstractDnsClient {\n\n    static final List<DnsServerLookupMechanism> LOOKUP_MECHANISMS = new CopyOnWriteArrayList<>();\n\n    static final Set<Inet4Address> STATIC_IPV4_DNS_SERVERS = new CopyOnWriteArraySet<>();\n    static final Set<Inet6Address> STATIC_IPV6_DNS_SERVERS = new CopyOnWriteArraySet<>();\n\n    static {\n        addDnsServerLookupMechanism(AndroidUsingExec.INSTANCE);\n        addDnsServerLookupMechanism(AndroidUsingReflection.INSTANCE);\n        addDnsServerLookupMechanism(UnixUsingEtcResolvConf.INSTANCE);\n\n        try {\n            Inet4Address googleV4Dns = InetAddressUtil.ipv4From(\"8.8.8.8\");\n            STATIC_IPV4_DNS_SERVERS.add(googleV4Dns);\n        } catch (IllegalArgumentException e) {\n            LOGGER.log(Level.WARNING, \"Could not add static IPv4 DNS Server\", e);\n        }\n\n        try {\n            Inet6Address googleV6Dns = InetAddressUtil.ipv6From(\"[2001:4860:4860::8888]\");\n            STATIC_IPV6_DNS_SERVERS.add(googleV6Dns);\n        } catch (IllegalArgumentException e) {\n            LOGGER.log(Level.WARNING, \"Could not add static IPv6 DNS Server\", e);\n        }\n    }\n\n    private static final Set<String> blacklistedDnsServers = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(4));\n\n    private final Set<InetAddress> nonRaServers = Collections.newSetFromMap(new ConcurrentHashMap<InetAddress, Boolean>(4));\n\n    private boolean askForDnssec = false;\n    private boolean disableResultFilter = false;\n\n    private boolean useHardcodedDnsServers = true;\n\n    /**\n     * Create a new DNS client using the global default cache.\n     */\n    public DnsClient() {\n        super();\n    }\n\n    public DnsClient(DnsCache dnsCache) {\n        super(dnsCache);\n    }\n\n    @Override\n    protected DnsMessage.Builder newQuestion(DnsMessage.Builder message) {\n        message.setRecursionDesired(true);\n        message.getEdnsBuilder().setUdpPayloadSize(dataSource.getUdpPayloadSize()).setDnssecOk(askForDnssec);\n        return message;\n    }\n\n    private List<InetAddress> getServerAddresses() {\n        List<InetAddress> dnsServerAddresses = findDnsAddresses();\n\n        if (useHardcodedDnsServers) {\n            InetAddress primaryHardcodedDnsServer, secondaryHardcodedDnsServer = null;\n            switch (ipVersionSetting) {\n            case v4v6:\n                primaryHardcodedDnsServer = getRandomHardcodedIpv4DnsServer();\n                secondaryHardcodedDnsServer = getRandomHarcodedIpv6DnsServer();\n                break;\n            case v6v4:\n                primaryHardcodedDnsServer = getRandomHarcodedIpv6DnsServer();\n                secondaryHardcodedDnsServer = getRandomHardcodedIpv4DnsServer();\n                break;\n            case v4only:\n                primaryHardcodedDnsServer = getRandomHardcodedIpv4DnsServer();\n                break;\n            case v6only:\n                primaryHardcodedDnsServer = getRandomHarcodedIpv6DnsServer();\n                break;\n            default:\n                throw new AssertionError(\"Unknown ipVersionSetting: \" + ipVersionSetting);\n            }\n\n            dnsServerAddresses.add(primaryHardcodedDnsServer);\n            if (secondaryHardcodedDnsServer != null) {\n                dnsServerAddresses.add(secondaryHardcodedDnsServer);\n            }\n        }\n\n        return dnsServerAddresses;\n    }\n\n    @Override\n    public DnsQueryResult query(DnsMessage.Builder queryBuilder) throws IOException {\n        DnsMessage q = newQuestion(queryBuilder).build();\n        // While this query method does in fact re-use query(Question, String)\n        // we still do a cache lookup here in order to avoid unnecessary\n        // findDNS()calls, which are expensive on Android. Note that we do not\n        // put the results back into the Cache, as this is already done by\n        // query(Question, String).\n        DnsQueryResult dnsQueryResult = (cache == null) ? null : cache.get(q);\n        if (dnsQueryResult != null) {\n            return dnsQueryResult;\n        }\n\n        List<InetAddress> dnsServerAddresses = getServerAddresses();\n\n        List<IOException> ioExceptions = new ArrayList<>(dnsServerAddresses.size());\n        for (InetAddress dns : dnsServerAddresses) {\n            if (nonRaServers.contains(dns)) {\n                LOGGER.finer(\"Skipping \" + dns + \" because it was marked as \\\"recursion not available\\\"\");\n                continue;\n            }\n\n            try {\n                dnsQueryResult = query(q, dns);\n            } catch (IOException ioe) {\n                ioExceptions.add(ioe);\n                continue;\n            }\n\n            DnsMessage responseMessage = dnsQueryResult.response;\n            if (!responseMessage.recursionAvailable) {\n                boolean newRaServer = nonRaServers.add(dns);\n                if (newRaServer) {\n                    LOGGER.warning(\"The DNS server \" + dns\n                            + \" returned a response without the \\\"recursion available\\\" (RA) flag set. This likely indicates a misconfiguration because the server is not suitable for DNS resolution\");\n                }\n                continue;\n            }\n\n            if (disableResultFilter) {\n                return dnsQueryResult;\n            }\n\n            switch (responseMessage.responseCode) {\n            case NO_ERROR:\n            case NX_DOMAIN:\n                break;\n            default:\n                String warning = \"Response from \" + dns + \" asked for \" + q.getQuestion() + \" with error code: \"\n                        + responseMessage.responseCode + '.';\n                if (!LOGGER.isLoggable(Level.FINE)) {\n                    // Only append the responseMessage is log level is not fine. If it is fine or higher, the\n                    // response has already been logged.\n                    warning += \"\\n\" + responseMessage;\n                }\n                LOGGER.warning(warning);\n\n                ErrorResponseException exception = new ErrorResponseException(q, dnsQueryResult);\n                ioExceptions.add(exception);\n                continue;\n            }\n\n            return dnsQueryResult;\n        }\n        MultipleIoException.throwIfRequired(ioExceptions);\n\n        // TODO: Shall we add the attempted DNS servers to the exception?\n        throw new NoQueryPossibleException(q);\n    }\n\n    @Override\n    protected MiniDnsFuture<DnsQueryResult, IOException> queryAsync(DnsMessage.Builder queryBuilder) {\n        DnsMessage q = newQuestion(queryBuilder).build();\n        // While this query method does in fact re-use query(Question, String)\n        // we still do a cache lookup here in order to avoid unnecessary\n        // findDNS()calls, which are expensive on Android. Note that we do not\n        // put the results back into the Cache, as this is already done by\n        // query(Question, String).\n        DnsQueryResult responseMessage = (cache == null) ? null : cache.get(q);\n        if (responseMessage != null) {\n            return MiniDnsFuture.from(responseMessage);\n        }\n\n        final List<InetAddress> dnsServerAddresses = getServerAddresses();\n\n        // Filter loop.\n        Iterator<InetAddress> it = dnsServerAddresses.iterator();\n        while (it.hasNext()) {\n            InetAddress dns = it.next();\n            if (nonRaServers.contains(dns)) {\n                it.remove();\n                LOGGER.finer(\"Skipping \" + dns + \" because it was marked as \\\"recursion not available\\\"\");\n                continue;\n            }\n        }\n\n        List<MiniDnsFuture<DnsQueryResult, IOException>> futures = new ArrayList<>(dnsServerAddresses.size());\n        // \"Main\" loop.\n        for (InetAddress dns : dnsServerAddresses) {\n            MiniDnsFuture<DnsQueryResult, IOException> f = queryAsync(q, dns);\n            futures.add(f);\n        }\n\n        return MiniDnsFuture.anySuccessfulOf(futures);\n    }\n\n    /**\n     * Retrieve a list of currently configured DNS servers IP addresses. This method does verify that only IP addresses are returned and\n     * nothing else (e.g. DNS names).\n     * <p>\n     * The addresses are discovered by using one (or more) of the configured {@link DnsServerLookupMechanism}s.\n     * </p>\n     *\n     * @return A list of DNS server IP addresses configured for this system.\n     */\n    public static List<String> findDNS() {\n        List<String> res = null;\n        final Level TRACE_LOG_LEVEL = Level.FINE;\n        for (DnsServerLookupMechanism mechanism : LOOKUP_MECHANISMS) {\n            try {\n                res = mechanism.getDnsServerAddresses();\n            } catch (SecurityException exception) {\n                LOGGER.log(Level.WARNING, \"Could not lookup DNS server\", exception);\n            }\n            if (res == null) {\n                LOGGER.log(TRACE_LOG_LEVEL, \"DnsServerLookupMechanism '\" + mechanism.getName() + \"' did not return any DNS server\");\n                continue;\n            }\n\n            if (LOGGER.isLoggable(TRACE_LOG_LEVEL)) {\n                // TODO: Use String.join() once MiniDNS is Android API 26 (or higher).\n                StringBuilder sb = new StringBuilder();\n                for (Iterator<String> it = res.iterator(); it.hasNext();) {\n                    String s = it.next();\n                    sb.append(s);\n                    if (it.hasNext()) {\n                        sb.append(\", \");\n                    }\n                }\n                String dnsServers = sb.toString();\n                LOGGER.log(TRACE_LOG_LEVEL, \"DnsServerLookupMechanism {0} returned the following DNS servers: {1}\",\n                        new Object[] { mechanism.getName(), dnsServers });\n            }\n\n            assert !res.isEmpty();\n\n            // We could cache if res only contains IP addresses and avoid the verification in case. Not sure if its really that beneficial\n            // though, because the list returned by the server mechanism is rather short.\n\n            // Verify the returned DNS servers: Ensure that only valid IP addresses are returned. We want to avoid that something else,\n            // especially a valid DNS name is returned, as this would cause the following String to InetAddress conversation using\n            // getByName(String) to cause a DNS lookup, which would be performed outside of the realm of MiniDNS and therefore also outside\n            // of its DNSSEC guarantees.\n            Iterator<String> it = res.iterator();\n            while (it.hasNext()) {\n                String potentialDnsServer = it.next();\n                if (!InetAddressUtil.isIpAddress(potentialDnsServer)) {\n                    LOGGER.warning(\"The DNS server lookup mechanism '\" + mechanism.getName()\n                            + \"' returned an invalid non-IP address result: '\" + potentialDnsServer + \"'\");\n                    it.remove();\n                } else if (blacklistedDnsServers.contains(potentialDnsServer)) {\n                    LOGGER.fine(\"The DNS server lookup mechanism '\" + mechanism.getName()\n                    + \"' returned a blacklisted result: '\" + potentialDnsServer + \"'\");\n                    it.remove();\n                }\n            }\n\n            if (!res.isEmpty()) {\n                break;\n            }\n\n            LOGGER.warning(\"The DNS server lookup mechanism '\" + mechanism.getName()\n                        + \"' returned not a single valid IP address after sanitazion\");\n            res = null;\n        }\n\n        return res;\n    }\n\n    /**\n     * Retrieve a list of currently configured DNS server addresses.\n     * <p>\n     * Note that unlike {@link #findDNS()}, the list returned by this method\n     * will take the IP version setting into account, and order the list by the\n     * preferred address types (IPv4/v6). The returned list is modifiable.\n     * </p>\n     *\n     * @return A list of DNS server addresses.\n     * @see #findDNS()\n     */\n    public static List<InetAddress> findDnsAddresses() {\n        // The findDNS() method contract guarantees that only IP addresses will be returned.\n        List<String> res = findDNS();\n\n        if (res == null) {\n            return new ArrayList<>();\n        }\n\n        final IpVersionSetting setting = DEFAULT_IP_VERSION_SETTING;\n\n        List<Inet4Address> ipv4DnsServer = null;\n        List<Inet6Address> ipv6DnsServer = null;\n        if (setting.v4) {\n            ipv4DnsServer = new ArrayList<>(res.size());\n        }\n        if (setting.v6) {\n            ipv6DnsServer = new ArrayList<>(res.size());\n        }\n\n        int validServerAddresses = 0;\n        for (String dnsServerString : res) {\n            // The following invariant must hold: \"dnsServerString is a IP address\". Therefore findDNS() must only return a List of Strings\n            // representing IP addresses. Otherwise the following call of getByName(String) may perform a DNS lookup without MiniDNS being\n            // involved. Something we want to avoid.\n            assert InetAddressUtil.isIpAddress(dnsServerString);\n\n            InetAddress dnsServerAddress;\n            try {\n                dnsServerAddress = InetAddress.getByName(dnsServerString);\n            } catch (UnknownHostException e) {\n                LOGGER.log(Level.SEVERE, \"Could not transform '\" + dnsServerString + \"' to InetAddress\", e);\n                continue;\n            }\n            if (dnsServerAddress instanceof Inet4Address) {\n                if (!setting.v4) {\n                    continue;\n                }\n                Inet4Address ipv4DnsServerAddress = (Inet4Address) dnsServerAddress;\n                ipv4DnsServer.add(ipv4DnsServerAddress);\n            } else if (dnsServerAddress instanceof Inet6Address) {\n                if (!setting.v6) {\n                    continue;\n                }\n                Inet6Address ipv6DnsServerAddress = (Inet6Address) dnsServerAddress;\n                ipv6DnsServer.add(ipv6DnsServerAddress);\n            } else {\n                throw new AssertionError(\"The address '\" + dnsServerAddress + \"' is neither of type Inet(4|6)Address\");\n            }\n\n            validServerAddresses++;\n        }\n\n        List<InetAddress> dnsServers = new ArrayList<>(validServerAddresses);\n\n        switch (setting) {\n        case v4v6:\n            dnsServers.addAll(ipv4DnsServer);\n            dnsServers.addAll(ipv6DnsServer);\n            break;\n        case v6v4:\n            dnsServers.addAll(ipv6DnsServer);\n            dnsServers.addAll(ipv4DnsServer);\n            break;\n        case v4only:\n            dnsServers.addAll(ipv4DnsServer);\n            break;\n        case v6only:\n            dnsServers.addAll(ipv6DnsServer);\n            break;\n        }\n        return dnsServers;\n    }\n\n    public static void addDnsServerLookupMechanism(DnsServerLookupMechanism dnsServerLookup) {\n        if (!dnsServerLookup.isAvailable()) {\n            LOGGER.fine(\"Not adding \" + dnsServerLookup.getName() + \" as it is not available.\");\n            return;\n        }\n        synchronized (LOOKUP_MECHANISMS) {\n            // We can't use Collections.sort(CopyOnWriteArrayList) with Java 7. So we first create a temp array, sort it, and replace\n            // LOOKUP_MECHANISMS with the result. For more information about the Java 7 Collections.sort(CopyOnWriteArarayList) issue see\n            // http://stackoverflow.com/a/34827492/194894\n            // TODO: Remove that workaround once MiniDNS is Java 8 only.\n            ArrayList<DnsServerLookupMechanism> tempList = new ArrayList<>(LOOKUP_MECHANISMS.size() + 1);\n            tempList.addAll(LOOKUP_MECHANISMS);\n            tempList.add(dnsServerLookup);\n\n            // Sadly, this Collections.sort() does not with the CopyOnWriteArrayList on Java 7.\n            Collections.sort(tempList);\n\n            LOOKUP_MECHANISMS.clear();\n            LOOKUP_MECHANISMS.addAll(tempList);\n        }\n    }\n\n    public static boolean removeDNSServerLookupMechanism(DnsServerLookupMechanism dnsServerLookup) {\n        synchronized (LOOKUP_MECHANISMS) {\n            return LOOKUP_MECHANISMS.remove(dnsServerLookup);\n        }\n    }\n\n    public static boolean addBlacklistedDnsServer(String dnsServer) {\n        return blacklistedDnsServers.add(dnsServer);\n    }\n\n    public static boolean removeBlacklistedDnsServer(String dnsServer) {\n        return blacklistedDnsServers.remove(dnsServer);\n    }\n\n    public boolean isAskForDnssec() {\n        return askForDnssec;\n    }\n\n    public void setAskForDnssec(boolean askForDnssec) {\n        this.askForDnssec = askForDnssec;\n    }\n\n    public boolean isDisableResultFilter() {\n        return disableResultFilter;\n    }\n\n    public void setDisableResultFilter(boolean disableResultFilter) {\n        this.disableResultFilter = disableResultFilter;\n    }\n\n    public boolean isUseHardcodedDnsServersEnabled() {\n        return useHardcodedDnsServers;\n    }\n\n    public void setUseHardcodedDnsServers(boolean useHardcodedDnsServers) {\n        this.useHardcodedDnsServers = useHardcodedDnsServers;\n    }\n\n    public InetAddress getRandomHardcodedIpv4DnsServer() {\n        return CollectionsUtil.getRandomFrom(STATIC_IPV4_DNS_SERVERS, insecureRandom);\n    }\n\n    public InetAddress getRandomHarcodedIpv6DnsServer() {\n        return CollectionsUtil.getRandomFrom(STATIC_IPV6_DNS_SERVERS, insecureRandom);\n    }\n\n    private static Question getReverseIpLookupQuestionFor(DnsName dnsName) {\n        return new Question(dnsName, TYPE.PTR);\n    }\n\n    public static Question getReverseIpLookupQuestionFor(Inet4Address inet4Address) {\n        DnsName reversedIpAddress = InetAddressUtil.reverseIpAddressOf(inet4Address);\n        DnsName dnsName = DnsName.from(reversedIpAddress, DnsName.IN_ADDR_ARPA);\n        return getReverseIpLookupQuestionFor(dnsName);\n    }\n\n    public static Question getReverseIpLookupQuestionFor(Inet6Address inet6Address) {\n        DnsName reversedIpAddress = InetAddressUtil.reverseIpAddressOf(inet6Address);\n        DnsName dnsName = DnsName.from(reversedIpAddress, DnsName.IP6_ARPA);\n        return getReverseIpLookupQuestionFor(dnsName);\n    }\n\n    public static Question getReverseIpLookupQuestionFor(InetAddress inetAddress) {\n        if (inetAddress instanceof Inet4Address) {\n            return getReverseIpLookupQuestionFor((Inet4Address) inetAddress);\n        } else if (inetAddress instanceof Inet6Address) {\n            return getReverseIpLookupQuestionFor((Inet6Address) inetAddress);\n        } else {\n            throw new IllegalArgumentException(\"The provided inetAddress '\" + inetAddress\n                    + \"' is neither of type Inet4Address nor Inet6Address\");\n        }\n     }\n\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/MiniDnsConfiguration.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns;\n\npublic class MiniDnsConfiguration {\n\n    public static String getVersion() {\n        return MiniDnsInitialization.VERSION;\n    }\n\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/MiniDnsException.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns;\n\nimport java.io.IOException;\n\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\n\npublic abstract class MiniDnsException extends IOException {\n    /**\n     * \n     */\n    private static final long serialVersionUID = 1L;\n\n    protected MiniDnsException(String message) {\n        super(message);\n    }\n\n    public static class IdMismatch extends MiniDnsException {\n\n        /**\n         * \n         */\n        private static final long serialVersionUID = 1L;\n\n        private final DnsMessage request;\n        private final DnsMessage response;\n\n        public IdMismatch(DnsMessage request, DnsMessage response) {\n            super(getString(request, response));\n            assert request.id != response.id;\n            this.request = request;\n            this.response = response;\n        }\n\n        public DnsMessage getRequest() {\n            return request;\n        }\n\n        public DnsMessage getResponse() {\n            return response;\n        }\n\n        private static String getString(DnsMessage request, DnsMessage response) {\n            return \"The response's ID doesn't matches the request ID. Request: \" + request.id + \". Response: \" + response.id;\n        }\n    }\n\n    public static class NullResultException extends MiniDnsException {\n\n        /**\n         * \n         */\n        private static final long serialVersionUID = 1L;\n\n        private final DnsMessage request;\n\n        public NullResultException(DnsMessage request) {\n            super(\"The request yielded a 'null' result while resolving.\");\n            this.request = request;\n        }\n\n        public DnsMessage getRequest() {\n            return request;\n        }\n    }\n\n    public static class ErrorResponseException extends MiniDnsException {\n\n        /**\n         *\n         */\n        private static final long serialVersionUID = 1L;\n\n        private final DnsMessage request;\n        private final DnsQueryResult result;\n\n        public ErrorResponseException(DnsMessage request, DnsQueryResult result) {\n            super(\"Received \" + result.response.responseCode + \" error response\\n\" + result);\n            this.request = request;\n            this.result = result;\n        }\n\n        public DnsMessage getRequest() {\n            return request;\n        }\n\n        public DnsQueryResult getResult() {\n            return result;\n        }\n    }\n\n    public static class NoQueryPossibleException extends MiniDnsException {\n\n        /**\n         *\n         */\n        private static final long serialVersionUID = 1L;\n\n        private final DnsMessage request;\n\n        public NoQueryPossibleException(DnsMessage request) {\n            super(\"No DNS server could be queried\");\n            this.request = request;\n        }\n\n        public DnsMessage getRequest() {\n            return request;\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/MiniDnsFuture.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.ArrayBlockingQueue;\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.RejectedExecutionHandler;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport org.minidns.util.CallbackRecipient;\nimport org.minidns.util.ExceptionCallback;\nimport org.minidns.util.MultipleIoException;\nimport org.minidns.util.SuccessCallback;\n\npublic abstract class MiniDnsFuture<V, E extends Exception> implements Future<V>, CallbackRecipient<V, E> {\n\n    private boolean cancelled;\n\n    protected V result;\n\n    protected E exception;\n\n    private SuccessCallback<V> successCallback;\n\n    private ExceptionCallback<E> exceptionCallback;\n\n    @Override\n    public synchronized boolean cancel(boolean mayInterruptIfRunning) {\n        if (isDone()) {\n            return false;\n        }\n\n        cancelled = true;\n\n        if (mayInterruptIfRunning) {\n            notifyAll();\n        }\n\n        return true;\n    }\n\n    @Override\n    public final synchronized boolean isCancelled() {\n        return cancelled;\n    }\n\n    @Override\n    public final synchronized boolean isDone() {\n        return hasResult() || hasException();\n    }\n\n    public final synchronized boolean hasResult() {\n        return result != null;\n    }\n\n    public final synchronized boolean hasException() {\n        return exception != null;\n    }\n\n    @Override\n    public CallbackRecipient<V, E> onSuccess(SuccessCallback<V> successCallback) {\n        this.successCallback = successCallback;\n        maybeInvokeCallbacks();\n        return this;\n    }\n\n    @Override\n    public CallbackRecipient<V, E> onError(ExceptionCallback<E> exceptionCallback) {\n        this.exceptionCallback = exceptionCallback;\n        maybeInvokeCallbacks();\n        return this;\n    }\n\n    private V getOrThrowExecutionException() throws ExecutionException {\n        assert result != null || exception != null || cancelled;\n        if (result != null) {\n            return result;\n        }\n        if (exception != null) {\n            throw new ExecutionException(exception);\n        }\n\n        assert cancelled;\n        throw new CancellationException();\n    }\n\n    @Override\n    public final synchronized V get() throws InterruptedException, ExecutionException {\n        while (result == null && exception == null && !cancelled) {\n            wait();\n        }\n\n        return getOrThrowExecutionException();\n    }\n\n    public final synchronized V getOrThrow() throws E {\n        while (result == null && exception == null && !cancelled) {\n            try {\n                wait();\n            } catch (InterruptedException e) {\n                throw new RuntimeException(e);\n            }\n        }\n\n        if (exception != null) {\n            throw exception;\n        }\n\n        if (cancelled) {\n            throw new CancellationException();\n        }\n\n        assert result != null;\n        return result;\n    }\n\n    @Override\n    public final synchronized V get(long timeout, TimeUnit unit)\n                    throws InterruptedException, ExecutionException, TimeoutException {\n        final long deadline = System.currentTimeMillis() + unit.toMillis(timeout);\n        while (result != null && exception != null && !cancelled) {\n            final long waitTimeRemaining = deadline - System.currentTimeMillis();\n            if (waitTimeRemaining > 0) {\n                wait(waitTimeRemaining);\n            }\n        }\n\n        if (cancelled) {\n            throw new CancellationException();\n        }\n\n        if (result == null || exception == null) {\n            throw new TimeoutException();\n        }\n\n        return getOrThrowExecutionException();\n    }\n\n    private static final ExecutorService EXECUTOR_SERVICE;\n\n    static {\n        ThreadFactory threadFactory = new ThreadFactory() {\n            @Override\n            public Thread newThread(Runnable r) {\n                Thread thread = new Thread(r);\n                thread.setDaemon(true);\n                thread.setName(\"MiniDnsFuture Thread\");\n                return thread;\n            }\n        };\n        BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(128);\n        RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() {\n            @Override\n            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {\n                r.run();\n            }\n        };\n        int cores = Runtime.getRuntime().availableProcessors();\n        int maximumPoolSize = cores <= 4 ? 2 : cores;\n        ExecutorService executorService = new ThreadPoolExecutor(0, maximumPoolSize, 60L, TimeUnit.SECONDS, blockingQueue, threadFactory,\n                rejectedExecutionHandler);\n\n        EXECUTOR_SERVICE = executorService;\n    }\n\n    @SuppressWarnings(\"FutureReturnValueIgnored\")\n    protected final synchronized void maybeInvokeCallbacks() {\n        if (cancelled) {\n            return;\n        }\n\n        if (result != null && successCallback != null) {\n            EXECUTOR_SERVICE.submit(new Runnable() {\n                @Override\n                public void run() {\n                    successCallback.onSuccess(result);\n                }\n            });\n        } else if (exception != null && exceptionCallback != null) {\n            EXECUTOR_SERVICE.submit(new Runnable() {\n                @Override\n                public void run() {\n                    exceptionCallback.processException(exception);\n                }\n            });\n        }\n    }\n\n    public static class InternalMiniDnsFuture<V, E extends Exception> extends MiniDnsFuture<V, E> {\n        public final synchronized void setResult(V result) {\n            if (isDone()) {\n                return;\n            }\n\n            this.result = result;\n            this.notifyAll();\n\n            maybeInvokeCallbacks();\n        }\n\n        public final synchronized void setException(E exception) {\n            if (isDone()) {\n                return;\n            }\n\n            this.exception = exception;\n            this.notifyAll();\n\n            maybeInvokeCallbacks();\n        }\n    }\n\n    public static <V, E extends Exception> MiniDnsFuture<V, E> from(V result) {\n        InternalMiniDnsFuture<V, E> future = new InternalMiniDnsFuture<>();\n        future.setResult(result);\n        return future;\n    }\n\n    public static <V> MiniDnsFuture<V, IOException> anySuccessfulOf(Collection<MiniDnsFuture<V, IOException>> futures) {\n        return anySuccessfulOf(futures, exceptions -> MultipleIoException.toIOException(exceptions));\n    }\n\n    public interface ExceptionsWrapper<EI extends Exception, EO extends Exception> {\n        EO wrap(List<EI> exceptions);\n    }\n\n    public static <V, EI extends Exception, EO extends Exception> MiniDnsFuture<V, EO> anySuccessfulOf(\n            Collection<MiniDnsFuture<V, EI>> futures,\n            ExceptionsWrapper<EI, EO> exceptionsWrapper) {\n        InternalMiniDnsFuture<V, EO> returnedFuture = new InternalMiniDnsFuture<>();\n\n        final List<EI> exceptions = Collections.synchronizedList(new ArrayList<>(futures.size()));\n\n        for (MiniDnsFuture<V, EI> future : futures) {\n            future.onSuccess(new SuccessCallback<V>() {\n                @Override\n                public void onSuccess(V result) {\n                    // Cancel all futures. Yes, this includes the future which just returned the\n                    // result and futures which already failed with an exception, but then cancel\n                    // will be a no-op.\n                    for (MiniDnsFuture<V, EI> futureToCancel : futures) {\n                        futureToCancel.cancel(true);\n                    }\n                    returnedFuture.setResult(result);\n                }\n            });\n            future.onError(new ExceptionCallback<EI>() {\n                @Override\n                public void processException(EI exception) {\n                    exceptions.add(exception);\n                    // Signal the main future about the exceptions, but only if all sub-futures returned an exception.\n                    if (exceptions.size() == futures.size()) {\n                        EO returnedException = exceptionsWrapper.wrap(exceptions);\n                        returnedFuture.setException(returnedException);\n                    }\n                }\n            });\n        }\n\n        return returnedFuture;\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/MiniDnsInitialization.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\npublic class MiniDnsInitialization {\n\n    private static final Logger LOGGER = Logger.getLogger(MiniDnsInitialization.class.getName());\n\n    static final String VERSION;\n\n    static {\n        String miniDnsVersion;\n        BufferedReader reader = null;\n        try {\n            InputStream is = MiniDnsInitialization.class.getClassLoader().getResourceAsStream(\"org.minidns/version\");\n            reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));\n            miniDnsVersion = reader.readLine();\n        } catch (Exception e) {\n            LOGGER.log(Level.SEVERE, \"Could not determine MiniDNS version\", e);\n            miniDnsVersion = \"unkown\";\n        } finally {\n            if (reader != null) {\n                try {\n                    reader.close();\n                } catch (IOException e) {\n                    LOGGER.log(Level.WARNING, \"IOException closing stream\", e);\n                }\n            }\n        }\n        VERSION = miniDnsVersion;\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/RrSet.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns;\n\nimport java.util.Collections;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\n\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.Data;\nimport org.minidns.record.Record;\nimport org.minidns.record.Record.CLASS;\nimport org.minidns.record.Record.TYPE;\n\npublic final class RrSet {\n\n    public final DnsName name;\n    public final TYPE type;\n    public final CLASS clazz;\n    public final Set<Record<? extends Data>> records;\n\n    private RrSet(DnsName name, TYPE type, CLASS clazz, Set<Record<? extends Data>> records) {\n        this.name = name;\n        this.type = type;\n        this.clazz = clazz;\n        this.records = Collections.unmodifiableSet(records);\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder sb = new StringBuilder();\n        sb.append(name).append('\\t').append(clazz).append('\\t').append(type).append('\\n');\n        for (Record<?> record : records) {\n            sb.append(record).append('\\n');\n        }\n        return sb.toString();\n    }\n\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    public static final class Builder {\n        private DnsName name;\n        private TYPE type;\n        private CLASS clazz;\n        Set<Record<? extends Data>> records = new LinkedHashSet<>(8);\n\n        private Builder() {\n        }\n\n        public Builder addRecord(Record<? extends Data> record) {\n            if (name == null) {\n                name = record.name;\n                type = record.type;\n                clazz = record.clazz;\n            } else if (!couldContain(record)) {\n                throw new IllegalArgumentException(\n                        \"Can not add \" + record + \" to RRSet \" + name + ' ' + type + ' ' + clazz);\n            }\n\n            boolean didNotExist = records.add(record);\n            assert didNotExist;\n\n            return this;\n        }\n\n        public boolean couldContain(Record<? extends Data> record) {\n            if (name == null) {\n                return true;\n            }\n            return name.equals(record.name) && type == record.type && clazz == record.clazz;\n        }\n\n        public boolean addIfPossible(Record<? extends Data> record) {\n            if (!couldContain(record)) {\n                return false;\n            }\n            addRecord(record);\n            return true;\n        }\n\n        public RrSet build() {\n            if (name == null) {\n                // There is no RR added to this builder.\n                throw new IllegalStateException();\n            }\n            return new RrSet(name, type, clazz, records);\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/cache/ExtendedLruCache.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.cache;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\n\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnsqueryresult.CachedDnsQueryResult;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.dnsqueryresult.SynthesizedCachedDnsQueryResult;\nimport org.minidns.record.Data;\nimport org.minidns.record.Record;\n\n/**\n * A variant of {@link LruCache} also using the data found in the sections for caching.\n */\npublic class ExtendedLruCache extends LruCache {\n\n    public ExtendedLruCache() {\n        this(DEFAULT_CACHE_SIZE);\n    }\n\n    public ExtendedLruCache(int capacity) {\n        super(capacity);\n    }\n\n    public ExtendedLruCache(int capacity, long maxTTL) {\n        super(capacity, maxTTL);\n    }\n\n    @SuppressWarnings(\"UnsynchronizedOverridesSynchronized\")\n    @Override\n    protected void putNormalized(DnsMessage q, DnsQueryResult result) {\n        super.putNormalized(q, result);\n        DnsMessage message = result.response;\n        Map<DnsMessage, List<Record<? extends Data>>> extraCaches = new HashMap<>(message.additionalSection.size());\n\n        gather(extraCaches, q, message.answerSection, null);\n        gather(extraCaches, q, message.authoritySection, null);\n        gather(extraCaches, q, message.additionalSection, null);\n\n        putExtraCaches(result, extraCaches);\n    }\n\n    @Override\n    public void offer(DnsMessage query, DnsQueryResult result, DnsName authoritativeZone) {\n        DnsMessage reply = result.response;\n        // The reply shouldn't be an authoritative answers when offer() is used. That would be a case for put().\n        assert !reply.authoritativeAnswer;\n\n        Map<DnsMessage, List<Record<? extends Data>>> extraCaches = new HashMap<>(reply.additionalSection.size());\n\n        // N.B. not gathering from reply.answerSection here. Since it is a non authoritativeAnswer it shouldn't contain anything.\n        gather(extraCaches, query, reply.authoritySection, authoritativeZone);\n        gather(extraCaches, query, reply.additionalSection, authoritativeZone);\n\n        putExtraCaches(result, extraCaches);\n    }\n\n    private void gather(Map<DnsMessage, List<Record<?extends Data>>> extraCaches, DnsMessage q, List<Record<? extends Data>> records, DnsName authoritativeZone) {\n        for (Record<? extends Data> extraRecord : records) {\n            if (!shouldGather(extraRecord, q.getQuestion(), authoritativeZone))\n                continue;\n\n            DnsMessage.Builder additionalRecordQuestionBuilder = extraRecord.getQuestionMessage();\n            if (additionalRecordQuestionBuilder == null)\n                continue;\n\n            additionalRecordQuestionBuilder.copyFlagsFrom(q);\n\n            additionalRecordQuestionBuilder.setAdditionalResourceRecords(q.additionalSection);\n\n            DnsMessage additionalRecordQuestion = additionalRecordQuestionBuilder.build();\n            if (additionalRecordQuestion.equals(q)) {\n                // No need to cache the additional question if it is the same as the original question.\n                continue;\n            }\n\n            List<Record<? extends Data>> additionalRecords = extraCaches.get(additionalRecordQuestion);\n            if (additionalRecords == null) {\n                 additionalRecords = new ArrayList<>();\n                 extraCaches.put(additionalRecordQuestion, additionalRecords);\n            }\n            additionalRecords.add(extraRecord);\n        }\n    }\n\n    private void putExtraCaches(DnsQueryResult synthesynthesizationSource, Map<DnsMessage, List<Record<? extends Data>>> extraCaches) {\n        DnsMessage reply = synthesynthesizationSource.response;\n        for (Entry<DnsMessage, List<Record<? extends Data>>> entry : extraCaches.entrySet()) {\n            DnsMessage question = entry.getKey();\n            DnsMessage answer = reply.asBuilder()\n                    .setQuestion(question.getQuestion())\n                    .setAuthoritativeAnswer(true)\n                    .addAnswers(entry.getValue())\n                    .build();\n            CachedDnsQueryResult cachedDnsQueryResult = new SynthesizedCachedDnsQueryResult(question, answer, synthesynthesizationSource);\n            synchronized (this) {\n                backend.put(question, cachedDnsQueryResult);\n            }\n        }\n    }\n\n    protected boolean shouldGather(Record<? extends Data> extraRecord, Question question, DnsName authoritativeZone) {\n        boolean extraRecordIsChildOfQuestion = extraRecord.name.isChildOf(question.name);\n\n        boolean extraRecordIsChildOfAuthoritativeZone = false;\n        if (authoritativeZone != null) {\n            extraRecordIsChildOfAuthoritativeZone = extraRecord.name.isChildOf(authoritativeZone);\n        }\n\n        return extraRecordIsChildOfQuestion || extraRecordIsChildOfAuthoritativeZone;\n    }\n\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/cache/FullLruCache.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.cache;\n\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.Data;\nimport org.minidns.record.Record;\n\n/**\n * An <b>insecure</b> variant of {@link LruCache} also using all the data found in the sections of an answer.\n */\npublic class FullLruCache extends ExtendedLruCache {\n\n    public FullLruCache() {\n        this(DEFAULT_CACHE_SIZE);\n    }\n\n    public FullLruCache(int capacity) {\n        super(capacity);\n    }\n\n    public FullLruCache(int capacity, long maxTTL) {\n        super(capacity, maxTTL);\n    }\n\n    @Override\n    protected boolean shouldGather(Record<? extends Data> extraRecord, Question question, DnsName authoritativeZone) {\n        return true;\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/cache/LruCache.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.cache;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map.Entry;\n\nimport org.minidns.DnsCache;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnsqueryresult.CachedDnsQueryResult;\nimport org.minidns.dnsqueryresult.DirectCachedDnsQueryResult;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\n\n/**\n * LRU based DNSCache backed by a LinkedHashMap.\n */\npublic class LruCache extends DnsCache {\n\n    /**\n     * Internal miss count.\n     */\n    protected long missCount = 0L;\n\n    /**\n     * Internal expire count (subset of misses that was caused by expire).\n     */\n    protected long expireCount = 0L;\n\n    /**\n     * Internal hit count.\n     */\n    protected long hitCount = 0L;\n\n    /**\n     * The internal capacity of the backend cache.\n     */\n    protected int capacity;\n\n    /**\n     * The upper bound of the ttl. All longer TTLs will be capped by this ttl.\n     */\n    protected long maxTTL;\n\n    /**\n     * The backend cache.\n     */\n    protected LinkedHashMap<DnsMessage, CachedDnsQueryResult> backend;\n\n    /**\n     * Create a new LRUCache with given capacity and upper bound ttl.\n     * @param capacity The internal capacity.\n     * @param maxTTL The upper bound for any ttl.\n     */\n    @SuppressWarnings(\"serial\")\n    public LruCache(final int capacity, final long maxTTL) {\n        this.capacity = capacity;\n        this.maxTTL = maxTTL;\n        backend = new LinkedHashMap<DnsMessage, CachedDnsQueryResult>(\n                Math.min(capacity + (capacity + 3) / 4 + 2, 11), 0.75f, true) {\n                @Override\n                protected boolean removeEldestEntry(\n                        Entry<DnsMessage, CachedDnsQueryResult> eldest) {\n                    return size() > capacity;\n                }\n            };\n    }\n\n    /**\n     * Create a new LRUCache with given capacity.\n     * @param capacity The capacity of this cache.\n     */\n    public LruCache(final int capacity) {\n        this(capacity, Long.MAX_VALUE);\n    }\n\n    public LruCache() {\n        this(DEFAULT_CACHE_SIZE);\n    }\n\n    @Override\n    protected synchronized void putNormalized(DnsMessage q, DnsQueryResult result) {\n        if (result.response.receiveTimestamp <= 0L) {\n            return;\n        }\n        backend.put(q, new DirectCachedDnsQueryResult(q, result));\n    }\n\n    @Override\n    protected synchronized CachedDnsQueryResult getNormalized(DnsMessage q) {\n        CachedDnsQueryResult result = backend.get(q);\n        if (result == null) {\n            missCount++;\n            return null;\n        }\n\n        DnsMessage message = result.response;\n\n        // RFC 2181 § 5.2 says that all TTLs in a RRSet should be equal, if this isn't the case, then we assume the\n        // shortest TTL to be the effective one.\n        final long answersMinTtl = message.getAnswersMinTtl();\n        final long ttl = Math.min(answersMinTtl, maxTTL);\n\n        final long expiryDate = message.receiveTimestamp + (ttl * 1000);\n        final long now = System.currentTimeMillis();\n        if (expiryDate < now) {\n            missCount++;\n            expireCount++;\n            backend.remove(q);\n            return null;\n        } else {\n            hitCount++;\n            return result;\n        }\n    }\n\n    /**\n     * Clear all entries in this cache.\n     */\n    public synchronized void clear() {\n        backend.clear();\n        missCount = 0L;\n        hitCount = 0L;\n        expireCount = 0L;\n    }\n\n    /**\n     * Get the miss count of this cache which is the number of fruitless\n     * get calls since this cache was last resetted.\n     * @return The number of cache misses.\n     */\n    public long getMissCount() {\n        return missCount;\n    }\n\n    /**\n     * The number of expires (cache hits that have had a ttl to low to be\n     * retrieved).\n     * @return The expire count.\n     */\n    public long getExpireCount() {\n        return expireCount;\n    }\n\n    /**\n     * The cache hit count (all successful calls to get).\n     * @return The hit count.\n     */\n    public long getHitCount() {\n        return hitCount;\n    }\n\n    @Override\n    public String toString() {\n        return \"LRUCache{usage=\" + backend.size() + \"/\" + capacity + \", hits=\" + hitCount + \", misses=\" + missCount + \", expires=\" + expireCount + \"}\";\n    }\n\n    @Override\n    public void offer(DnsMessage query, DnsQueryResult result, DnsName knownAuthoritativeZone) {\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/cache/MiniDnsCacheFactory.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.cache;\n\nimport org.minidns.DnsCache;\n\npublic interface MiniDnsCacheFactory {\n\n    DnsCache newCache();\n\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/dnsqueryresult/CachedDnsQueryResult.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsqueryresult;\n\nimport org.minidns.dnsmessage.DnsMessage;\n\npublic abstract class CachedDnsQueryResult extends DnsQueryResult {\n\n    protected final DnsQueryResult cachedDnsQueryResult;\n\n    protected CachedDnsQueryResult(DnsMessage query, DnsQueryResult cachedDnsQueryResult) {\n        super(QueryMethod.cachedDirect, query, cachedDnsQueryResult.response);\n        this.cachedDnsQueryResult = cachedDnsQueryResult;\n    }\n\n    protected CachedDnsQueryResult(DnsMessage query, DnsMessage response, DnsQueryResult synthesynthesizationSource) {\n        super(QueryMethod.cachedSynthesized, query, response);\n        this.cachedDnsQueryResult = synthesynthesizationSource;\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/dnsqueryresult/DirectCachedDnsQueryResult.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsqueryresult;\n\nimport org.minidns.dnsmessage.DnsMessage;\n\npublic class DirectCachedDnsQueryResult extends CachedDnsQueryResult {\n\n    public DirectCachedDnsQueryResult(DnsMessage query, DnsQueryResult cachedDnsQueryResult) {\n        super(query, cachedDnsQueryResult);\n    }\n\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/dnsqueryresult/DnsQueryResult.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsqueryresult;\n\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;\n\npublic abstract class DnsQueryResult {\n\n    public enum QueryMethod {\n        udp,\n        tcp,\n        asyncUdp,\n        asyncTcp,\n        cachedDirect,\n        cachedSynthesized,\n        testWorld,\n    }\n\n    public final QueryMethod queryMethod;\n\n    public final DnsMessage query;\n\n    public final DnsMessage response;\n\n    protected DnsQueryResult(QueryMethod queryMethod, DnsMessage query, DnsMessage response) {\n        assert queryMethod != null;\n        assert query != null;\n        assert response != null;\n\n        this.queryMethod = queryMethod;\n        this.query = query;\n        this.response = response;\n    }\n\n    @Override\n    public String toString() {\n        return response.toString();\n    }\n\n    public boolean wasSuccessful() {\n        return response.responseCode == RESPONSE_CODE.NO_ERROR;\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/dnsqueryresult/StandardDnsQueryResult.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsqueryresult;\n\nimport java.net.InetAddress;\n\nimport org.minidns.dnsmessage.DnsMessage;\n\npublic class StandardDnsQueryResult extends DnsQueryResult {\n\n    public final InetAddress serverAddress;\n\n    public final int port;\n\n    public StandardDnsQueryResult(InetAddress serverAddress, int port, QueryMethod queryMethod, DnsMessage query, DnsMessage responseDnsMessage) {\n        super(queryMethod, query, responseDnsMessage);\n        this.serverAddress = serverAddress;\n        this.port = port;\n    }\n\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/dnsqueryresult/SynthesizedCachedDnsQueryResult.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsqueryresult;\n\nimport org.minidns.dnsmessage.DnsMessage;\n\npublic class SynthesizedCachedDnsQueryResult extends CachedDnsQueryResult {\n\n    public SynthesizedCachedDnsQueryResult(DnsMessage query, DnsMessage response, DnsQueryResult synthesynthesizationSource) {\n        super(query, response, synthesynthesizationSource);\n    }\n\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/dnsserverlookup/AbstractDnsServerLookupMechanism.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\n package org.minidns.dnsserverlookup;\n\nimport java.net.InetAddress;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.logging.Logger;\n\npublic abstract class AbstractDnsServerLookupMechanism implements DnsServerLookupMechanism {\n\n    protected static final Logger LOGGER = Logger.getLogger(AbstractDnsServerLookupMechanism.class.getName());\n\n    private final String name;\n    private final int priority;\n\n    protected AbstractDnsServerLookupMechanism(String name, int priority) {\n        this.name = name;\n        this.priority = priority;\n    }\n\n    @Override\n    public final String getName() {\n        return name;\n    }\n\n    @Override\n    public final int getPriority() {\n        return priority;\n    }\n\n    @Override\n    public final int compareTo(DnsServerLookupMechanism other) {\n        int myPriority = getPriority();\n        int otherPriority = other.getPriority();\n\n        return Integer.compare(myPriority, otherPriority);\n    }\n\n    @Override\n    public abstract List<String> getDnsServerAddresses();\n\n    protected static List<String> toListOfStrings(Collection<? extends InetAddress> inetAddresses) {\n        List<String> result = new ArrayList<>(inetAddresses.size());\n        for (InetAddress inetAddress : inetAddresses) {\n            String address = inetAddress.getHostAddress();\n            result.add(address);\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/dnsserverlookup/AndroidUsingExec.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsserverlookup;\n\nimport org.minidns.util.PlatformDetection;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.LineNumberReader;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.logging.Level;\n\n/**\n * Try to retrieve the list of DNS server by executing getprop.\n */\npublic final class AndroidUsingExec extends AbstractDnsServerLookupMechanism {\n\n    public static final DnsServerLookupMechanism INSTANCE = new AndroidUsingExec();\n    public static final int PRIORITY = AndroidUsingReflection.PRIORITY - 1;\n\n    private AndroidUsingExec() {\n        super(AndroidUsingExec.class.getSimpleName(), PRIORITY);\n    }\n\n    @Override\n    public List<String> getDnsServerAddresses() {\n        try {\n            Process process = Runtime.getRuntime().exec(\"getprop\");\n            InputStream inputStream = process.getInputStream();\n            LineNumberReader lnr = new LineNumberReader(\n                new InputStreamReader(inputStream, StandardCharsets.UTF_8));\n            Set<String> server = parseProps(lnr, true);\n            if (server.size() > 0) {\n                List<String> res = new ArrayList<>(server.size());\n                res.addAll(server);\n                return res;\n            }\n        } catch (IOException e) {\n            LOGGER.log(Level.WARNING, \"Exception in findDNSByExec\", e);\n        }\n        return null;\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return PlatformDetection.isAndroid();\n    }\n\n    private static final String PROP_DELIM = \"]: [\";\n    static Set<String> parseProps(BufferedReader lnr, boolean logWarning) throws UnknownHostException, IOException {\n        String line = null;\n        Set<String> server = new HashSet<String>(6);\n\n        while ((line = lnr.readLine()) != null) {\n            int split = line.indexOf(PROP_DELIM);\n            if (split == -1) {\n                continue;\n            }\n            String property = line.substring(1, split);\n\n            int valueStart = split + PROP_DELIM.length();\n            int valueEnd = line.length() - 1;\n            if (valueEnd < valueStart) {\n                // This can happen if a newline sneaks in as the first character of the property value. For example\n                // \"[propName]: [\\n…]\".\n                if (logWarning) {\n                    LOGGER.warning(\"Malformed property detected: \\\"\" + line + '\"');\n                }\n                continue;\n            }\n\n            String value = line.substring(valueStart, valueEnd);\n\n            if (value.isEmpty()) {\n                continue;\n            }\n\n            if (property.endsWith(\".dns\") || property.endsWith(\".dns1\") ||\n                property.endsWith(\".dns2\") || property.endsWith(\".dns3\") ||\n                property.endsWith(\".dns4\")) {\n\n                // normalize the address\n\n                InetAddress ip = InetAddress.getByName(value);\n\n                if (ip == null) continue;\n\n                value = ip.getHostAddress();\n\n                if (value == null) continue;\n                if (value.length() == 0) continue;\n\n                server.add(value);\n            }\n        }\n\n        return server;\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/dnsserverlookup/AndroidUsingReflection.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsserverlookup;\n\nimport org.minidns.util.PlatformDetection;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.logging.Level;\n\n/**\n * Try to retrieve the list of DNS server by calling SystemProperties.\n */\npublic class AndroidUsingReflection extends AbstractDnsServerLookupMechanism {\n\n    public static final DnsServerLookupMechanism INSTANCE = new AndroidUsingReflection();\n    public static final int PRIORITY = 1000;\n\n    private final Method systemPropertiesGet;\n\n    protected AndroidUsingReflection() {\n        super(AndroidUsingReflection.class.getSimpleName(), PRIORITY);\n        Method systemPropertiesGet = null;\n        if (PlatformDetection.isAndroid()) {\n            try {\n                Class<?> SystemProperties = Class.forName(\"android.os.SystemProperties\");\n                systemPropertiesGet = SystemProperties.getMethod(\"get\", new Class<?>[] { String.class });\n            } catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) {\n                // This is not unexpected, as newer Android versions do not provide access to it any more.\n                LOGGER.log(Level.FINE, \"Can not get method handle for android.os.SystemProperties.get(String).\", e);\n            }\n        }\n        this.systemPropertiesGet = systemPropertiesGet;\n    }\n\n    @Override\n    public List<String> getDnsServerAddresses() {\n        ArrayList<String> servers = new ArrayList<String>(5);\n\n        for (String propKey : new String[] {\n                \"net.dns1\", \"net.dns2\", \"net.dns3\", \"net.dns4\"}) {\n\n            String value;\n            try {\n                value = (String) systemPropertiesGet.invoke(null, propKey);\n            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {\n                LOGGER.log(Level.WARNING, \"Exception in findDNSByReflection\", e);\n                return null;\n            }\n\n            if (value == null) continue;\n            if (value.length() == 0) continue;\n            if (servers.contains(value)) continue;\n\n            InetAddress ip;\n            try {\n                ip = InetAddress.getByName(value);\n            } catch (UnknownHostException e) {\n                LOGGER.log(Level.WARNING, \"Exception in findDNSByReflection\", e);\n                continue;\n            }\n\n            if (ip == null) continue;\n\n            value = ip.getHostAddress();\n\n            if (value == null) continue;\n            if (value.length() == 0) continue;\n            if (servers.contains(value)) continue;\n\n            servers.add(value);\n        }\n\n        if (servers.size() > 0) {\n            return servers;\n        }\n\n        return null;\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return systemPropertiesGet != null;\n    }\n\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/dnsserverlookup/DnsServerLookupMechanism.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsserverlookup;\n\nimport java.util.List;\n\npublic interface DnsServerLookupMechanism extends Comparable<DnsServerLookupMechanism> {\n\n    String getName();\n\n    int getPriority();\n\n    boolean isAvailable();\n\n    /**\n     * Returns a List of String representing ideally IP addresses. The list must be modifiable.\n     * <p>\n     * Note that the lookup mechanisms are not required to assure that only IP addresses are returned. This verification is performed in\n     * when using {@link org.minidns.DnsClient#findDNS()}.\n     * </p>\n     *\n     * @return a List of Strings presenting hopefully IP addresses.\n     */\n    List<String> getDnsServerAddresses();\n\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/dnsserverlookup/UnixUsingEtcResolvConf.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsserverlookup;\n\nimport org.minidns.util.PlatformDetection;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic final class UnixUsingEtcResolvConf extends AbstractDnsServerLookupMechanism {\n\n    public static final DnsServerLookupMechanism INSTANCE = new UnixUsingEtcResolvConf();\n    public static final int PRIORITY = 2000;\n\n    private static final Logger LOGGER = Logger.getLogger(UnixUsingEtcResolvConf.class.getName());\n\n    private static final String RESOLV_CONF_FILE = \"/etc/resolv.conf\";\n    private static final Pattern NAMESERVER_PATTERN = Pattern.compile(\"^nameserver\\\\s+(.*)$\");\n\n    private static List<String> cached;\n    private static long lastModified;\n\n    private UnixUsingEtcResolvConf() {\n        super(UnixUsingEtcResolvConf.class.getSimpleName(), PRIORITY);\n    }\n\n    @Override\n    public List<String> getDnsServerAddresses() {\n        File file = new File(RESOLV_CONF_FILE);\n        if (!file.exists()) {\n            // Not very unixoid systems\n            return null;\n        }\n\n        long currentLastModified = file.lastModified();\n        if (currentLastModified == lastModified && cached != null) {\n            return cached;\n        }\n\n        List<String> servers = new ArrayList<>();\n        BufferedReader reader = null;\n        try {\n            reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));\n            String line;\n            while ((line = reader.readLine()) != null) {\n                Matcher matcher = NAMESERVER_PATTERN.matcher(line);\n                if (matcher.matches()) {\n                    servers.add(matcher.group(1).trim());\n                }\n            }\n        } catch (IOException e) {\n            LOGGER.log(Level.WARNING, \"Could not read from \" + RESOLV_CONF_FILE, e);\n            return null;\n        } finally {\n            if (reader != null) try {\n                reader.close();\n            } catch (IOException e) {\n                LOGGER.log(Level.WARNING, \"Could not close reader\", e);\n            }\n        }\n\n        if (servers.isEmpty()) {\n            LOGGER.fine(\"Could not find any nameservers in \" + RESOLV_CONF_FILE);\n            return null;\n        }\n\n        cached = servers;\n        lastModified = currentLastModified;\n\n        return cached;\n    }\n\n    @Override\n    public boolean isAvailable() {\n        if (PlatformDetection.isAndroid()) {\n            // Don't rely on resolv.conf when on Android\n            return false;\n        }\n\n        File file = new File(RESOLV_CONF_FILE);\n\n        boolean resolvConfFileExists;\n        try {\n            resolvConfFileExists = file.exists();\n        } catch (SecurityException securityException) {\n            LOGGER.log(Level.FINE, \"Access to /etc/resolv.conf not possible\", securityException);\n            return false;\n        }\n        return resolvConfFileExists;\n    }\n\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/source/AbstractDnsDataSource.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.source;\n\nimport org.minidns.DnsCache;\nimport org.minidns.MiniDnsFuture;\nimport org.minidns.MiniDnsFuture.InternalMiniDnsFuture;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\n\npublic abstract class AbstractDnsDataSource implements DnsDataSource {\n\n    @Override\n    public abstract DnsQueryResult query(DnsMessage message, InetAddress address, int port) throws IOException;\n\n    @Override\n    public MiniDnsFuture<DnsQueryResult, IOException> queryAsync(DnsMessage message, InetAddress address, int port, OnResponseCallback onResponseCallback) {\n        InternalMiniDnsFuture<DnsQueryResult, IOException> future = new InternalMiniDnsFuture<>();\n        DnsQueryResult result;\n        try {\n            result = query(message, address, port);\n        } catch (IOException e) {\n            future.setException(e);\n            return future;\n        }\n        future.setResult(result);\n        return future;\n    }\n\n    protected int udpPayloadSize = 1232;\n\n    /**\n     * DNS timeout.\n     */\n    protected int timeout = 5000;\n\n    @Override\n    public int getTimeout() {\n        return timeout;\n    }\n\n    @Override\n    public void setTimeout(int timeout) {\n        if (timeout <= 0) {\n            throw new IllegalArgumentException(\"Timeout must be greater than zero\");\n        }\n        this.timeout = timeout;\n    }\n\n    @Override\n    public int getUdpPayloadSize() {\n        return udpPayloadSize;\n    }\n\n    public void setUdpPayloadSize(int udpPayloadSize) {\n        if (udpPayloadSize <= 0) {\n            throw new IllegalArgumentException(\"UDP payload size must be greater than zero\");\n        }\n        this.udpPayloadSize = udpPayloadSize;\n    }\n\n    private DnsCache cache;\n\n    protected final void cacheResult(DnsMessage request, DnsQueryResult response) {\n        final DnsCache activeCache = cache;\n        if (activeCache == null) {\n            return;\n        }\n        activeCache.put(request, response);\n    }\n\n    public enum QueryMode {\n        /**\n         * Perform the query mode that is assumed \"best\" for that particular case.\n         */\n        dontCare,\n\n        /**\n         * Try UDP first, and if the result is bigger than the maximum UDP payload size, or if something else goes wrong, fallback to TCP.\n         */\n        udpTcp,\n\n        /**\n         * Always use only TCP when querying DNS servers.\n         */\n        tcp,\n    }\n\n    private QueryMode queryMode = QueryMode.dontCare;\n\n    public void setQueryMode(QueryMode queryMode) {\n        if (queryMode == null) {\n            throw new IllegalArgumentException();\n        }\n        this.queryMode = queryMode;\n    }\n\n    public QueryMode getQueryMode() {\n        return queryMode;\n    }\n\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/source/DnsDataSource.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.source;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\n\nimport org.minidns.MiniDnsFuture;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\n\npublic interface DnsDataSource {\n\n    DnsQueryResult query(DnsMessage message, InetAddress address, int port) throws IOException;\n\n    MiniDnsFuture<DnsQueryResult, IOException> queryAsync(DnsMessage message, InetAddress address, int port, OnResponseCallback onResponseCallback);\n\n    int getUdpPayloadSize();\n\n    /**\n     * Retrieve the current dns query timeout, in milliseconds.\n     *\n     * @return the current dns query timeout in milliseconds.\n     */\n    int getTimeout();\n\n    /**\n     * Change the dns query timeout for all future queries. The timeout\n     * must be specified in milliseconds.\n     *\n     * @param timeout new dns query timeout in milliseconds.\n     */\n    void setTimeout(int timeout);\n\n    interface OnResponseCallback {\n        void onResponse(DnsMessage request, DnsQueryResult result);\n    }\n\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/source/NetworkDataSource.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.source;\n\nimport org.minidns.MiniDnsException;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsqueryresult.DnsQueryResult.QueryMethod;\nimport org.minidns.dnsqueryresult.StandardDnsQueryResult;\nimport org.minidns.util.MultipleIoException;\n\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.net.DatagramPacket;\nimport java.net.DatagramSocket;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\nimport java.net.SocketAddress;\nimport java.net.SocketException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\npublic class NetworkDataSource extends AbstractDnsDataSource {\n\n    protected static final Logger LOGGER = Logger.getLogger(NetworkDataSource.class.getName());\n\n    // TODO: Rename 'message' parameter to query.\n    @Override\n    public StandardDnsQueryResult query(DnsMessage message, InetAddress address, int port) throws IOException {\n        final QueryMode queryMode = getQueryMode();\n        boolean doUdpFirst;\n        switch (queryMode) {\n        case dontCare:\n        case udpTcp:\n            doUdpFirst = true;\n            break;\n        case tcp:\n            doUdpFirst = false;\n            break;\n        default:\n            throw new IllegalStateException(\"Unsupported query mode: \" + queryMode);\n        }\n\n        List<IOException> ioExceptions = new ArrayList<>(2);\n        DnsMessage dnsMessage = null;\n\n        if (doUdpFirst) {\n            try {\n                dnsMessage = queryUdp(message, address, port);\n            } catch (IOException e) {\n                ioExceptions.add(e);\n            }\n\n            // TODO: This null check could probably be removed by now.\n            if (dnsMessage != null && !dnsMessage.truncated) {\n                return new StandardDnsQueryResult(address, port, QueryMethod.udp, message, dnsMessage);\n            }\n\n            assert dnsMessage == null || dnsMessage.truncated || ioExceptions.size() == 1;\n            LOGGER.log(Level.FINE, \"Fallback to TCP because {0}\",\n                    new Object[] { dnsMessage != null ? \"response is truncated\" : ioExceptions.get(0) });\n        }\n\n        try {\n            dnsMessage = queryTcp(message, address, port);\n        } catch (IOException e) {\n            ioExceptions.add(e);\n            MultipleIoException.throwIfRequired(ioExceptions);\n        }\n\n        return new StandardDnsQueryResult(address, port, QueryMethod.tcp, message, dnsMessage);\n    }\n\n    protected DnsMessage queryUdp(DnsMessage message, InetAddress address, int port) throws IOException {\n        // TODO Use a try-with-resource statement here once miniDNS minimum\n        // required Android API level is >= 19\n        DatagramSocket socket = null;\n        DatagramPacket packet = message.asDatagram(address, port);\n        byte[] buffer = new byte[udpPayloadSize];\n        try {\n            socket = createDatagramSocket();\n            socket.setSoTimeout(timeout);\n            socket.send(packet);\n            packet = new DatagramPacket(buffer, buffer.length);\n            socket.receive(packet);\n            DnsMessage dnsMessage = new DnsMessage(packet.getData());\n            if (dnsMessage.id != message.id) {\n                throw new MiniDnsException.IdMismatch(message, dnsMessage);\n            }\n            return dnsMessage;\n        } finally {\n            if (socket != null) {\n                socket.close();\n            }\n        }\n    }\n\n    protected DnsMessage queryTcp(DnsMessage message, InetAddress address, int port) throws IOException {\n        // TODO Use a try-with-resource statement here once miniDNS minimum\n        // required Android API level is >= 19\n        Socket socket = null;\n        try {\n            socket = createSocket();\n            SocketAddress socketAddress = new InetSocketAddress(address, port);\n            socket.connect(socketAddress, timeout);\n            socket.setSoTimeout(timeout);\n            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());\n            message.writeTo(dos);\n            dos.flush();\n            DataInputStream dis = new DataInputStream(socket.getInputStream());\n            int length = dis.readUnsignedShort();\n            byte[] data = new byte[length];\n            int read = 0;\n            while (read < length) {\n                read += dis.read(data, read, length - read);\n            }\n            DnsMessage dnsMessage = new DnsMessage(data);\n            if (dnsMessage.id != message.id) {\n                throw new MiniDnsException.IdMismatch(message, dnsMessage);\n            }\n            return dnsMessage;\n        } finally {\n            if (socket != null) {\n                socket.close();\n            }\n        }\n    }\n\n    /**\n     * Create a {@link Socket} using the system default {@link javax.net.SocketFactory}.\n     *\n     * @return The new {@link Socket} instance\n     */\n    protected Socket createSocket() {\n        return new Socket();\n    }\n\n    /**\n     * Create a {@link DatagramSocket} using the system defaults.\n     *\n     * @return The new {@link DatagramSocket} instance\n     * @throws SocketException If creation of the {@link DatagramSocket} fails\n     */\n    protected DatagramSocket createDatagramSocket() throws SocketException {\n        return new DatagramSocket();\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/main/java/org/minidns/source/NetworkDataSourceWithAccounting.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.source;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.util.Locale;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport org.minidns.AbstractDnsClient;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsqueryresult.StandardDnsQueryResult;\n\npublic class NetworkDataSourceWithAccounting extends NetworkDataSource {\n\n    private final AtomicInteger successfulQueries = new AtomicInteger();\n    private final AtomicInteger responseSize = new AtomicInteger();\n    private final AtomicInteger failedQueries = new AtomicInteger();\n\n    private final AtomicInteger successfulUdpQueries = new AtomicInteger();\n    private final AtomicInteger udpResponseSize = new AtomicInteger();\n    private final AtomicInteger failedUdpQueries = new AtomicInteger();\n\n    private final AtomicInteger successfulTcpQueries = new AtomicInteger();\n    private final AtomicInteger tcpResponseSize = new AtomicInteger();\n    private final AtomicInteger failedTcpQueries = new AtomicInteger();\n\n    @Override\n    public StandardDnsQueryResult query(DnsMessage message, InetAddress address, int port) throws IOException {\n        StandardDnsQueryResult response;\n        try {\n            response = super.query(message, address, port);\n        } catch (IOException e) {\n            failedQueries.incrementAndGet();\n            throw e;\n        }\n\n        successfulQueries.incrementAndGet();\n        responseSize.addAndGet(response.response.toArray().length);\n\n        return response;\n    }\n\n    @Override\n    protected DnsMessage queryUdp(DnsMessage message, InetAddress address, int port) throws IOException {\n        DnsMessage response;\n        try {\n            response = super.queryUdp(message, address, port);\n        } catch (IOException e) {\n            failedUdpQueries.incrementAndGet();\n            throw e;\n        }\n\n        successfulUdpQueries.incrementAndGet();\n        udpResponseSize.addAndGet(response.toArray().length);\n\n        return response;\n    }\n\n    @Override\n    protected DnsMessage queryTcp(DnsMessage message, InetAddress address, int port) throws IOException {\n        DnsMessage response;\n        try {\n            response = super.queryTcp(message, address, port);\n        } catch (IOException e) {\n            failedTcpQueries.incrementAndGet();\n            throw e;\n        }\n\n        successfulTcpQueries.incrementAndGet();\n        tcpResponseSize.addAndGet(response.toArray().length);\n\n        return response;\n    }\n\n    public Stats getStats() {\n        return new Stats(this);\n    }\n\n    public static NetworkDataSourceWithAccounting from(AbstractDnsClient client) {\n        DnsDataSource ds = client.getDataSource();\n        if (ds instanceof NetworkDataSourceWithAccounting) {\n            return (NetworkDataSourceWithAccounting) ds;\n        }\n        return null;\n    }\n\n    public static final class Stats {\n        public final int successfulQueries;\n        public final int responseSize;\n        public final int averageResponseSize;\n        public final int failedQueries;\n\n        public final int successfulUdpQueries;\n        public final int udpResponseSize;\n        public final int averageUdpResponseSize;\n        public final int failedUdpQueries;\n\n        public final int successfulTcpQueries;\n        public final int tcpResponseSize;\n        public final int averageTcpResponseSize;\n        public final int failedTcpQueries;\n\n        private String stringCache;\n\n        private Stats(NetworkDataSourceWithAccounting ndswa) {\n            successfulQueries = ndswa.successfulQueries.get();\n            responseSize = ndswa.responseSize.get();\n            failedQueries = ndswa.failedQueries.get();\n\n            successfulUdpQueries = ndswa.successfulUdpQueries.get();\n            udpResponseSize = ndswa.udpResponseSize.get();\n            failedUdpQueries = ndswa.failedUdpQueries.get();\n\n            successfulTcpQueries = ndswa.successfulTcpQueries.get();\n            tcpResponseSize = ndswa.tcpResponseSize.get();\n            failedTcpQueries = ndswa.failedTcpQueries.get();\n\n            // Calculated stats section\n            averageResponseSize = successfulQueries > 0 ? responseSize / successfulQueries : 0;\n            averageUdpResponseSize = successfulUdpQueries > 0 ? udpResponseSize / successfulUdpQueries : 0;\n            averageTcpResponseSize = successfulTcpQueries > 0 ? tcpResponseSize / successfulTcpQueries : 0;\n        }\n\n        @Override\n        public String toString() {\n            if (stringCache != null)\n                return stringCache;\n\n            StringBuilder sb = new StringBuilder();\n\n            sb.append(\"Stats\\t\").append(\"# Successful\").append('\\t').append(\"# Failed\").append('\\t')\n                    .append(\"Resp. Size\").append('\\t').append(\"Avg. Resp. Size\").append('\\n');\n            sb.append(\"Total\\t\").append(toString(successfulQueries)).append('\\t').append(toString(failedQueries))\n                    .append('\\t').append(toString(responseSize)).append('\\t').append(toString(averageResponseSize))\n                    .append('\\n');\n            sb.append(\"UDP\\t\").append(toString(successfulUdpQueries)).append('\\t').append(toString(failedUdpQueries))\n                    .append('\\t').append(toString(udpResponseSize)).append('\\t')\n                    .append(toString(averageUdpResponseSize)).append('\\n');\n            sb.append(\"TCP\\t\").append(toString(successfulTcpQueries)).append('\\t').append(toString(failedTcpQueries))\n                    .append('\\t').append(toString(tcpResponseSize)).append('\\t')\n                    .append(toString(averageTcpResponseSize)).append('\\n');\n\n            stringCache = sb.toString();\n            return stringCache;\n        }\n\n        private static String toString(int i) {\n            return String.format(Locale.US, \"%,09d\", i);\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/main/resources/de.measite.minidns/.keep",
    "content": ""
  },
  {
    "path": "minidns-client/src/test/java/org/minidns/DnsClientTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns;\n\nimport org.minidns.cache.LruCache;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.dnsqueryresult.TestWorldDnsQueryResult;\nimport org.minidns.dnsserverlookup.AbstractDnsServerLookupMechanism;\nimport org.minidns.dnsserverlookup.AndroidUsingExec;\nimport org.minidns.dnsserverlookup.AndroidUsingReflection;\nimport org.minidns.dnsserverlookup.DnsServerLookupMechanism;\nimport org.minidns.record.A;\nimport org.minidns.record.Record.TYPE;\nimport org.minidns.source.AbstractDnsDataSource;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.minidns.DnsWorld.a;\nimport static org.minidns.DnsWorld.applyStubRecords;\nimport static org.minidns.DnsWorld.record;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class DnsClientTest {\n\n    @Test\n    public void testLookupMechanismOrder() {\n        DnsClient.addDnsServerLookupMechanism(new TestDnsServerLookupMechanism(AndroidUsingExec.INSTANCE));\n        DnsClient.addDnsServerLookupMechanism(new TestDnsServerLookupMechanism(AndroidUsingReflection.INSTANCE));\n\n        List<DnsServerLookupMechanism> expectedOrder = new ArrayList<>();\n        expectedOrder.add(0, AndroidUsingExec.INSTANCE);\n        expectedOrder.add(1, AndroidUsingReflection.INSTANCE);\n        for (DnsServerLookupMechanism mechanism : DnsClient.LOOKUP_MECHANISMS) {\n            if (expectedOrder.isEmpty()) {\n                break;\n            }\n            DnsServerLookupMechanism shouldBeRemovedNext = expectedOrder.get(0);\n            if (mechanism.getName().equals(shouldBeRemovedNext.getName())) {\n                expectedOrder.remove(0);\n            }\n        }\n        assertTrue(expectedOrder.isEmpty());\n    }\n\n    private static class TestDnsServerLookupMechanism extends AbstractDnsServerLookupMechanism {\n        protected TestDnsServerLookupMechanism(DnsServerLookupMechanism lookupMechanism) {\n            super(lookupMechanism.getName(), lookupMechanism.getPriority());\n        }\n        @Override\n        public boolean isAvailable() {\n            return true;\n        }\n        @Override\n        public List<String> getDnsServerAddresses() {\n            return null;\n        }\n    }\n\n    @Test\n    public void testSingleRecordQuery() throws IOException {\n        DnsClient client = new DnsClient(new LruCache(0));\n        applyStubRecords(client, record(\"www.example.com\", a(\"127.0.0.1\")));\n        DnsQueryResult result = client.query(\"www.example.com\", TYPE.A);\n        DnsMessage response = result.response;\n        assertNotNull(response);\n        assertEquals(1, response.answerSection.size());\n        assertEquals(TYPE.A, response.answerSection.get(0).type);\n        assertArrayEquals(new byte[] {127, 0, 0, 1}, ((A) response.answerSection.get(0).payloadData).getIp());\n\n        result = client.query(\"www2.example.com\", TYPE.A);\n        assertEquals(RESPONSE_CODE.NX_DOMAIN, result.response.responseCode);\n\n        result = client.query(\"www.example.com\", TYPE.CNAME);\n        assertEquals(RESPONSE_CODE.NX_DOMAIN, result.response.responseCode);\n    }\n\n    @Test\n    public void testReturnNullSource() throws IOException {\n        class NullSource extends AbstractDnsDataSource {\n            boolean queried = false;\n\n            @Override\n            public DnsQueryResult query(DnsMessage message, InetAddress address, int port) {\n                queried = true;\n                DnsMessage response = message.getResponseBuilder(RESPONSE_CODE.NO_ERROR)\n                        .setRecursionAvailable(true)\n                        .build();\n                return new TestWorldDnsQueryResult(message, response);\n            }\n        }\n        DnsClient client = new DnsClient(new LruCache(0));\n        NullSource source = new NullSource();\n        client.setDataSource(source);\n        DnsQueryResult message = client.query(\"www.example.com\", TYPE.A);\n        assertTrue(source.queried);\n        assertNotNull(message);\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/test/java/org/minidns/DnsWorld.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns;\n\nimport org.minidns.constants.DnsRootServer;\nimport org.minidns.constants.DnssecConstants.DigestAlgorithm;\nimport org.minidns.constants.DnssecConstants.SignatureAlgorithm;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.dnsqueryresult.TestWorldDnsQueryResult;\nimport org.minidns.record.A;\nimport org.minidns.record.AAAA;\nimport org.minidns.record.CNAME;\nimport org.minidns.record.DLV;\nimport org.minidns.record.DNSKEY;\nimport org.minidns.record.DS;\nimport org.minidns.record.Data;\nimport org.minidns.record.MX;\nimport org.minidns.record.NS;\nimport org.minidns.record.NSEC;\nimport org.minidns.record.NSEC3;\nimport org.minidns.record.RRSIG;\nimport org.minidns.record.Record;\nimport org.minidns.record.SOA;\nimport org.minidns.record.SRV;\nimport org.minidns.record.Record.CLASS;\nimport org.minidns.record.Record.TYPE;\nimport org.minidns.source.AbstractDnsDataSource;\nimport org.minidns.util.InetAddressUtil;\n\nimport java.net.Inet4Address;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\npublic class DnsWorld extends AbstractDnsDataSource {\n    private List<PreparedResponse> answers = new ArrayList<>();\n\n    private final Map<DnsName, Map<TYPE, RrSet>> worldData = new HashMap<>();\n\n    @Override\n    public DnsQueryResult query(DnsMessage message, InetAddress address, int port) {\n        assertNotNull(message);\n        assertNotNull(address);\n        assertEquals(53, port);\n\n        for (PreparedResponse answer : answers) {\n            if (answer.isResponse(message, address)) {\n                DnsMessage.Builder response = answer.getResponse().asBuilder();\n                response.setId(message.id);\n                response.setQuestions(message.questions);\n                return new TestWorldDnsQueryResult(message, response.build(), answer);\n            }\n        }\n\n        DnsMessage nxDomainResponse = message\n                .getResponseBuilder(RESPONSE_CODE.NX_DOMAIN)\n                // TODO: This RA is faked and eventually causes problems.\n                .setRecursionAvailable(true)\n                .setAuthoritativeAnswer(true)\n                .build();\n        return new TestWorldDnsQueryResult(message, nxDomainResponse);\n    }\n\n    public void addPreparedResponse(PreparedResponse answer) {\n        answers.add(answer);\n    }\n\n    public interface PreparedResponse {\n        boolean isResponse(DnsMessage request, InetAddress address);\n\n        DnsMessage getResponse();\n    }\n\n    public static class AnswerResponse implements PreparedResponse {\n        final DnsMessage request;\n        final DnsMessage response;\n\n        public AnswerResponse(DnsMessage request, DnsMessage response) {\n            this.request = request;\n            this.response = response;\n        }\n\n        @Override\n        public boolean isResponse(DnsMessage request, InetAddress address) {\n            List<Question> questions = this.request.copyQuestions();\n            for (Question q : request.questions) {\n                if (!hasQuestion(questions, q)) {\n                    return false;\n                }\n            }\n            return questions.isEmpty();\n        }\n\n        @Override\n        public DnsMessage getResponse() {\n            return response;\n        }\n\n        private static boolean hasQuestion(Collection<Question> questions, Question q) {\n            for (Iterator<Question> iterator = questions.iterator(); iterator.hasNext(); ) {\n                if (iterator.next().equals(q)) {\n                    iterator.remove();\n                    return true;\n                }\n            }\n            return false;\n        }\n\n        @Override\n        public String toString() {\n            return\n                    \"req: \" + request + '\\n'\n                  + \"res: \" + response + '\\n';\n        }\n    }\n\n    public static class RootAnswerResponse extends AnswerResponse {\n\n        public RootAnswerResponse(DnsMessage request, DnsMessage response) {\n            super(request, response);\n        }\n\n        @Override\n        public boolean isResponse(DnsMessage request, InetAddress address) {\n            return address.getHostName().endsWith(\".root-servers.net\") && super.isResponse(request, address);\n        }\n\n        @Override\n        public String toString() {\n            return getClass().getSimpleName() + '\\n' + super.toString();\n        }\n    }\n\n    public static class AddressedAnswerResponse extends AnswerResponse {\n\n        final InetAddress address;\n\n        public AddressedAnswerResponse(InetAddress address, DnsMessage request, DnsMessage response) {\n            super(request, response);\n            this.address = address;\n        }\n\n        @Override\n        public boolean isResponse(DnsMessage request, InetAddress address) {\n            return address.equals(this.address) && super.isResponse(request, address);\n        }\n\n        @Override\n        public String toString() {\n            return getClass().getSimpleName() + \": \" + address + '\\n' + super.toString();\n        }\n    }\n\n    public abstract static class HintsResponse implements PreparedResponse {\n        final DnsName ending;\n        final DnsMessage response;\n\n        public HintsResponse(DnsName ending, DnsMessage response) {\n            this.ending = ending;\n            this.response = response;\n        }\n\n        boolean questionHintable(DnsMessage request) {\n            for (Question question : request.questions) {\n                if (question.name.isChildOf(ending) || question.name.equals(ending)) {\n                    return true;\n                }\n            }\n            return false;\n        }\n\n        @Override\n        public DnsMessage getResponse() {\n            return response;\n        }\n\n        @Override\n        public String toString() {\n            return\n                    getClass().getSimpleName() + \": \" + ending + '\\n'\n                  + response;\n        }\n    }\n\n    public static class RootHintsResponse extends HintsResponse {\n\n        public RootHintsResponse(DnsName ending, DnsMessage response) {\n            super(ending, response);\n        }\n\n        @Override\n        public boolean isResponse(DnsMessage request, InetAddress address) {\n            // TODO: It appears that we shouldn't hint down to the nameserver if the query is about a 'DS' RR. Because\n            // they have to get answered at the parental part of the zone cut.\n            if (request.getQuestion().type == TYPE.DS) {\n                return false;\n            }\n            return address.getHostName().endsWith(\".root-servers.net\") && questionHintable(request);\n        }\n    }\n\n    public static class AddressedHintsResponse extends HintsResponse {\n        final InetAddress address;\n\n        public AddressedHintsResponse(InetAddress address, DnsName ending, DnsMessage response) {\n            super(ending, response);\n            this.address = address;\n        }\n\n        @Override\n        public boolean isResponse(DnsMessage request, InetAddress address) {\n            return address.equals(this.address) && questionHintable(request);\n        }\n\n        @Override\n        public String toString() {\n            return\n                    getClass().getSimpleName() + \": \" + address + '\\n'\n                  + response;\n        }\n    }\n\n    public static class Zone {\n        // TODO: Change type of zoneName to DnsName and make fields final.\n        String zoneName;\n        InetAddress address;\n        List<Record<? extends Data>> records;\n\n        public Zone(String zoneName, InetAddress address, List<Record<? extends Data>> records) {\n            this.zoneName = zoneName;\n            this.address = address;\n            this.records = records;\n        }\n\n        public List<RrSet> getRRSets() {\n            List<RrSet.Builder> rrSetBuilders = new ArrayList<>();\n            outerloop: for (Record<? extends Data> record : records) {\n                for (RrSet.Builder builder : rrSetBuilders) {\n                    if (builder.addIfPossible(record)) {\n                        continue outerloop;\n                    }\n                }\n                rrSetBuilders.add(RrSet.builder().addRecord(record));\n            }\n            List<RrSet> rrSets = new ArrayList<>(rrSetBuilders.size());\n            for (RrSet.Builder builder : rrSetBuilders) {\n                rrSets.add(builder.build());\n            }\n            return rrSets;\n        }\n\n        boolean isRootZone() {\n            return (zoneName == null || zoneName.isEmpty()) && address == null;\n        }\n    }\n\n    public static DnsWorld applyZones(AbstractDnsClient client, Zone... zones) {\n        DnsWorld world = new DnsWorld();\n        client.setDataSource(world);\n        for (Zone zone : zones) {\n            for (RrSet rrSet : zone.getRRSets()) {\n                // A zone may have glue RR sets, so we need use rrSet.name as zoneName here.\n                DnsName zoneName = rrSet.name;\n                Map<TYPE, RrSet> zoneData = world.worldData.get(zoneName);\n                if (zoneData == null) {\n                    zoneData = new HashMap<>();\n                    world.worldData.put(zoneName, zoneData);\n                }\n\n                // TODO: Shouldn't we try to merge with a previously existing rrSet of the same type instead of\n                // overriding it? Or does this not happen by construction?\n                zoneData.put(rrSet.type, rrSet);\n\n                DnsMessage.Builder req = client.buildMessage(new Question(rrSet.name, rrSet.type, rrSet.clazz));\n                DnsMessage.Builder resp = DnsMessage.builder();\n                resp.setAnswers(rrSet.records);\n                resp.setAuthoritativeAnswer(true);\n                attachGlues(resp, rrSet.records, zone.records);\n                attachSignatures(resp, zone.records);\n                DnsMessage request = req.build();\n                DnsMessage response = resp.build();\n                if (zone.isRootZone()) {\n                    world.addPreparedResponse(new RootAnswerResponse(request, response));\n                } else {\n                    world.addPreparedResponse(new AddressedAnswerResponse(zone.address, request, response));\n                }\n                if (rrSet.type == TYPE.NS) {\n                    DnsMessage.Builder hintsResp = DnsMessage.builder();\n                    hintsResp.setNameserverRecords(rrSet.records);\n                    hintsResp.setAdditionalResourceRecords(response.additionalSection);\n                    DnsMessage hintsResponse = hintsResp.build();\n                    if (zone.isRootZone()) {\n                        world.addPreparedResponse(new RootHintsResponse(rrSet.name, hintsResponse));\n                    } else {\n                        world.addPreparedResponse(new AddressedHintsResponse(zone.address, rrSet.name, hintsResponse));\n                    }\n                }\n            }\n        }\n        return world;\n    }\n\n    static void attachSignatures(DnsMessage.Builder response, List<Record<? extends Data>> records) {\n        List<Record<? extends Data>> recordList = new ArrayList<>(records.size());\n        for (Record<? extends Data> record : response.getAnswers()) {\n            for (Record<? extends Data> r : records) {\n                if (r.name.equals(record.name) && r.type == TYPE.RRSIG && ((RRSIG) r.payloadData).typeCovered == record.type) {\n                    recordList.add(r);\n                }\n            }\n        }\n        response.addAnswers(recordList);\n\n        recordList.clear();\n\n        for (Record<? extends Data> record : response.getAdditionalResourceRecords()) {\n            for (Record<? extends Data> r : records) {\n                if (r.name.equals(record.name) && r.type == TYPE.RRSIG && ((RRSIG) r.payloadData).typeCovered == record.type) {\n                    recordList.add(r);\n                }\n            }\n        }\n        response.addAdditionalResourceRecords(recordList);\n    }\n\n    static void attachGlues(DnsMessage.Builder response, Collection<Record<? extends Data>> answers, List<Record<? extends Data>> records) {\n        List<Record<? extends Data>> glues = new ArrayList<>();\n        for (Record<? extends Data> record : answers) {\n            if (record.type == TYPE.CNAME) {\n                glues.addAll(findGlues(((CNAME) record.payloadData).target, records));\n            } else if (record.type == TYPE.NS) {\n                glues.addAll(findGlues(((NS) record.payloadData).target, records));\n            } else if (record.type == TYPE.SRV) {\n                glues.addAll(findGlues(((SRV) record.payloadData).target, records));\n            }\n        }\n\n        if (!glues.isEmpty()) {\n            response.setAdditionalResourceRecords(glues);\n        }\n    }\n\n    private static List<Record<? extends Data>> findGlues(DnsName name, List<Record<? extends Data>> records) {\n        List<Record<? extends Data>> glues = new ArrayList<>();\n        for (Record<? extends Data> record : records) {\n            if (record.name.equals(name)) {\n                if (record.type == TYPE.CNAME) {\n                    glues.addAll(findGlues(((CNAME) record.payloadData).target, records));\n                } else if (record.type == TYPE.A || record.type == TYPE.AAAA) {\n                    glues.add(record);\n                }\n            }\n        }\n        return glues;\n    }\n\n    @SafeVarargs\n    public static DnsWorld applyStubRecords(AbstractDnsClient client, Record<? extends Data>... records) {\n        DnsWorld world = new DnsWorld();\n        client.setDataSource(world);\n        for (Record<? extends Data> record : records) {\n            DnsMessage.Builder request = client.buildMessage(new Question(record.name, record.type, record.clazz, record.unicastQuery));\n            request.setRecursionDesired(true);\n            DnsMessage.Builder response = DnsMessage.builder();\n            response.addAnswer(record);\n            response.setRecursionAvailable(true);\n            world.addPreparedResponse(new AnswerResponse(request.build(), response.build()));\n        }\n        return world;\n    }\n\n    @SafeVarargs\n    public static Zone rootZone(Record<? extends Data>... records) {\n        List<Record<? extends Data>> listOfRecords = new ArrayList<>(records.length);\n        for (Record<? extends Data> record : records) {\n            listOfRecords.add(record);\n        }\n        return rootZone(listOfRecords);\n    }\n\n    public static Zone rootZone(List<Record<? extends Data>> records) {\n        return new Zone(\"\", null, records);\n    }\n\n    @SafeVarargs\n    public static Zone zone(String zoneName, String nsName, String nsIp, Record<? extends Data>... records) {\n        List<Record<? extends Data>> listOfRecords = new ArrayList<>(records.length);\n        for (Record<? extends Data> record : records) {\n            listOfRecords.add(record);\n        }\n        return zone(zoneName, nsName, nsIp, listOfRecords);\n    }\n\n    public static Zone zone(String zoneName, String nsName, String nsIp, List<Record<? extends Data>> records) {\n        Inet4Address inet4Address = InetAddressUtil.ipv4From(nsIp);\n        try {\n            return zone(zoneName, InetAddress.getByAddress(nsName, inet4Address.getAddress()), records);\n        } catch (UnknownHostException e) {\n            // This will never happen, as we already ensured the validity of the IP address by using parseIpV4()\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static Zone zone(String zoneName, InetAddress address, List<Record<? extends Data>> records) {\n        return new Zone(zoneName, address, records);\n    }\n\n    public static <D extends Data> Record<D> record(String name, long ttl, D data) {\n        return new Record<D>(name, data.getType(), CLASS.IN, ttl, data, false);\n    }\n\n    public static <D extends Data> Record<D> record(DnsName name, long ttl, D data) {\n        return new Record<D>(name, data.getType(), CLASS.IN, ttl, data, false);\n    }\n\n    public static <D extends Data> Record<D> record(String name, D data) {\n        return record(name, 3600, data);\n    }\n\n    public static <D extends Data> Record<D> record(DnsName name, D data) {\n        return record(name, 3600, data);\n    }\n\n    public static A a(byte[] ip) {\n        return new A(ip);\n    }\n\n    public static A a(CharSequence ipCharSequence) {\n        return new A(ipCharSequence);\n    }\n\n    public static AAAA aaaa(byte[] ip) {\n        return new AAAA(ip);\n    }\n\n    public static AAAA CharSequence(CharSequence ipCharSequence) {\n        return new AAAA(ipCharSequence);\n    }\n\n    public static CNAME cname(String name) {\n        return cname(DnsName.from(name));\n    }\n\n    public static CNAME cname(DnsName name) {\n        return new CNAME(name);\n    }\n\n    public static DNSKEY dnskey(int flags, int protocol, SignatureAlgorithm algorithm, byte[] key) {\n        return new DNSKEY((short) flags, (byte) protocol, algorithm, key);\n    }\n\n    public static DNSKEY dnskey(int flags, SignatureAlgorithm algorithm, byte[] key) {\n        return dnskey(flags, DNSKEY.PROTOCOL_RFC4034, algorithm, key);\n    }\n\n    public static DS ds(int keyTag, SignatureAlgorithm algorithm, DigestAlgorithm digestType, byte[] digest) {\n        return new DS(keyTag, algorithm, digestType, digest);\n    }\n\n    public static DS ds(int keyTag, SignatureAlgorithm algorithm, byte digestType, byte[] digest) {\n        return new DS(keyTag, algorithm, digestType, digest);\n    }\n\n    public static DLV dlv(int keyTag, SignatureAlgorithm algorithm, DigestAlgorithm digestType, byte[] digest) {\n        return new DLV(keyTag, algorithm, digestType, digest);\n    }\n\n    public static MX mx(int priority, String name) {\n        return mx(priority, DnsName.from(name));\n    }\n\n    public static MX mx(int priority, DnsName name) {\n        return new MX(priority, name);\n    }\n\n    public static MX mx(String name) {\n        return mx(10, name);\n    }\n\n    public static NS ns(String name) {\n        return ns(DnsName.from(name));\n    }\n\n    public static NS ns(DnsName name) {\n        return new NS(name);\n    }\n\n    public static NSEC nsec(String next, TYPE... types) {\n        return nsec(DnsName.from(next), types);\n    }\n\n    public static NSEC nsec(DnsName next, TYPE... types) {\n        List<TYPE> typesList = Arrays.asList(types);\n        return new NSEC(next, typesList);\n    }\n\n    public static NSEC3 nsec3(byte hashAlgorithm, byte flags, int iterations, byte[] salt, byte[] nextHashed, TYPE... types) {\n        List<TYPE> typesList = Arrays.asList(types);\n        return new NSEC3(hashAlgorithm, flags, iterations, salt, nextHashed, typesList);\n    }\n\n    public static RRSIG rrsig(TYPE typeCovered, SignatureAlgorithm algorithm, int labels, long originalTtl, Date signatureExpiration,\n                              Date signatureInception, int keyTag, String signerName, byte[] signature) {\n        return rrsig(typeCovered, algorithm, (byte) labels, originalTtl, signatureExpiration,\n                signatureInception, keyTag, DnsName.from(signerName), signature);\n    }\n\n    public static RRSIG rrsig(TYPE typeCovered, SignatureAlgorithm algorithm, int labels, long originalTtl,\n            Date signatureExpiration, Date signatureInception, int keyTag, DnsName signerName, byte[] signature) {\n        return new RRSIG(typeCovered, algorithm, (byte) labels, originalTtl, signatureExpiration, signatureInception,\n                keyTag, signerName, signature);\n    }\n\n    public static RRSIG rrsig(TYPE typeCovered, int algorithm,\n            int labels, long originalTtl, Date signatureExpiration,\n            Date signatureInception, int keyTag, String signerName,\n            byte[] signature) {\n        return rrsig(typeCovered, algorithm, (byte) labels,\n                originalTtl, signatureExpiration, signatureInception, keyTag,\n                DnsName.from(signerName), signature);\n    }\n\n    public static RRSIG rrsig(TYPE typeCovered, int algorithm,\n            int labels, long originalTtl, Date signatureExpiration,\n            Date signatureInception, int keyTag, DnsName signerName,\n            byte[] signature) {\n        return new RRSIG(typeCovered, algorithm, (byte) labels,\n                originalTtl, signatureExpiration, signatureInception, keyTag,\n                signerName, signature);\n    }\n\n    public static SOA soa(String mname, String rname, long serial, int refresh, int retry, int expire, long minimum) {\n        return soa(DnsName.from(mname), DnsName.from(rname), serial, refresh, retry, expire, minimum);\n    }\n\n    public static SOA soa(DnsName mname, DnsName rname, long serial, int refresh, int retry, int expire, long minimum) {\n        return new SOA(mname, rname, serial, refresh, retry, expire, minimum);\n    }\n\n    public static SRV srv(int priority, int weight, int port, String name) {\n        return srv(priority, weight, port, DnsName.from(name));\n    }\n\n    public static SRV srv(int priority, int weight, int port, DnsName name) {\n        return new SRV(priority, weight, port, name);\n    }\n\n    public static SRV srv(int port, String name) {\n        return srv(10, 10, port, name);\n    }\n\n    public RrSet lookupRrSetFor(DnsName name, TYPE type) {\n        Map<TYPE, RrSet> zoneData = worldData.get(name);\n        if (zoneData == null) {\n            return null;\n        }\n\n        return zoneData.get(type);\n    }\n\n    public InetAddress lookupSingleAuthoritativeNameserverForZone(DnsName zone) {\n        if (zone.isRootLabel()) {\n            return DnsRootServer.getIpv4RootServerById('a');\n        }\n\n        RrSet nsRrSet = lookupRrSetFor(zone, TYPE.NS);\n        if (nsRrSet == null) {\n            throw new IllegalStateException();\n        }\n\n        @SuppressWarnings(\"unchecked\")\n        Record<NS> nsRecord = (Record<NS>) nsRrSet.records.iterator().next();\n\n        RrSet aRrSet = lookupRrSetFor(nsRecord.name, TYPE.A);\n        if (aRrSet == null) {\n            throw new IllegalStateException();\n        }\n\n        @SuppressWarnings(\"unchecked\")\n        Record<A> aRecord = (Record<A>) aRrSet.records.iterator().next();\n\n        try {\n            return InetAddress.getByAddress(nsRecord.name.toString(), aRecord.payloadData.getIp());\n        } catch (UnknownHostException e) {\n            throw new AssertionError(e);\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/test/java/org/minidns/LruCacheTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.minidns.cache.LruCache;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsqueryresult.TestWorldDnsQueryResult;\nimport org.minidns.record.Record;\n\nimport static org.minidns.DnsWorld.a;\nimport static org.minidns.DnsWorld.ns;\nimport static org.minidns.DnsWorld.record;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\n\npublic class LruCacheTest {\n\n    @Test\n    public void testOutdatedCacheEntry() {\n        LruCache lruCache = new LruCache(5);\n\n        Question q = new Question(\"\", Record.TYPE.A);\n        TestWorldDnsQueryResult result = createSampleMessage(q, 1);\n        DnsMessage question = q.asQueryMessage();\n        lruCache.put(question, result);\n\n        assertNull(lruCache.get(question));\n        assertNull(lruCache.get(question));\n        assertEquals(1, lruCache.getExpireCount());\n        assertEquals(2, lruCache.getMissCount());\n    }\n\n    @Test\n    public void testOverfilledCache() {\n        LruCache lruCache = new LruCache(5);\n\n        Question firstQuestion = new Question(\"\", Record.TYPE.A);\n        lruCache.put(firstQuestion.asQueryMessage(), createSampleMessage(firstQuestion));\n        assertNotNull(lruCache.get(firstQuestion.asQueryMessage()));\n\n        Question question;\n        question = new Question(\"1\", Record.TYPE.A);\n        lruCache.put(question.asQueryMessage(), createSampleMessage(question));\n        question = new Question(\"2\", Record.TYPE.A);\n        lruCache.put(question.asQueryMessage(), createSampleMessage(question));\n        question = new Question(\"3\", Record.TYPE.A);\n        lruCache.put(question.asQueryMessage(), createSampleMessage(question));\n        question = new Question(\"4\", Record.TYPE.A);\n        lruCache.put(question.asQueryMessage(), createSampleMessage(question));\n        question = new Question(\"5\", Record.TYPE.A);\n        lruCache.put(question.asQueryMessage(), createSampleMessage(question));\n\n        assertNull(lruCache.get(firstQuestion.asQueryMessage()));\n        assertEquals(0, lruCache.getExpireCount());\n        assertEquals(1, lruCache.getMissCount());\n        assertEquals(1, lruCache.getHitCount());\n    }\n\n    private static TestWorldDnsQueryResult createSampleMessage(Question question) {\n        return createSampleMessage(question, System.currentTimeMillis());\n    }\n\n    private static TestWorldDnsQueryResult createSampleMessage(Question question, long receiveTimestamp) {\n        DnsMessage.Builder message = DnsMessage.builder();\n        message.setReceiveTimestamp(receiveTimestamp);\n        message.addAnswer(record(\"\", ns(\"a.root-servers.net\")));\n        message.addAdditionalResourceRecord(record(\"a.root-servers.net\", a(\"127.0.0.1\")));\n        DnsMessage responseMessage = message.build();\n        DnsMessage query = question.asQueryMessage();\n        return new TestWorldDnsQueryResult(query, responseMessage);\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/test/java/org/minidns/dnsqueryresult/TestWorldDnsQueryResult.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsqueryresult;\n\nimport org.minidns.DnsWorld.PreparedResponse;\nimport org.minidns.dnsmessage.DnsMessage;\n\npublic class TestWorldDnsQueryResult extends DnsQueryResult {\n\n    public final PreparedResponse preparedResponse;\n\n    public TestWorldDnsQueryResult(DnsMessage query, DnsMessage response) {\n        this(query, response, null);\n    }\n\n    public TestWorldDnsQueryResult(DnsMessage query, DnsMessage response, PreparedResponse preparedResponse) {\n        super(QueryMethod.testWorld, query, response);\n        this.preparedResponse = preparedResponse;\n    }\n\n}\n"
  },
  {
    "path": "minidns-client/src/test/java/org/minidns/dnsserverlookup/AndroidUsingExecTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsserverlookup;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.net.UnknownHostException;\nimport java.util.Set;\n\nimport org.junit.jupiter.api.Test;\n\npublic class AndroidUsingExecTest {\n\n    private static final String PROPS_WITH_NEWLINE = \"[property.name]: [\\n\" +\n            \"]\\n\";\n\n    @Test\n    public void parsePropsWithNewlineTest() throws UnknownHostException, IOException {\n        Reader reader = new StringReader(PROPS_WITH_NEWLINE);\n        BufferedReader bufferedReader = new BufferedReader(reader);\n\n        Set<String> servers = AndroidUsingExec.parseProps(bufferedReader, false);\n\n        assertTrue(servers.isEmpty());\n    }\n}\n"
  },
  {
    "path": "minidns-client/src/test/java/org/minidns/source/NetworkDataSourceTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.source;\n\nimport org.junit.jupiter.api.Test;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class NetworkDataSourceTest {\n\n    @Test\n    public void udpTruncatedTcpFallbackTest() throws IOException {\n        final int tcpResponseId = 42;\n        class TestNetworkDataSource extends NetworkDataSource {\n            boolean lastQueryUdp = false;\n\n            @Override\n            protected DnsMessage queryUdp(DnsMessage message, InetAddress address, int port) throws IOException {\n                assertFalse(lastQueryUdp);\n                lastQueryUdp = true;\n                DnsMessage.Builder msg = DnsMessage.builder();\n                msg.setTruncated(true);\n                return msg.build();\n            }\n\n            @Override\n            protected DnsMessage queryTcp(DnsMessage message, InetAddress address, int port) throws IOException {\n                assertTrue(lastQueryUdp);\n                lastQueryUdp = false;\n                return DnsMessage.builder().setId(tcpResponseId).build();\n            }\n        }\n\n        TestNetworkDataSource world = new TestNetworkDataSource();\n        DnsQueryResult result = world.query(DnsMessage.builder().build(), null, 53);\n        assertEquals(tcpResponseId, result.response.id);\n        assertFalse(world.lastQueryUdp);\n    }\n}\n"
  },
  {
    "path": "minidns-core/build.gradle",
    "content": "plugins {\n\tid 'org.minidns.java-conventions'\n\tid 'org.minidns.android-conventions'\n}\n\ndescription = \"MiniDNS' core classes and functionality\"\n\nclass CreateFileTask extends DefaultTask {\n\t@Input\n\tString fileContent\n\n\t@OutputFile\n\tFile outputFile\n\n\t@TaskAction\n\tdef createFile() {\n\t\toutputFile.text = fileContent\n\t}\n}\n\ntask createVersionResource(type: CreateFileTask) {\n\tfileContent = version + ' (' + gitCommit + ')'\n\toutputFile = new File(projectDir, 'src/main/resources/org.minidns/version')\n}\n\nprocessResources.dependsOn(createVersionResource)\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/constants/DnsRootServer.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.constants;\n\nimport java.net.Inet4Address;\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Random;\n\npublic class DnsRootServer {\n\n    private static final Map<Character, Inet4Address> IPV4_ROOT_SERVER_MAP = new HashMap<>();\n\n    private static final Map<Character, Inet6Address> IPV6_ROOT_SERVER_MAP = new HashMap<>();\n\n    protected static final Inet4Address[] IPV4_ROOT_SERVERS = new Inet4Address[] {\n            rootServerInet4Address('a', 198,  41,   0,   4),\n            rootServerInet4Address('b', 192, 228,  79, 201),\n            rootServerInet4Address('c', 192,  33,   4,  12),\n            rootServerInet4Address('d', 199,   7,  91 , 13),\n            rootServerInet4Address('e', 192, 203, 230,  10),\n            rootServerInet4Address('f', 192,   5,   5, 241),\n            rootServerInet4Address('g', 192, 112,  36,   4),\n            rootServerInet4Address('h', 198,  97, 190,  53),\n            rootServerInet4Address('i', 192,  36, 148,  17),\n            rootServerInet4Address('j', 192,  58, 128,  30),\n            rootServerInet4Address('k', 193,   0,  14, 129),\n            rootServerInet4Address('l', 199,   7,  83,  42),\n            rootServerInet4Address('m', 202,  12,  27,  33),\n        };\n\n        protected static final Inet6Address[] IPV6_ROOT_SERVERS = new Inet6Address[] {\n            rootServerInet6Address('a', 0x2001, 0x0503, 0xba3e, 0x0000, 0x0000, 0x000, 0x0002, 0x0030),\n            rootServerInet6Address('b', 0x2001, 0x0500, 0x0084, 0x0000, 0x0000, 0x000, 0x0000, 0x000b),\n            rootServerInet6Address('c', 0x2001, 0x0500, 0x0002, 0x0000, 0x0000, 0x000, 0x0000, 0x000c),\n            rootServerInet6Address('d', 0x2001, 0x0500, 0x002d, 0x0000, 0x0000, 0x000, 0x0000, 0x000d),\n            rootServerInet6Address('f', 0x2001, 0x0500, 0x002f, 0x0000, 0x0000, 0x000, 0x0000, 0x000f),\n            rootServerInet6Address('h', 0x2001, 0x0500, 0x0001, 0x0000, 0x0000, 0x000, 0x0000, 0x0053),\n            rootServerInet6Address('i', 0x2001, 0x07fe, 0x0000, 0x0000, 0x0000, 0x000, 0x0000, 0x0053),\n            rootServerInet6Address('j', 0x2001, 0x0503, 0x0c27, 0x0000, 0x0000, 0x000, 0x0002, 0x0030),\n            rootServerInet6Address('l', 0x2001, 0x0500, 0x0003, 0x0000, 0x0000, 0x000, 0x0000, 0x0042),\n            rootServerInet6Address('m', 0x2001, 0x0dc3, 0x0000, 0x0000, 0x0000, 0x000, 0x0000, 0x0035),\n        };\n\n        private static Inet4Address rootServerInet4Address(char rootServerId, int addr0, int addr1, int addr2, int addr3) {\n            Inet4Address inetAddress;\n            String name = rootServerId + \".root-servers.net\";\n                try {\n                    inetAddress = (Inet4Address) InetAddress.getByAddress(name, new byte[] { (byte) addr0, (byte) addr1, (byte) addr2,\n                            (byte) addr3 });\n                    IPV4_ROOT_SERVER_MAP.put(rootServerId, inetAddress);\n                } catch (UnknownHostException e) {\n                    // This should never happen, if it does it's our fault!\n                    throw new RuntimeException(e);\n                }\n\n            return inetAddress;\n        }\n\n        private static Inet6Address rootServerInet6Address(char rootServerId, int addr0, int addr1, int addr2, int addr3, int addr4, int addr5, int addr6, int addr7) {\n            Inet6Address inetAddress;\n            String name = rootServerId + \".root-servers.net\";\n                try {\n                    inetAddress = (Inet6Address) InetAddress.getByAddress(name, new byte[] {\n                            // @formatter:off\n                            (byte) (addr0 >> 8), (byte) addr0, (byte) (addr1 >> 8), (byte) addr1,\n                            (byte) (addr2 >> 8), (byte) addr2, (byte) (addr3 >> 8), (byte) addr3,\n                            (byte) (addr4 >> 8), (byte) addr4, (byte) (addr5 >> 8), (byte) addr5,\n                            (byte) (addr6 >> 8), (byte) addr6, (byte) (addr7 >> 8), (byte) addr7\n                            // @formatter:on\n                    });\n                    IPV6_ROOT_SERVER_MAP.put(rootServerId, inetAddress);\n                } catch (UnknownHostException e) {\n                    // This should never happen, if it does it's our fault!\n                    throw new RuntimeException(e);\n                }\n            return inetAddress;\n        }\n\n        public static Inet4Address getRandomIpv4RootServer(Random random) {\n            return IPV4_ROOT_SERVERS[random.nextInt(IPV4_ROOT_SERVERS.length)];\n        }\n\n        public static Inet6Address getRandomIpv6RootServer(Random random) {\n            return IPV6_ROOT_SERVERS[random.nextInt(IPV6_ROOT_SERVERS.length)];\n        }\n\n        public static Inet4Address getIpv4RootServerById(char id) {\n            return IPV4_ROOT_SERVER_MAP.get(id);\n        }\n\n        public static Inet6Address getIpv6RootServerById(char id) {\n            return IPV6_ROOT_SERVER_MAP.get(id);\n        }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/constants/DnssecConstants.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.constants;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic final class DnssecConstants {\n    /**\n     * Do not allow to instantiate DNSSECConstants\n     */\n    private DnssecConstants() {\n    }\n\n    private static final Map<Byte, SignatureAlgorithm> SIGNATURE_ALGORITHM_LUT = new HashMap<>();\n\n    /**\n     * DNSSEC Signature Algorithms.\n     * \n     * @see <a href=\n     *      \"http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml\">\n     *      IANA DNSSEC Algorithm Numbers</a>\n     */\n    public enum SignatureAlgorithm {\n        @Deprecated\n        RSAMD5(1, \"RSA/MD5\"),\n        DH(2, \"Diffie-Hellman\"),\n        DSA(3, \"DSA/SHA1\"),\n        RSASHA1(5, \"RSA/SHA-1\"),\n        DSA_NSEC3_SHA1(6, \"DSA_NSEC3-SHA1\"),\n        RSASHA1_NSEC3_SHA1(7, \"RSASHA1-NSEC3-SHA1\"),\n        RSASHA256(8, \"RSA/SHA-256\"),\n        RSASHA512(10, \"RSA/SHA-512\"),\n        ECC_GOST(12, \"GOST R 34.10-2001\"),\n        ECDSAP256SHA256(13, \"ECDSA Curve P-256 with SHA-256\"),\n        ECDSAP384SHA384(14, \"ECDSA Curve P-384 with SHA-384\"),\n        INDIRECT(252, \"Reserved for Indirect Keys\"),\n        PRIVATEDNS(253, \"private algorithm\"),\n        PRIVATEOID(254, \"private algorithm oid\"),\n        ;\n\n        SignatureAlgorithm(int number, String description) {\n            if (number < 0 || number > 255) {\n                throw new IllegalArgumentException();\n            }\n            this.number = (byte) number;\n            this.description = description;\n            SIGNATURE_ALGORITHM_LUT.put(this.number, this);\n        }\n\n        public final byte number;\n        public final String description;\n\n        public static SignatureAlgorithm forByte(byte b) {\n            return SIGNATURE_ALGORITHM_LUT.get(b);\n        }\n    }\n\n    private static final Map<Byte, DigestAlgorithm> DELEGATION_DIGEST_LUT = new HashMap<>();\n\n    /**\n     * DNSSEC Digest Algorithms.\n     * \n     * @see <a href=\n     *      \"https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml\">\n     *      IANA Delegation Signer (DS) Resource Record (RR)</a>\n     */\n    public enum DigestAlgorithm {\n        SHA1(1, \"SHA-1\"),\n        SHA256(2, \"SHA-256\"),\n        GOST(3, \"GOST R 34.11-94\"),\n        SHA384(4, \"SHA-384\"),\n        ;\n\n        DigestAlgorithm(int value, String description) {\n            if (value < 0 || value > 255) {\n                throw new IllegalArgumentException();\n            }\n            this.value = (byte) value;\n            this.description = description;\n            DELEGATION_DIGEST_LUT.put(this.value, this);\n        }\n\n        public final byte value;\n        public final String description;\n\n        public static DigestAlgorithm forByte(byte b) {\n            return DELEGATION_DIGEST_LUT.get(b);\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnslabel/ALabel.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnslabel;\n\nimport org.minidns.idna.MiniDnsIdna;\n\npublic final class ALabel extends XnLabel {\n\n    ALabel(String label) {\n        super(label);\n    }\n\n    @Override\n    protected String getInternationalizedRepresentationInternal() {\n        return MiniDnsIdna.toUnicode(label);\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnslabel/DnsLabel.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnslabel;\n\nimport java.io.ByteArrayOutputStream;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Locale;\n\nimport org.minidns.util.SafeCharSequence;\n\n/**\n * A DNS label is an individual component of a DNS name. Labels are usually shown separated by dots.\n * <p>\n * This class implements {@link Comparable} which compares DNS labels according to the Canonical DNS Name Order as\n * specified in <a href=\"https://tools.ietf.org/html/rfc4034#section-6.1\">RFC 4034 § 6.1</a>.\n * </p>\n * <p>\n * Note that as per <a href=\"https://tools.ietf.org/html/rfc2181#section-11\">RFC 2181 § 11</a> DNS labels may contain\n * any byte.\n * </p>\n * \n * @see <a href=\"https://tools.ietf.org/html/rfc5890#section-2.2\">RFC 5890 § 2.2. DNS-Related Terminology</a>\n * @author Florian Schmaus\n *\n */\npublic abstract class DnsLabel extends SafeCharSequence implements Comparable<DnsLabel> {\n\n    /**\n     * The maximum length of a DNS label in octets.\n     *\n     * @see <a href=\"https://tools.ietf.org/html/rfc1035\">RFC 1035 § 2.3.4.</a>\n     */\n    public static final int MAX_LABEL_LENGTH_IN_OCTETS = 63;\n\n    public static final DnsLabel WILDCARD_LABEL = DnsLabel.from(\"*\");\n\n    /**\n     * Whether or not the DNS label is validated on construction.\n     */\n    public static boolean VALIDATE = true;\n\n    public final String label;\n\n    protected DnsLabel(String label) {\n        this.label = label;\n\n        if (!VALIDATE) {\n            return;\n        }\n\n        setBytesIfRequired();\n        if (byteCache.length > MAX_LABEL_LENGTH_IN_OCTETS) {\n            throw new LabelToLongException(label);\n        }\n    }\n\n    private transient String internationalizedRepresentation;\n\n    public final String getInternationalizedRepresentation() {\n        if (internationalizedRepresentation == null) {\n            internationalizedRepresentation = getInternationalizedRepresentationInternal();\n        }\n        return internationalizedRepresentation;\n    }\n\n    protected String getInternationalizedRepresentationInternal() {\n        return label;\n    }\n\n    public final String getLabelType() {\n        return getClass().getSimpleName();\n    }\n\n    private transient String safeToStringRepresentation;\n\n    @Override\n    public final String toString() {\n        if (safeToStringRepresentation == null) {\n            safeToStringRepresentation = toSafeRepesentation(label);\n        }\n\n        return safeToStringRepresentation;\n    }\n\n    /**\n     * Get the raw label. Note that this may return a String containing null bytes.\n     * Those Strings are notoriously difficult to handle from a security\n     * perspective. Therefore it is recommended to use {@link #toString()} instead,\n     * which will return a sanitized String.\n     *\n     * @return the raw label.\n     * @since 1.1.0\n     */\n    public final String getRawLabel() {\n        return label;\n    }\n\n    @Override\n    public final boolean equals(Object other) {\n        if (!(other instanceof DnsLabel)) {\n            return false;\n        }\n        DnsLabel otherDnsLabel = (DnsLabel) other;\n        return label.equals(otherDnsLabel.label);\n    }\n\n    @Override\n    public final int hashCode() {\n        return label.hashCode();\n    }\n\n    private transient DnsLabel lowercasedVariant;\n\n    public final DnsLabel asLowercaseVariant() {\n        if (lowercasedVariant == null) {\n            String lowercaseLabel = label.toLowerCase(Locale.US);\n            lowercasedVariant = DnsLabel.from(lowercaseLabel);\n        }\n        return lowercasedVariant;\n    }\n\n    private transient byte[] byteCache;\n\n    private void setBytesIfRequired() {\n        if (byteCache == null) {\n            byteCache = label.getBytes(StandardCharsets.US_ASCII);\n        }\n    }\n\n    public final void writeToBoas(ByteArrayOutputStream byteArrayOutputStream) {\n        setBytesIfRequired();\n\n        byteArrayOutputStream.write(byteCache.length);\n        byteArrayOutputStream.write(byteCache, 0, byteCache.length);\n    }\n\n    @Override\n    public final int compareTo(DnsLabel other) {\n        String myCanonical = asLowercaseVariant().label;\n        String otherCanonical = other.asLowercaseVariant().label;\n\n        return myCanonical.compareTo(otherCanonical);\n    }\n\n    public static DnsLabel from(String label) {\n        if (label == null || label.isEmpty()) {\n            throw new IllegalArgumentException(\"Label is null or empty\");\n        }\n\n        if (LdhLabel.isLdhLabel(label)) {\n            return LdhLabel.fromInternal(label);\n        }\n\n        return NonLdhLabel.fromInternal(label);\n    }\n\n    public static DnsLabel[] from(String[] labels) {\n        DnsLabel[] res = new DnsLabel[labels.length];\n\n        for (int i = 0; i < labels.length; i++) {\n            res[i] = DnsLabel.from(labels[i]);\n        }\n\n        return res;\n    }\n\n    public static boolean isIdnAcePrefixed(String string) {\n        return string.toLowerCase(Locale.US).startsWith(\"xn--\");\n    }\n\n    public static String toSafeRepesentation(String dnsLabel) {\n        if (consistsOnlyOfLettersDigitsHypenAndUnderscore(dnsLabel)) {\n            // This label is safe, nothing to do.\n            return dnsLabel;\n        }\n\n        StringBuilder sb = new StringBuilder(2 * dnsLabel.length());\n        for (int i = 0; i < dnsLabel.length(); i++) {\n            char c = dnsLabel.charAt(i);\n            if (isLdhOrMaybeUnderscore(c, true)) {\n                sb.append(c);\n                continue;\n            }\n\n\n            // Let's see if we found and unsafe char we want to replace.\n            switch (c) {\n            case '.':\n                sb.append('●'); // U+25CF BLACK CIRCLE;\n                break;\n            case '\\\\':\n                sb.append('⧷'); // U+29F7 REVERSE SOLIDUS WITH HORIZONTAL STROKE\n                break;\n            case '\\u007f':\n                // Convert DEL to U+2421 SYMBOL FOR DELETE\n                sb.append('␡');\n                break;\n            case ' ':\n                sb.append('␣'); // U+2423 OPEN BOX\n                break;\n            default:\n                if (c < 32) {\n                    // First convert the ASCI control codes to the Unicode Control Pictures\n                    int substituteAsInt = c + '\\u2400';\n                    assert substituteAsInt <= Character.MAX_CODE_POINT;\n                    char substitute = (char) substituteAsInt;\n                    sb.append(substitute);\n                } else if (c < 127) {\n                    // Everything smaller than 127 is now safe to directly append.\n                    sb.append(c);\n                } else if (c > 255) {\n                    throw new IllegalArgumentException(\"The string '\" + dnsLabel\n                            + \"' contains characters outside the 8-bit range: \" + c + \" at position \" + i);\n                } else {\n                    // Everything that did not match the previous conditions is explicitly escaped.\n                    sb.append(\"〚\"); // U+301A\n                    // Transform the char to hex notation. Note that we have ensure that c is <= 255\n                    // here, hence only two hexadecimal places are ok.\n                    String hex = String.format(\"%02X\", (int) c);\n                    sb.append(hex);\n                    sb.append(\"〛\"); // U+301B\n                }\n            }\n        }\n\n        return sb.toString();\n    }\n\n    private static boolean isLdhOrMaybeUnderscore(char c, boolean underscore) {\n            // CHECKSTYLE:OFF\n            return (c >= 'a' && c <= 'z')\n                    || (c >= 'A' && c <= 'Z')\n                    || (c >= '0' && c <= '9')\n                    || c == '-'\n                    || (underscore && c == '_')\n                    ;\n            // CHECKSTYLE:ON\n    }\n\n    private static boolean consistsOnlyOfLdhAndMaybeUnderscore(String string, boolean underscore) {\n        for (int i = 0; i < string.length(); i++) {\n            char c = string.charAt(i);\n            if (isLdhOrMaybeUnderscore(c, underscore)) {\n                continue;\n            }\n            return false;\n        }\n        return true;\n    }\n\n    public static boolean consistsOnlyOfLettersDigitsAndHypen(String string) {\n        return consistsOnlyOfLdhAndMaybeUnderscore(string, false);\n    }\n\n    public static boolean consistsOnlyOfLettersDigitsHypenAndUnderscore(String string) {\n        return consistsOnlyOfLdhAndMaybeUnderscore(string, true);\n    }\n\n    public static class LabelToLongException extends IllegalArgumentException {\n\n        /**\n         *\n         */\n        private static final long serialVersionUID = 1L;\n\n        public final String label;\n\n        LabelToLongException(String label) {\n            this.label = label;\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnslabel/FakeALabel.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnslabel;\n\npublic final class FakeALabel extends XnLabel {\n\n    FakeALabel(String label) {\n        super(label);\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnslabel/LdhLabel.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnslabel;\n\n/**\n * A LDH (<b>L</b>etters, <b>D</b>igits, <b>H</b>yphen) label, which is the\n * classical label form.\n * <p>\n * Note that it is a common misconception that LDH labels can not start with a\n * digit. The origin of this misconception is likely that\n * <a href=\"https://datatracker.ietf.org/doc/html/rfc1034#section-3.5\">RFC 1034\n * § 3.5</a> specified\n * </p>\n * <blockquote>\n * They [i.e, DNS labels] must start with a letter, end with a letter or digit,\n * and have as interior characters only letters, digits, and hyphen.\n * </blockquote>.\n * However, this was relaxed in\n * <a href=\"https://datatracker.ietf.org/doc/html/rfc1123#page-13\">RFC 1123 §\n * 2.1</a>\n * <blockquote>\n * One aspect of host name syntax is hereby changed: the restriction on the first\n * character is relaxed to allow either a letter or a digit.\n * </blockquote>\n * and later summarized in\n * <a href=\"https://datatracker.ietf.org/doc/html/rfc3696#section-2\">RFC 3696 §\n * 2</a>:\n * <blockquote>\n * If the hyphen is used, it is not permitted to appear at either the beginning\n * or end of a label.\n * </blockquote>\n * Furthermore\n * <a href=\"https://datatracker.ietf.org/doc/html/rfc5890#section-2.3.1\">RFC\n * 5890 § 2.3.1</a> only mentions the requirement that hyphen must not be the\n * first or last character of a LDH label.\n *\n * @see <a href=\"https://tools.ietf.org/html/rfc5890#section-2.3.1\">RFC 5890 §\n *      2.3.1. LDH Label</a>\n *\n */\npublic abstract class LdhLabel extends DnsLabel {\n\n    protected LdhLabel(String label) {\n        super(label);\n    }\n\n    public static boolean isLdhLabel(String label) {\n        if (label.isEmpty()) {\n            return false;\n        }\n\n        if (LeadingOrTrailingHyphenLabel.isLeadingOrTrailingHypenLabelInternal(label)) {\n            return false;\n        }\n\n        return consistsOnlyOfLettersDigitsAndHypen(label);\n    }\n\n    protected static LdhLabel fromInternal(String label) {\n        assert isLdhLabel(label);\n\n        if (ReservedLdhLabel.isReservedLdhLabel(label)) {\n            // Label starts with '??--'. Now let us see if it is a XN-Label, starting with 'xn--', but be aware that the\n            // 'xn' part is case insensitive. The XnLabel.isXnLabelInternal(String) method takes care of this.\n            if (XnLabel.isXnLabelInternal(label)) {\n                return XnLabel.fromInternal(label);\n            } else {\n                return new ReservedLdhLabel(label);\n            }\n        }\n        return new NonReservedLdhLabel(label);\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnslabel/LeadingOrTrailingHyphenLabel.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnslabel;\n\n/**\n * A DNS label with a leading or trailing hyphen ('-').\n */\npublic final class LeadingOrTrailingHyphenLabel extends NonLdhLabel {\n\n    LeadingOrTrailingHyphenLabel(String label) {\n        super(label);\n    }\n\n    static boolean isLeadingOrTrailingHypenLabelInternal(String label) {\n        if (label.isEmpty()) {\n            return false;\n        }\n\n        if (label.charAt(0) == '-') {\n            return true;\n        }\n\n        if (label.charAt(label.length() - 1) == '-') {\n            return true;\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnslabel/NonLdhLabel.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnslabel;\n\n/**\n * A DNS label which contains more than just letters, digits and a hyphen.\n *\n */\npublic abstract class NonLdhLabel extends DnsLabel {\n\n    protected NonLdhLabel(String label) {\n        super(label);\n    }\n\n    protected static DnsLabel fromInternal(String label) {\n        if (UnderscoreLabel.isUnderscoreLabelInternal(label)) {\n            return new UnderscoreLabel(label);\n        }\n\n        if (LeadingOrTrailingHyphenLabel.isLeadingOrTrailingHypenLabelInternal(label)) {\n            return new LeadingOrTrailingHyphenLabel(label);\n        }\n\n        return new OtherNonLdhLabel(label);\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnslabel/NonReservedLdhLabel.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnslabel;\n\n/**\n * A Non-Reserved LDH label (NR-LDH label), which do <em>not</em> have \"--\" in the third and fourth position.\n *\n */\npublic final class NonReservedLdhLabel extends LdhLabel {\n\n    NonReservedLdhLabel(String label) {\n        super(label);\n        assert isNonReservedLdhLabelInternal(label);\n    }\n\n    public static boolean isNonReservedLdhLabel(String label) {\n        if (!isLdhLabel(label)) {\n            return false;\n        }\n        return isNonReservedLdhLabelInternal(label);\n    }\n\n    static boolean isNonReservedLdhLabelInternal(String label) {\n        return !ReservedLdhLabel.isReservedLdhLabelInternal(label);\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnslabel/OtherNonLdhLabel.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnslabel;\n\n/**\n * A Non-LDH label which does <em>not</em> begin with an underscore ('_'), hyphen ('-') or ends with an hyphen.\n *\n */\npublic final class OtherNonLdhLabel extends NonLdhLabel {\n\n    OtherNonLdhLabel(String label) {\n        super(label);\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnslabel/ReservedLdhLabel.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnslabel;\n\n/**\n * A reserved LDH label (R-LDH label), which have the property that they contain \"--\" in the third and fourth characters.\n *\n */\npublic class ReservedLdhLabel extends LdhLabel {\n\n    protected ReservedLdhLabel(String label) {\n        super(label);\n        assert isReservedLdhLabelInternal(label);\n    }\n\n    public static boolean isReservedLdhLabel(String label) {\n        if (!isLdhLabel(label)) {\n            return false;\n        }\n        return isReservedLdhLabelInternal(label);\n    }\n\n    static boolean isReservedLdhLabelInternal(String label) {\n        return label.length() >= 4\n                && label.charAt(2) == '-'\n                && label.charAt(3) == '-';\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnslabel/UnderscoreLabel.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnslabel;\n\n/**\n * A DNS label which begins with an underscore ('_').\n *\n */\npublic final class UnderscoreLabel extends NonLdhLabel {\n\n    UnderscoreLabel(String label) {\n        super(label);\n    }\n\n    static boolean isUnderscoreLabelInternal(String label) {\n        return label.charAt(0) == '_';\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnslabel/XnLabel.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnslabel;\n\nimport java.util.Locale;\n\nimport org.minidns.idna.MiniDnsIdna;\n\n/**\n * A label that begins with \"xn--\" and follows the LDH rule.\n */\npublic abstract class XnLabel extends ReservedLdhLabel {\n\n    protected XnLabel(String label) {\n        super(label);\n    }\n\n    protected static LdhLabel fromInternal(String label) {\n        assert isIdnAcePrefixed(label);\n\n        String uLabel = MiniDnsIdna.toUnicode(label);\n        if (label.equals(uLabel)) {\n            // No Punycode conversation to Unicode was performed, this is a fake A-label!\n            return new FakeALabel(label);\n        } else {\n            return new ALabel(label);\n        }\n    }\n\n    public static boolean isXnLabel(String label) {\n        if (!isReservedLdhLabel(label)) {\n            return false;\n        }\n        return isXnLabelInternal(label);\n    }\n\n    static boolean isXnLabelInternal(String label) {\n        // Note that we already ensure the minimum label length here, since reserved LDH\n        // labels must start with \"xn--\".\n        return label.substring(0, 2).toLowerCase(Locale.US).equals(\"xn\");\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnsmessage/DnsMessage.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsmessage;\n\nimport org.minidns.edns.Edns;\nimport org.minidns.record.Data;\nimport org.minidns.record.OPT;\nimport org.minidns.record.Record;\nimport org.minidns.record.Record.TYPE;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.DatagramPacket;\nimport java.net.InetAddress;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\n/**\n * A DNS message as defined by RFC 1035. The message consists of a header and\n * 4 sections: question, answer, nameserver and addition resource record\n * section.\n * A message can either be parsed ({@link #DnsMessage(byte[])}) or serialized\n * ({@link DnsMessage#toArray()}).\n * \n * @see <a href=\"https://www.ietf.org/rfc/rfc1035.txt\">RFC 1035</a>\n */\npublic class DnsMessage {\n\n    private static final Logger LOGGER = Logger.getLogger(DnsMessage.class.getName());\n\n    /**\n     * Possible DNS response codes.\n     * \n     * @see <a href=\n     *      \"http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6\">\n     *      IANA Domain Name System (DNS) Paramters - DNS RCODEs</a>\n     * @see <a href=\"http://tools.ietf.org/html/rfc6895#section-2.3\">RFC 6895 § 2.3</a>\n     */\n    public enum RESPONSE_CODE {\n        NO_ERROR(0),\n        FORMAT_ERR(1),\n        SERVER_FAIL(2),\n        NX_DOMAIN(3),\n        NO_IMP(4),\n        REFUSED(5),\n        YXDOMAIN(6),\n        YXRRSET(7),\n        NXRRSET(8),\n        NOT_AUTH(9),\n        NOT_ZONE(10),\n        BADVERS_BADSIG(16),\n        BADKEY(17),\n        BADTIME(18),\n        BADMODE(19),\n        BADNAME(20),\n        BADALG(21),\n        BADTRUNC(22),\n        BADCOOKIE(23),\n        ;\n\n        /**\n         * Reverse lookup table for response codes.\n         */\n        private static final Map<Integer, RESPONSE_CODE> INVERSE_LUT = new HashMap<>(RESPONSE_CODE.values().length);\n\n        static {\n            for (RESPONSE_CODE responseCode : RESPONSE_CODE.values()) {\n                INVERSE_LUT.put((int) responseCode.value, responseCode);\n            }\n        }\n\n        /**\n         * The response code value.\n         */\n        private final byte value;\n\n        /**\n         * Create a new response code.\n         *\n         * @param value The response code value.\n         */\n        RESPONSE_CODE(int value) {\n            this.value = (byte) value;\n        }\n\n        /**\n         * Retrieve the byte value of the response code.\n         *\n         * @return the response code.\n         */\n        public byte getValue() {\n            return value;\n        }\n\n        /**\n         * Retrieve the response code for a byte value.\n         *\n         * @param value The byte value.\n         * @return The symbolic response code or null.\n         * @throws IllegalArgumentException if the value is not in the range of 0..15.\n         */\n        public static RESPONSE_CODE getResponseCode(int value) throws IllegalArgumentException {\n            if (value < 0 || value > 65535) {\n                throw new IllegalArgumentException();\n            }\n            return INVERSE_LUT.get(value);\n        }\n\n    }\n\n    /**\n     * Symbolic DNS Opcode values.\n     * \n     * @see <a href=\n     *      \"http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5\">\n     *      IANA Domain Name System (DNS) Paramters - DNS OpCodes</a>\n     */\n    public enum OPCODE {\n        QUERY,\n        INVERSE_QUERY,\n        STATUS,\n        UNASSIGNED3,\n        NOTIFY,\n        UPDATE,\n        ;\n\n        /**\n         * Lookup table for for opcode resolution.\n         */\n        private static final OPCODE[] INVERSE_LUT = new OPCODE[OPCODE.values().length];\n\n        static {\n            for (OPCODE opcode : OPCODE.values()) {\n                if (INVERSE_LUT[opcode.getValue()] != null) {\n                    throw new IllegalStateException();\n                }\n                INVERSE_LUT[opcode.getValue()] = opcode;\n            }\n        }\n\n        /**\n         * The value of this opcode.\n         */\n        private final byte value;\n\n        /**\n         * Create a new opcode for a given byte value.\n         *\n         */\n        @SuppressWarnings(\"EnumOrdinal\")\n        OPCODE() {\n            this.value = (byte) this.ordinal();\n        }\n\n        /**\n         * Retrieve the byte value of this opcode.\n         *\n         * @return The byte value of this opcode.\n         */\n        public byte getValue() {\n            return value;\n        }\n\n        /**\n         * Retrieve the symbolic name of an opcode byte.\n         *\n         * @param value The byte value of the opcode.\n         * @return The symbolic opcode or null.\n         * @throws IllegalArgumentException If the byte value is not in the\n         *                                  range 0..15.\n         */\n        public static OPCODE getOpcode(int value) throws IllegalArgumentException {\n            if (value < 0 || value > 15) {\n                throw new IllegalArgumentException();\n            }\n            if (value >= INVERSE_LUT.length) {\n                return null;\n            }\n            return INVERSE_LUT[value];\n        }\n\n    }\n\n    /**\n     * The DNS message id.\n     */\n    public final int id;\n\n    /**\n     * The DNS message opcode.\n     */\n    public final OPCODE opcode;\n\n    /**\n     * The response code of this dns message.\n     */\n    public final RESPONSE_CODE responseCode;\n\n    /**\n     * The QR flag of the DNS message header. Note that this will be <code>true</code> if the message is a\n     * <b>response</b> and <code>false</code> if it is a <b>query</b>.\n     * \n     * @see <a href=\"https://www.ietf.org/rfc/rfc1035.txt\">RFC 1035 § 4.1.1</a>\n     */\n    public final boolean qr;\n\n    /**\n     * True if this is a authorative response. If set, the responding nameserver is an authority for the domain name in\n     * the question section. Note that the answer section may have multiple owner names because of aliases. This flag\n     * corresponds to the name which matches the query name, or the first owner name in the query section.\n     *\n     * @see <a href=\"https://www.ietf.org/rfc/rfc1035.txt\">RFC 1035 § 4.1.1. Header section format</a>\n     */\n    public final boolean authoritativeAnswer;\n\n    /**\n     * True if message is truncated. Then TCP should be used.\n     */\n    public final boolean truncated;\n\n    /**\n     * True if the server should recurse.\n     */\n    public final boolean recursionDesired;\n\n    /**\n     * True if recursion is possible.\n     */\n    public final boolean recursionAvailable;\n\n    /**\n     * True if the server regarded the response as authentic.\n     */\n    public final boolean authenticData;\n\n    /**\n     * True if the server should not perform DNSSEC validation before returning the result.\n     */\n    public final boolean checkingDisabled;\n\n    /**\n     * The question section content. Usually there will be only one question.\n     * <p>\n     * This list is unmodifiable.\n     * </p>\n     */\n    public final List<Question> questions;\n\n    /**\n     * The answers section records. Note that it is not guaranteed that all records found in this section will be direct\n     * answers to the question in the query. If DNSSEC is used, then this section also contains the RRSIG record.\n     * <p>\n     * This list is unmodifiable.\n     * </p>\n     */\n    public final List<Record<? extends Data>> answerSection;\n\n    /**\n     * The Authority Section. Note that it is not guaranteed that this section only contains nameserver records. If DNSSEC is used, then this section could also contain a NSEC(3) record.\n     * <p>\n     * This list is unmodifiable.\n     * </p>\n     */\n    public final List<Record<? extends Data>> authoritySection;\n\n    /**\n     * The additional section. It eventually contains RRs which relate to the query.\n     * <p>\n     * This list is unmodifiable.\n     * </p> \n     */\n    public final List<Record<? extends Data>> additionalSection;\n\n    public final int optRrPosition;\n\n    /**\n     * The optional but very common EDNS information. Note that this field is lazily populated.\n     *\n     */\n    private Edns edns;\n\n    /**\n     * The receive timestamp. Set only if this message was created via parse.\n     * This should be used to evaluate TTLs.\n     */\n    public final long receiveTimestamp;\n\n    protected DnsMessage(Builder builder) {\n        this.id = builder.id;\n        this.opcode = builder.opcode;\n        this.responseCode = builder.responseCode;\n        this.receiveTimestamp = builder.receiveTimestamp;\n        this.qr = builder.query;\n        this.authoritativeAnswer = builder.authoritativeAnswer;\n        this.truncated = builder.truncated;\n        this.recursionDesired = builder.recursionDesired;\n        this.recursionAvailable = builder.recursionAvailable;\n        this.authenticData = builder.authenticData;\n        this.checkingDisabled = builder.checkingDisabled;\n\n        if (builder.questions == null) {\n            this.questions = Collections.emptyList();\n        } else {\n            List<Question> q = new ArrayList<>(builder.questions.size());\n            q.addAll(builder.questions);\n            this.questions = Collections.unmodifiableList(q);\n        }\n\n        if (builder.answerSection == null) {\n            this.answerSection = Collections.emptyList();\n        } else {\n            List<Record<? extends Data>> a = new ArrayList<>(builder.answerSection.size());\n            a.addAll(builder.answerSection);\n            this.answerSection = Collections.unmodifiableList(a);\n        }\n\n        if (builder.authoritySection == null) {\n            this.authoritySection = Collections.emptyList();\n        } else {\n            List<Record<? extends Data>> n = new ArrayList<>(builder.authoritySection.size());\n            n.addAll(builder.authoritySection);\n            this.authoritySection = Collections.unmodifiableList(n);\n        }\n\n        if (builder.additionalSection == null && builder.ednsBuilder == null) {\n            this.additionalSection = Collections.emptyList();\n        } else {\n            int size = 0;\n            if (builder.additionalSection != null) {\n                size += builder.additionalSection.size();\n            }\n            if (builder.ednsBuilder != null) {\n                size++;\n            }\n            List<Record<? extends Data>> a = new ArrayList<>(size);\n            if (builder.additionalSection != null) {\n                a.addAll(builder.additionalSection);\n            }\n            if (builder.ednsBuilder != null) {\n                Edns edns = builder.ednsBuilder.build();\n                this.edns = edns;\n                a.add(edns.asRecord());\n            }\n            this.additionalSection = Collections.unmodifiableList(a);\n        }\n\n        optRrPosition = getOptRrPosition(this.additionalSection);\n\n        if (optRrPosition != -1) {\n            // Verify that there are no further OPT records but the one we already found.\n            for (int i = optRrPosition + 1; i < this.additionalSection.size(); i++) {\n                if (this.additionalSection.get(i).type == TYPE.OPT) {\n                    throw new IllegalArgumentException(\"There must be only one OPT pseudo RR in the additional section\");\n                }\n            }\n        }\n\n        // TODO Add verification of dns message state here\n    }\n\n    /**\n     * Build a DNS Message based on a binary DNS message.\n     *\n     * @param data The DNS message data.\n     * @throws IOException On read errors.\n     */\n    public DnsMessage(byte[] data) throws IOException {\n        ByteArrayInputStream bis = new ByteArrayInputStream(data);\n        DataInputStream dis = new DataInputStream(bis);\n        id = dis.readUnsignedShort();\n        int header = dis.readUnsignedShort();\n        qr = ((header >> 15) & 1) == 1;\n        opcode = OPCODE.getOpcode((header >> 11) & 0xf);\n        authoritativeAnswer = ((header >> 10) & 1) == 1;\n        truncated = ((header >> 9) & 1) == 1;\n        recursionDesired = ((header >> 8) & 1) == 1;\n        recursionAvailable = ((header >> 7) & 1) == 1;\n        authenticData = ((header >> 5) & 1) == 1;\n        checkingDisabled = ((header >> 4) & 1) == 1;\n        responseCode = RESPONSE_CODE.getResponseCode(header & 0xf);\n        receiveTimestamp = System.currentTimeMillis();\n        int questionCount = dis.readUnsignedShort();\n        int answerCount = dis.readUnsignedShort();\n        int nameserverCount = dis.readUnsignedShort();\n        int additionalResourceRecordCount = dis.readUnsignedShort();\n        questions = new ArrayList<>(questionCount);\n        for (int i = 0; i < questionCount; i++) {\n            questions.add(new Question(dis, data));\n        }\n        answerSection = new ArrayList<>(answerCount);\n        for (int i = 0; i < answerCount; i++) {\n            answerSection.add(Record.parse(dis, data));\n        }\n        authoritySection = new ArrayList<>(nameserverCount);\n        for (int i = 0; i < nameserverCount; i++) {\n            authoritySection.add(Record.parse(dis, data));\n        }\n        additionalSection = new ArrayList<>(additionalResourceRecordCount);\n        for (int i = 0; i < additionalResourceRecordCount; i++) {\n            additionalSection.add(Record.parse(dis, data));\n        }\n        optRrPosition = getOptRrPosition(additionalSection);\n    }\n\n    /**\n     * Constructs an normalized version of the given DnsMessage by setting the id to '0'.\n     *\n     * @param message the message of which normalized version should be constructed.\n     */\n    private DnsMessage(DnsMessage message) {\n        id = 0;\n        qr = message.qr;\n        opcode = message.opcode;\n        authoritativeAnswer = message.authoritativeAnswer;\n        truncated = message.truncated;\n        recursionDesired = message.recursionDesired;\n        recursionAvailable = message.recursionAvailable;\n        authenticData = message.authenticData;\n        checkingDisabled = message.checkingDisabled;\n        responseCode = message.responseCode;\n        receiveTimestamp = message.receiveTimestamp;\n        questions = message.questions;\n        answerSection = message.answerSection;\n        authoritySection = message.authoritySection;\n        additionalSection = message.additionalSection;\n        optRrPosition = message.optRrPosition;\n    }\n\n    private static int getOptRrPosition(List<Record<? extends Data>> additionalSection) {\n        int optRrPosition = -1;\n        for (int i = 0; i < additionalSection.size(); i++) {\n            Record<? extends Data> record = additionalSection.get(i);\n            if (record.type == Record.TYPE.OPT) {\n                optRrPosition = i;\n                break;\n            }\n        }\n        return optRrPosition;\n    }\n\n    /**\n     * Generate a binary dns packet out of this message.\n     *\n     * @return byte[] the binary representation.\n     */\n    public byte[] toArray() {\n        return serialize().clone();\n    }\n\n    public DatagramPacket asDatagram(InetAddress address, int port) {\n        byte[] bytes = serialize();\n        return new DatagramPacket(bytes, bytes.length, address, port);\n    }\n\n    public void writeTo(OutputStream outputStream) throws IOException {\n        writeTo(outputStream, true);\n    }\n\n    public void writeTo(OutputStream outputStream, boolean writeLength) throws IOException {\n        byte[] bytes = serialize();\n        DataOutputStream dataOutputStream = new DataOutputStream(outputStream);\n        if (writeLength) {\n            dataOutputStream.writeShort(bytes.length);\n        }\n        dataOutputStream.write(bytes);\n    }\n\n    public ByteBuffer getInByteBuffer() {\n        byte[] bytes = serialize().clone();\n        return ByteBuffer.wrap(bytes);\n    }\n\n    private byte[] byteCache;\n\n    private byte[] serialize() {\n        if (byteCache != null) {\n            return byteCache;\n        }\n\n        ByteArrayOutputStream baos = new ByteArrayOutputStream(512);\n        DataOutputStream dos = new DataOutputStream(baos);\n        int header = calculateHeaderBitmap();\n        try {\n            dos.writeShort((short) id);\n            dos.writeShort((short) header);\n            if (questions == null) {\n                dos.writeShort(0);\n            } else {\n                dos.writeShort((short) questions.size());\n            }\n            if (answerSection == null) {\n                dos.writeShort(0);\n            } else {\n                dos.writeShort((short) answerSection.size());\n            }\n            if (authoritySection == null) {\n                dos.writeShort(0);\n            } else {\n                dos.writeShort((short) authoritySection.size());\n            }\n            if (additionalSection == null) {\n                dos.writeShort(0);\n            } else {\n                dos.writeShort((short) additionalSection.size());\n            }\n            if (questions != null) {\n                for (Question question : questions) {\n                    dos.write(question.toByteArray());\n                }\n            }\n            if (answerSection != null) {\n                for (Record<? extends Data> answer : answerSection) {\n                    dos.write(answer.toByteArray());\n                }\n            }\n            if (authoritySection != null) {\n                for (Record<? extends Data> nameserverRecord : authoritySection) {\n                    dos.write(nameserverRecord.toByteArray());\n                }\n            }\n            if (additionalSection != null) {\n                for (Record<? extends Data> additionalResourceRecord : additionalSection) {\n                    dos.write(additionalResourceRecord.toByteArray());\n                }\n            }\n            dos.flush();\n        } catch (IOException e) {\n            // Should never happen.\n            throw new AssertionError(e);\n        }\n        byteCache = baos.toByteArray();\n        return byteCache;\n    }\n\n    int calculateHeaderBitmap() {\n        int header = 0;\n        if (qr) {\n            header += 1 << 15;\n        }\n        if (opcode != null) {\n            header += opcode.getValue() << 11;\n        }\n        if (authoritativeAnswer) {\n            header += 1 << 10;\n        }\n        if (truncated) {\n            header += 1 << 9;\n        }\n        if (recursionDesired) {\n            header += 1 << 8;\n        }\n        if (recursionAvailable) {\n            header += 1 << 7;\n        }\n        if (authenticData) {\n            header += 1 << 5;\n        }\n        if (checkingDisabled) {\n            header += 1 << 4;\n        }\n        if (responseCode != null) {\n            header += responseCode.getValue();\n        }\n        return header;\n    }\n\n    public Question getQuestion() {\n        return questions.get(0);\n    }\n\n    /**\n     * Copy the questions found in the question section.\n     *\n     * @return a copy of the question section questions.\n     * @see #questions\n     */\n    public List<Question> copyQuestions() {\n        List<Question> copy = new ArrayList<>(questions.size());\n        copy.addAll(questions);\n        return copy;\n    }\n\n    /**\n     * Copy the records found in the answer section into a new list.\n     *\n     * @return a copy of the answer section records.\n     * @see #answerSection\n     */\n    public List<Record<? extends Data>> copyAnswers() {\n        List<Record<? extends Data>> res = new ArrayList<>(answerSection.size());\n        res.addAll(answerSection);\n        return res;\n    }\n\n    /**\n     * Copy the records found in the authority section into a new list.\n     *\n     * @return a copy of the authority section records.\n     * @see #authoritySection\n     */\n    public List<Record<? extends Data>> copyAuthority() {\n        List<Record<? extends Data>> res = new ArrayList<>(authoritySection.size());\n        res.addAll(authoritySection);\n        return res;\n    }\n\n    public Edns getEdns() {\n        if (edns != null) return edns;\n\n        Record<OPT> optRecord = getOptPseudoRecord();\n        if (optRecord == null) return null;\n        edns = new Edns(optRecord);\n        return edns;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public Record<OPT> getOptPseudoRecord() {\n        if (optRrPosition == -1) return null;\n        return (Record<OPT>) additionalSection.get(optRrPosition);\n    }\n\n    /**\n     * Check if the EDNS DO (DNSSEC OK) flag is set.\n     *\n     * @return true if the DO flag is set.\n     */\n    public boolean isDnssecOk() {\n        Edns edns = getEdns();\n        if (edns == null)\n            return false;\n\n        return edns.dnssecOk;\n    }\n\n    private String toStringCache;\n\n    @Override\n    public String toString() {\n        if (toStringCache != null) return toStringCache;\n\n        StringBuilder sb = new StringBuilder(\"DnsMessage\");\n        asBuilder().writeToStringBuilder(sb);\n\n        toStringCache = sb.toString();\n        return toStringCache;\n    }\n\n    private String terminalOutputCache;\n\n    /**\n     * Format the DnsMessage object in a way suitable for terminal output.\n     * The format is loosely based on the output provided by {@code dig}.\n     *\n     * @return This message as a String suitable for terminal output.\n     */\n     @SuppressWarnings(\"JavaUtilDate\")\n     public String asTerminalOutput() {\n        if (terminalOutputCache != null) return terminalOutputCache;\n\n        StringBuilder sb = new StringBuilder(\";; ->>HEADER<<-\")\n                .append(\" opcode: \").append(opcode)\n                .append(\", status: \").append(responseCode)\n                .append(\", id: \").append(id).append(\"\\n\")\n                .append(\";; flags:\");\n        if (!qr) sb.append(\" qr\");\n        if (authoritativeAnswer) sb.append(\" aa\");\n        if (truncated) sb.append(\" tr\");\n        if (recursionDesired) sb.append(\" rd\");\n        if (recursionAvailable) sb.append(\" ra\");\n        if (authenticData) sb.append(\" ad\");\n        if (checkingDisabled) sb.append(\" cd\");\n        sb.append(\"; QUERY: \").append(questions.size())\n                .append(\", ANSWER: \").append(answerSection.size())\n                .append(\", AUTHORITY: \").append(authoritySection.size())\n                .append(\", ADDITIONAL: \").append(additionalSection.size())\n                .append(\"\\n\\n\");\n        for (Record<? extends Data> record : additionalSection) {\n            Edns edns = Edns.fromRecord(record);\n            if (edns != null) {\n                sb.append(\";; OPT PSEUDOSECTION:\\n; \").append(edns.asTerminalOutput());\n                break;\n            }\n        }\n        if (questions.size() != 0) {\n            sb.append(\";; QUESTION SECTION:\\n\");\n            for (Question question : questions) {\n                sb.append(';').append(question.toString()).append('\\n');\n            }\n        }\n        if (authoritySection.size() != 0) {\n            sb.append(\"\\n;; AUTHORITY SECTION:\\n\");\n            for (Record<? extends Data> record : authoritySection) {\n                sb.append(record.toString()).append('\\n');\n            }\n        }\n        if (answerSection.size() != 0) {\n            sb.append(\"\\n;; ANSWER SECTION:\\n\");\n            for (Record<? extends Data> record : answerSection) {\n                sb.append(record.toString()).append('\\n');\n            }\n        }\n        if (additionalSection.size() != 0) {\n            boolean hasNonOptArr = false;\n            for (Record<? extends Data> record : additionalSection) {\n                if (record.type != Record.TYPE.OPT) {\n                    if (!hasNonOptArr) {\n                        hasNonOptArr = true;\n                        sb.append(\"\\n;; ADDITIONAL SECTION:\\n\");\n                    }\n                    sb.append(record.toString()).append('\\n');\n                }\n            }\n        }\n        if (receiveTimestamp > 0) {\n            sb.append(\"\\n;; WHEN: \").append(new Date(receiveTimestamp).toString());\n        }\n        terminalOutputCache = sb.toString();\n        return terminalOutputCache;\n    }\n\n    public <D extends Data> Set<D> getAnswersFor(Question q) {\n        if (responseCode != RESPONSE_CODE.NO_ERROR) return null;\n\n        // It would be great if we could verify that D matches q.type at this\n        // point. But on the other hand, if it does not, then the cast to D\n        // below will fail.\n        Set<D> res = new HashSet<>(answerSection.size());\n        for (Record<? extends Data> record : answerSection) {\n            if (!record.isAnswer(q)) continue;\n\n            Data data = record.getPayload();\n            @SuppressWarnings(\"unchecked\")\n            D d = (D) data;\n            boolean isNew = res.add(d);\n            if (!isNew) {\n                LOGGER.log(Level.WARNING, \"DnsMessage contains duplicate answers. Record: \" + record + \"; DnsMessage: \" + this);\n            }\n        }\n        return res;\n    }\n\n    private long answersMinTtlCache = -1;\n\n    /**\n     * Get the minimum TTL from all answers in seconds.\n     *\n     * @return the minimum TTL from all answers in seconds.\n     */\n    public long getAnswersMinTtl() {\n        if (answersMinTtlCache >= 0) {\n            return answersMinTtlCache;\n        }\n\n        answersMinTtlCache = Long.MAX_VALUE;\n        for (Record<? extends Data> r : answerSection) {\n            answersMinTtlCache = Math.min(answersMinTtlCache, r.ttl);\n        }\n        return answersMinTtlCache;\n    }\n\n    public Builder asBuilder() {\n        return new Builder(this);\n    }\n\n    private DnsMessage normalizedVersionCache;\n\n    public DnsMessage asNormalizedVersion() {\n        if (normalizedVersionCache == null) {\n            normalizedVersionCache = new DnsMessage(this);\n        }\n        return normalizedVersionCache;\n    }\n\n    public Builder getResponseBuilder(RESPONSE_CODE responseCode) {\n        if (qr) {\n            throw new IllegalStateException();\n        }\n        Builder responseBuilder = DnsMessage.builder()\n                .setQrFlag(true)\n                .setResponseCode(responseCode)\n                .setId(id)\n                .setQuestion(getQuestion());\n\n        return responseBuilder;\n    }\n\n    private transient Integer hashCodeCache;\n\n    @Override\n    public int hashCode() {\n        if (hashCodeCache == null) {\n            byte[] bytes = serialize();\n            hashCodeCache = Arrays.hashCode(bytes);\n        }\n        return hashCodeCache;\n    }\n\n    private enum SectionName {\n        answer,\n        authority,\n        additional,\n    }\n\n    private <D extends Data> List<Record<D>> filterSectionByType(boolean stopOnFirst, SectionName sectionName, Class<D> type) {\n        List<Record<?>> sectionToFilter;\n        switch (sectionName) {\n        case answer:\n            sectionToFilter = answerSection;\n            break;\n        case authority:\n            sectionToFilter = authoritySection;\n            break;\n        case additional:\n            sectionToFilter = additionalSection;\n            break;\n        default:\n            throw new AssertionError(\"Unknown section name \" + sectionName);\n        }\n\n        List<Record<D>> res = new ArrayList<>(stopOnFirst ? 1 : sectionToFilter.size());\n\n        for (Record<?> record : sectionToFilter) {\n            Record<D> target = record.ifPossibleAs(type);\n            if (target != null) {\n                res.add(target);\n                if (stopOnFirst) {\n                    return res;\n                }\n            }\n        }\n\n        return res;\n    }\n\n    private <D extends Data> List<Record<D>> filterSectionByType(SectionName sectionName, Class<D> type) {\n        return filterSectionByType(false, sectionName, type);\n    }\n\n    private <D extends Data> Record<D> getFirstOfType(SectionName sectionName, Class<D> type) {\n        List<Record<D>> result = filterSectionByType(true, sectionName, type);\n        if (result.isEmpty()) {\n            return null;\n        }\n\n        return result.get(0);\n    }\n\n    public <D extends Data> List<Record<D>> filterAnswerSectionBy(Class<D> type) {\n        return filterSectionByType(SectionName.answer, type);\n    }\n\n    public <D extends Data> List<Record<D>> filterAuthoritySectionBy(Class<D> type) {\n        return filterSectionByType(SectionName.authority, type);\n    }\n\n    public <D extends Data> List<Record<D>> filterAdditionalSectionBy(Class<D> type) {\n        return filterSectionByType(SectionName.additional, type);\n    }\n\n    public <D extends Data> Record<D> getFirstOfTypeFromAnswerSection(Class<D> type) {\n        return getFirstOfType(SectionName.answer, type);\n    }\n\n    public <D extends Data> Record<D> getFirstOfTypeFromAuthoritySection(Class<D> type) {\n        return getFirstOfType(SectionName.authority, type);\n    }\n\n    public <D extends Data> Record<D> getFirstOfTypeFromAdditionalSection(Class<D> type) {\n        return getFirstOfType(SectionName.additional, type);\n    }\n\n    @Override\n    public boolean equals(Object other) {\n        if (!(other instanceof DnsMessage)) {\n            return false;\n        }\n        if (other == this) {\n            return true;\n        }\n        DnsMessage otherDnsMessage = (DnsMessage) other;\n        byte[] otherBytes = otherDnsMessage.serialize();\n        byte[] myBytes = serialize();\n        return Arrays.equals(myBytes, otherBytes);\n    }\n\n    public static Builder builder() {\n        return new DnsMessage.Builder();\n    }\n\n    public static final class Builder {\n\n        private Builder() {\n        }\n\n        private Builder(DnsMessage message) {\n            id = message.id;\n            opcode = message.opcode;\n            responseCode = message.responseCode;\n            query = message.qr;\n            authoritativeAnswer = message.authoritativeAnswer;\n            truncated = message.truncated;\n            recursionDesired = message.recursionDesired;\n            recursionAvailable = message.recursionAvailable;\n            authenticData = message.authenticData;\n            checkingDisabled = message.checkingDisabled;\n            receiveTimestamp = message.receiveTimestamp;\n\n            // Copy the unmodifiable lists over into this new builder.\n            questions = new ArrayList<>(message.questions.size());\n            questions.addAll(message.questions);\n            answerSection = new ArrayList<>(message.answerSection.size());\n            answerSection.addAll(message.answerSection);\n            authoritySection = new ArrayList<>(message.authoritySection.size());\n            authoritySection.addAll(message.authoritySection);\n            additionalSection = new ArrayList<>(message.additionalSection.size());\n            additionalSection.addAll(message.additionalSection);\n        }\n\n        private int id;\n        private OPCODE opcode = OPCODE.QUERY;\n        private RESPONSE_CODE responseCode = RESPONSE_CODE.NO_ERROR;\n        private boolean query;\n        private boolean authoritativeAnswer;\n        private boolean truncated;\n        private boolean recursionDesired;\n        private boolean recursionAvailable;\n        private boolean authenticData;\n        private boolean checkingDisabled;\n\n        private long receiveTimestamp = -1;\n\n        private List<Question> questions;\n        private List<Record<? extends Data>> answerSection;\n        private List<Record<? extends Data>> authoritySection;\n        private List<Record<? extends Data>> additionalSection;\n        private Edns.Builder ednsBuilder;\n\n        /**\n         * Set the current DNS message id.\n         *\n         * @param id The new DNS message id.\n         * @return a reference to this builder.\n         */\n        public Builder setId(int id) {\n            this.id = id & 0xffff;\n            return this;\n        }\n\n        public Builder setOpcode(OPCODE opcode) {\n            this.opcode = opcode;\n            return this;\n        }\n\n        public Builder setResponseCode(RESPONSE_CODE responseCode) {\n            this.responseCode = responseCode;\n            return this;\n        }\n\n        /**\n         * Set the QR flag. Note that this will be <code>true</code> if the message is a\n         * <b>response</b> and <code>false</code> if it is a <b>query</b>.\n         *\n         * @param query The new QR flag status.\n         * @return a reference to this builder.\n         */\n        public Builder setQrFlag(boolean query) {\n            this.query = query;\n            return this;\n        }\n\n        /**\n         * Set the authoritative answer flag.\n         *\n         * @param authoritativeAnswer Tge new authoritative answer value.\n         * @return a reference to this builder.\n         */\n        public Builder setAuthoritativeAnswer(boolean authoritativeAnswer) {\n            this.authoritativeAnswer = authoritativeAnswer;\n            return this;\n        }\n\n        /**\n         * Set the truncation bit on this DNS message.\n         *\n         * @param truncated The new truncated bit status.\n         * @return a reference to this builder.\n         */\n        public Builder setTruncated(boolean truncated) {\n            this.truncated = truncated;\n            return this;\n        }\n\n        /**\n         * Set the recursion desired flag on this message.\n         *\n         * @param recursionDesired The new recusrion setting.\n         * @return a reference to this builder.\n         */\n        public Builder setRecursionDesired(boolean recursionDesired) {\n            this.recursionDesired = recursionDesired;\n            return this;\n        }\n\n        /**\n         * Set the recursion available flog from this DNS message.\n         *\n\t\t * @param recursionAvailable The new recursion available status.\n         * @return a reference to this builder.\n         */\n        public Builder setRecursionAvailable(boolean recursionAvailable) {\n            this.recursionAvailable = recursionAvailable;\n            return this;\n        }\n\n        /**\n         * Set the authentic data flag on this DNS message.\n         *\n         * @param authenticData The new authentic data flag value.\n         * @return a reference to this builder.\n         */\n        public Builder setAuthenticData(boolean authenticData) {\n            this.authenticData = authenticData;\n            return this;\n        }\n\n        /**\n         * Change the check status of this packet.\n         *\n         * @param checkingDisabled The new check disabled value.\n         * @return a reference to this builder.\n         */\n        @Deprecated\n        public Builder setCheckDisabled(boolean checkingDisabled) {\n            this.checkingDisabled = checkingDisabled;\n            return this;\n        }\n\n        /**\n         * Change the check status of this packet.\n         *\n         * @param checkingDisabled The new check disabled value.\n         * @return a reference to this builder.\n         */\n        public Builder setCheckingDisabled(boolean checkingDisabled) {\n            this.checkingDisabled = checkingDisabled;\n            return this;\n        }\n\n        public void copyFlagsFrom(DnsMessage dnsMessage) {\n            this.query = dnsMessage.qr;\n            this.authoritativeAnswer = dnsMessage.authenticData;\n            this.truncated = dnsMessage.truncated;\n            this.recursionDesired = dnsMessage.recursionDesired;\n            this.recursionAvailable = dnsMessage.recursionAvailable;\n            this.authenticData = dnsMessage.authenticData;\n            this.checkingDisabled = dnsMessage.checkingDisabled;\n        }\n\n        public Builder setReceiveTimestamp(long receiveTimestamp) {\n            this.receiveTimestamp = receiveTimestamp;\n            return this;\n        }\n\n        public Builder addQuestion(Question question) {\n            if (questions == null) {\n                questions = new ArrayList<>(1);\n            }\n            questions.add(question);\n            return this;\n        }\n\n        /**\n         * Set the question part of this message.\n         *\n         * @param questions The questions.\n         * @return a reference to this builder.\n         */\n        public Builder setQuestions(List<Question> questions) {\n            this.questions = questions;\n            return this;\n        }\n\n        /**\n         * Set the question part of this message.\n         *\n         * @param question The question.\n         * @return a reference to this builder.\n         */\n        public Builder setQuestion(Question question) {\n            this.questions = new ArrayList<>(1);\n            this.questions.add(question);\n            return this;\n        }\n\n        public Builder addAnswer(Record<? extends Data> answer) {\n            if (answerSection == null) {\n                answerSection = new ArrayList<>(1);\n            }\n            answerSection.add(answer);\n            return this;\n        }\n\n        public Builder addAnswers(Collection<Record<? extends Data>> records) {\n            if (answerSection == null) {\n                answerSection = new ArrayList<>(records.size());\n            }\n            answerSection.addAll(records);\n            return this;\n        }\n\n        public Builder setAnswers(Collection<Record<? extends Data>> records) {\n            answerSection = new ArrayList<>(records.size());\n            answerSection.addAll(records);\n            return this;\n        }\n\n        public List<Record<? extends Data>> getAnswers() {\n            if (answerSection == null) {\n                return Collections.emptyList();\n            }\n            return answerSection;\n        }\n\n        public Builder addNameserverRecords(Record<? extends Data> record) {\n            if (authoritySection == null) {\n                authoritySection = new ArrayList<>(8);\n            }\n            authoritySection.add(record);\n            return this;\n        }\n\n        public Builder setNameserverRecords(Collection<Record<? extends Data>> records) {\n            authoritySection = new ArrayList<>(records.size());\n            authoritySection.addAll(records);\n            return this;\n        }\n\n        public Builder setAdditionalResourceRecords(Collection<Record<? extends Data>> records) {\n            additionalSection = new ArrayList<>(records.size());\n            additionalSection.addAll(records);\n            return this;\n        }\n\n        public Builder addAdditionalResourceRecord(Record<? extends Data> record) {\n            if (additionalSection == null) {\n                additionalSection = new ArrayList<>();\n            }\n            additionalSection.add(record);\n            return this;\n        }\n\n        public Builder addAdditionalResourceRecords(List<Record<? extends Data>> records) {\n            if (additionalSection == null) {\n                additionalSection = new ArrayList<>(records.size());\n            }\n            additionalSection.addAll(records);\n            return this;\n        }\n\n        public List<Record<? extends Data>> getAdditionalResourceRecords() {\n            if (additionalSection == null) {\n                return Collections.emptyList();\n            }\n            return additionalSection;\n        }\n\n        /**\n         * Get the {@link Edns} builder. If no builder has been set so far, then a new one will be created.\n         * <p>\n         * The EDNS record can be used to announce the supported size of UDP payload as well as additional flags.\n         * </p>\n         * <p>\n         * Note that some networks and firewalls are known to block big UDP payloads. 1280 should be a reasonable value,\n         * everything below 512 is treated as 512 and should work on all networks.\n         * </p>\n         *\n         * @return a EDNS builder.\n         */\n        public Edns.Builder getEdnsBuilder() {\n            if (ednsBuilder == null) {\n                ednsBuilder = Edns.builder();\n            }\n            return ednsBuilder;\n        }\n\n        public DnsMessage build() {\n            return new DnsMessage(this);\n        }\n\n        private void writeToStringBuilder(StringBuilder sb) {\n            sb.append('(')\n                .append(id)\n                .append(' ')\n                .append(opcode)\n                .append(' ')\n                .append(responseCode)\n                .append(' ');\n            if (query) {\n                sb.append(\"resp[qr=1]\");\n            } else {\n                sb.append(\"query[qr=0]\");\n            }\n            if (authoritativeAnswer)\n                sb.append(\" aa\");\n            if (truncated)\n                sb.append(\" tr\");\n            if (recursionDesired)\n                sb.append(\" rd\");\n            if (recursionAvailable)\n                sb.append(\" ra\");\n            if (authenticData)\n                sb.append(\" ad\");\n            if (checkingDisabled)\n                sb.append(\" cd\");\n            sb.append(\")\\n\");\n            if (questions != null) {\n                for (Question question : questions) {\n                    sb.append(\"[Q: \").append(question).append(\"]\\n\");\n                }\n            }\n            if (answerSection != null) {\n                for (Record<? extends Data> record : answerSection) {\n                    sb.append(\"[A: \").append(record).append(\"]\\n\");\n                }\n            }\n            if (authoritySection != null) {\n                for (Record<? extends Data> record : authoritySection) {\n                    sb.append(\"[N: \").append(record).append(\"]\\n\");\n                }\n            }\n            if (additionalSection != null) {\n                for (Record<? extends Data> record : additionalSection) {\n                    sb.append(\"[X: \");\n                    Edns edns = Edns.fromRecord(record);\n                    if (edns != null) {\n                        sb.append(edns.toString());\n                    } else {\n                        sb.append(record);\n                    }\n                    sb.append(\"]\\n\");\n                }\n            }\n\n            // Strip trailing newline.\n            if (sb.charAt(sb.length() - 1) == '\\n') {\n                sb.setLength(sb.length() - 1);\n            }\n        }\n\n        @Override\n        public String toString() {\n            StringBuilder sb = new StringBuilder(\"Builder of DnsMessage\");\n            writeToStringBuilder(sb);\n            return sb.toString();\n        }\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnsmessage/Question.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsmessage;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.util.Arrays;\n\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.Record.CLASS;\nimport org.minidns.record.Record.TYPE;\n\n/**\n * A DNS question (request).\n */\npublic class Question {\n\n    /**\n     * The question string (e.g. \"measite.de\").\n     */\n    public final DnsName name;\n\n    /**\n     * The question type (e.g. A).\n     */\n    public final TYPE type;\n\n    /**\n     * The question class (usually IN for Internet).\n     */\n    public final CLASS clazz;\n\n    /**\n     * UnicastQueries have the highest bit of the CLASS field set to 1.\n     */\n    private final boolean unicastQuery;\n\n    /**\n     * Cache for the serialized object.\n     */\n    private byte[] byteArray;\n\n    /**\n     * Create a dns question for the given name/type/class.\n     * @param name The name e.g. \"measite.de\".\n     * @param type The type, e.g. A.\n     * @param clazz The class, usually IN (internet).\n     * @param unicastQuery True if this is a unicast query.\n     */\n    public Question(CharSequence name, TYPE type, CLASS clazz, boolean unicastQuery) {\n        this(DnsName.from(name), type, clazz, unicastQuery);\n    }\n\n    public Question(DnsName name, TYPE type, CLASS clazz, boolean unicastQuery) {\n        assert name != null;\n        assert type != null;\n        assert clazz != null;\n        this.name = name;\n        this.type = type;\n        this.clazz = clazz;\n        this.unicastQuery = unicastQuery;\n    }\n\n    /**\n     * Create a dns question for the given name/type/class.\n     * @param name The name e.g. \"measite.de\".\n     * @param type The type, e.g. A.\n     * @param clazz The class, usually IN (internet).\n     */\n    public Question(DnsName name, TYPE type, CLASS clazz) {\n        this(name, type, clazz, false);\n    }\n\n    /**\n     * Create a dns question for the given name/type/IN (internet class).\n     * @param name The name e.g. \"measite.de\".\n     * @param type The type, e.g. A.\n     */\n    public Question(DnsName name, TYPE type) {\n        this(name, type, CLASS.IN);\n    }\n\n    /**\n     * Create a dns question for the given name/type/class.\n     * @param name The name e.g. \"measite.de\".\n     * @param type The type, e.g. A.\n     * @param clazz The class, usually IN (internet).\n     */\n    public Question(CharSequence name, TYPE type, CLASS clazz) {\n        this(DnsName.from(name), type, clazz);\n    }\n\n    /**\n     * Create a dns question for the given name/type/IN (internet class).\n     * @param name The name e.g. \"measite.de\".\n     * @param type The type, e.g. A.\n     */\n    public Question(CharSequence name, TYPE type) {\n        this(DnsName.from(name), type);\n    }\n\n    /**\n     * Parse a byte array and rebuild the dns question from it.\n     * @param dis The input stream.\n     * @param data The plain data (for dns name references).\n     * @throws IOException On errors (read outside of packet).\n     */\n    public Question(DataInputStream dis, byte[] data) throws IOException {\n        name = DnsName.parse(dis, data);\n        type = TYPE.getType(dis.readUnsignedShort());\n        clazz = CLASS.getClass(dis.readUnsignedShort());\n        unicastQuery = false;\n    }\n\n    /**\n     * Generate a binary paket for this dns question.\n     * @return The dns question.\n     */\n    public byte[] toByteArray() {\n        if (byteArray == null) {\n            ByteArrayOutputStream baos = new ByteArrayOutputStream(512);\n            DataOutputStream dos = new DataOutputStream(baos);\n\n            try {\n                name.writeToStream(dos);\n                dos.writeShort(type.getValue());\n                dos.writeShort(clazz.getValue() | (unicastQuery ? (1 << 15) : 0));\n                dos.flush();\n            } catch (IOException e) {\n                // Should never happen\n                throw new RuntimeException(e);\n            }\n            byteArray = baos.toByteArray();\n        }\n        return byteArray;\n    }\n\n    @Override\n    public int hashCode() {\n        return Arrays.hashCode(toByteArray());\n    }\n\n    @Override\n    public boolean equals(Object other) {\n        if (this == other) {\n            return true;\n        }\n        if (!(other instanceof Question)) {\n            return false;\n        }\n        byte[] t = toByteArray();\n        byte[] o = ((Question) other).toByteArray();\n        return Arrays.equals(t, o);\n    }\n\n    @Override\n    public String toString() {\n        return name.getRawAce() + \".\\t\" + clazz + '\\t' + type;\n    }\n\n    public DnsMessage.Builder asMessageBuilder() {\n        DnsMessage.Builder builder = DnsMessage.builder();\n        builder.setQuestion(this);\n        return builder;\n    }\n\n    public DnsMessage asQueryMessage() {\n        return asMessageBuilder().build();\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnsname/DnsName.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsname;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.DataInputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.io.Serializable;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Locale;\n\nimport org.minidns.dnslabel.DnsLabel;\nimport org.minidns.idna.MiniDnsIdna;\nimport org.minidns.util.SafeCharSequence;\n\n/**\n * A DNS name, also called \"domain name\". A DNS name consists of multiple 'labels' (see {@link DnsLabel}) and is subject to certain restrictions (see\n * for example <a href=\"https://tools.ietf.org/html/rfc3696#section-2\">RFC 3696 § 2.</a>).\n * <p>\n * Instances of this class can be created by using {@link #from(String)}.\n * </p>\n * <p>\n * This class holds three representations of a DNS name: ACE, raw ACE and IDN. ACE (ASCII Compatible Encoding), which\n * can be accessed via {@link #ace}, represents mostly the data that got send over the wire. But since DNS names are\n * case insensitive, the ACE value is normalized to lower case. You can use {@link #getRawAce()} to get the raw ACE data\n * that was received, which possibly includes upper case characters. The IDN (Internationalized Domain Name), that is\n * the DNS name as it should be shown to the user, can be retrieved using {@link #asIdn()}.\n * </p>\n * More information about Internationalized Domain Names can be found at:\n * <ul>\n * <li><a href=\"https://unicode.org/reports/tr46/\">UTS #46 - Unicode IDNA Compatibility Processing</a>\n * <li><a href=\"https://tools.ietf.org/html/rfc8753\">RFC 8753 - Internationalized Domain Names for Applications (IDNA) Review for New Unicode Versions</a>\n * </ul>\n *\n * @see <a href=\"https://tools.ietf.org/html/rfc3696\">RFC 3696</a>\n * @see DnsLabel\n * @author Florian Schmaus\n *\n */\npublic final class DnsName extends SafeCharSequence implements Serializable, Comparable<DnsName> {\n\n    /**\n     * \n     */\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * @see <a href=\"https://www.ietf.org/rfc/rfc3490.txt\">RFC 3490 § 3.1 1.</a>\n     */\n    private static final String LABEL_SEP_REGEX = \"[.\\u3002\\uFF0E\\uFF61]\";\n\n    /**\n     * See <a href=\"https://tools.ietf.org/html/rfc1035\">RFC 1035 § 2.3.4.</a>\n     */\n    static final int MAX_DNSNAME_LENGTH_IN_OCTETS = 255;\n\n    public static final int MAX_LABELS = 128;\n\n    public static final DnsName ROOT = new DnsName(\".\");\n\n    public static final DnsName IN_ADDR_ARPA = new DnsName(\"in-addr.arpa\");\n\n    public static final DnsName IP6_ARPA = new DnsName(\"ip6.arpa\");\n\n    /**\n     * Whether or not the DNS name is validated on construction.\n     */\n    public static boolean VALIDATE = true;\n\n    /**\n     * The DNS name in ASCII Compatible Encoding (ACE).\n     */\n    public final String ace;\n\n    /**\n     * The DNS name in raw format, i.e. as it was received from the remote server. This means that compared to\n     * {@link #ace}, this String may not be lower-cased.\n     */\n    private final String rawAce;\n\n    private transient byte[] bytes;\n\n    private transient byte[] rawBytes;\n\n    private transient String idn;\n\n    private transient String domainpart;\n\n    private transient String hostpart;\n\n    /**\n     * The labels in <b>reverse</b> order.\n     */\n    private transient DnsLabel[] labels;\n\n    private transient DnsLabel[] rawLabels;\n\n    private transient int hashCode;\n\n    private int size = -1;\n\n    private DnsName(String name) {\n        this(name, true);\n    }\n\n    private DnsName(String name, boolean inAce) {\n        if (name.isEmpty()) {\n            rawAce = ROOT.rawAce;\n        } else {\n            final int nameLength = name.length();\n            final int nameLastPos = nameLength - 1;\n\n            // Strip potential trailing dot. N.B. that we require nameLength > 2, because we don't want to strip the one\n            // character string containing only a single dot to the empty string.\n            if (nameLength >= 2 && name.charAt(nameLastPos) == '.') {\n                name = name.subSequence(0, nameLastPos).toString();\n            }\n\n            if (inAce) {\n                // Name is already in ACE format.\n                rawAce = name;\n            } else {\n                rawAce = MiniDnsIdna.toASCII(name);\n            }\n        }\n\n        ace = rawAce.toLowerCase(Locale.US);\n\n        if (!VALIDATE) {\n            return;\n        }\n\n        // Validate the DNS name.\n        validateMaxDnsnameLengthInOctets();\n    }\n\n    private DnsName(DnsLabel[] rawLabels, boolean validateMaxDnsnameLength) {\n        this.rawLabels = rawLabels;\n        this.labels = new DnsLabel[rawLabels.length];\n\n        int size = 0;\n        for (int i = 0; i < rawLabels.length; i++) {\n            size += rawLabels[i].length() + 1;\n            labels[i] = rawLabels[i].asLowercaseVariant();\n        }\n\n        rawAce = labelsToString(rawLabels, size);\n        ace    = labelsToString(labels,    size);\n\n        // The following condition is deliberately designed that VALIDATE=false causes the validation to be skipped even\n        // if validateMaxDnsnameLength is set to true. There is no need to validate even if this constructor is called\n        // with validateMaxDnsnameLength set to true if VALIDATE is globally set to false.\n        if (!validateMaxDnsnameLength || !VALIDATE) {\n            return;\n        }\n\n        validateMaxDnsnameLengthInOctets();\n    }\n\n    private static String labelsToString(DnsLabel[] labels, int stringLength) {\n        StringBuilder sb = new StringBuilder(stringLength);\n        for (int i = labels.length - 1; i >= 0; i--) {\n            sb.append(labels[i]).append('.');\n        }\n        sb.setLength(sb.length() - 1);\n        return sb.toString();\n    }\n\n    private void validateMaxDnsnameLengthInOctets() {\n        setBytesIfRequired();\n        if (bytes.length > MAX_DNSNAME_LENGTH_IN_OCTETS) {\n            throw new InvalidDnsNameException.DNSNameTooLongException(ace, bytes);\n        }\n    }\n\n    public void writeToStream(OutputStream os) throws IOException {\n        setBytesIfRequired();\n        os.write(bytes);\n    }\n\n    /**\n     * Serialize a domain name under IDN rules.\n     *\n     * @return The binary domain name representation.\n     */\n    public byte[] getBytes() {\n        setBytesIfRequired();\n        return bytes.clone();\n    }\n\n    public byte[] getRawBytes() {\n        if (rawBytes == null) {\n            setLabelsIfRequired();\n            rawBytes = toBytes(rawLabels);\n        }\n\n        return rawBytes.clone();\n    }\n\n    private void setBytesIfRequired() {\n        if (bytes != null)\n            return;\n\n        setLabelsIfRequired();\n        bytes = toBytes(labels);\n    }\n\n    private static byte[] toBytes(DnsLabel[] labels) {\n        ByteArrayOutputStream baos = new ByteArrayOutputStream(64);\n        for (int i = labels.length - 1; i >= 0; i--) {\n            labels[i].writeToBoas(baos);\n        }\n\n        baos.write(0);\n\n        assert baos.size() <= MAX_DNSNAME_LENGTH_IN_OCTETS;\n\n        return baos.toByteArray();\n    }\n\n    private void setLabelsIfRequired() {\n        if (labels != null && rawLabels != null) return;\n\n        if (isRootLabel()) {\n            rawLabels = labels = new DnsLabel[0];\n            return;\n        }\n\n        labels = getLabels(ace);\n        rawLabels = getLabels(rawAce);\n    }\n\n    private static DnsLabel[] getLabels(String ace) {\n        String[] labels = ace.split(LABEL_SEP_REGEX, MAX_LABELS);\n\n        // Reverse the labels, so that 'foo, example, org' becomes 'org, example, foo'.\n        for (int i = 0; i < labels.length / 2; i++) {\n            String t = labels[i];\n            int j = labels.length - i - 1;\n            labels[i] = labels[j];\n            labels[j] = t;\n        }\n\n        try {\n            return DnsLabel.from(labels);\n        } catch (DnsLabel.LabelToLongException e) {\n            throw new InvalidDnsNameException.LabelTooLongException(ace, e.label);\n        }\n    }\n\n    /**\n     * Return the ACE (ASCII Compatible Encoding) version of this DNS name. Note\n     * that this method may return a String containing null bytes. Those Strings are\n     * notoriously difficult to handle from a security perspective. Therefore it is\n     * recommended to use {@link #toString()} instead, which will return a sanitized\n     * String.\n     *\n     * @return the ACE version of this DNS name.\n     * @since 1.1.0\n     */\n    public String getAce() {\n        return ace;\n    }\n\n    /**\n     * Returns the raw ACE version of this DNS name. That is, the version as it was\n     * received over the wire. Most notably, this version may include uppercase\n     * letters.\n     *\n     * <b>Please refer to {@link #getAce()} for a discussion of the security\n     * implications when working with the ACE representation of a DNS name.</b>\n     *\n     * @return the raw ACE version of this DNS name.\n     * @see #getAce()\n     */\n    public String getRawAce() {\n        return rawAce;\n    }\n\n    public String asIdn() {\n        if (idn != null)\n            return idn;\n\n        idn = MiniDnsIdna.toUnicode(ace);\n        return idn;\n    }\n\n    /**\n     * Domainpart in ACE representation.\n     *\n     * @return the domainpart in ACE representation.\n     */\n    public String getDomainpart() {\n        setHostnameAndDomainpartIfRequired();\n        return domainpart;\n    }\n\n    /**\n     * Hostpart in ACE representation.\n     *\n     * @return the hostpart in ACE representation.\n     */\n    public String getHostpart() {\n        setHostnameAndDomainpartIfRequired();\n        return hostpart;\n    }\n\n    public DnsLabel getHostpartLabel() {\n        setLabelsIfRequired();\n        return labels[labels.length - 1];\n    }\n\n    private void setHostnameAndDomainpartIfRequired() {\n        if (hostpart != null) return;\n\n        String[] parts = ace.split(LABEL_SEP_REGEX, 2);\n        hostpart = parts[0];\n        if (parts.length > 1) {\n            domainpart = parts[1];\n        } else {\n            domainpart = \"\";\n        }\n    }\n\n    public int size() {\n        if (size < 0) {\n            if (isRootLabel()) {\n                size = 1;\n            } else {\n                size = ace.length() + 2;\n            }\n        }\n        return size;\n    }\n\n    private transient String safeToStringRepresentation;\n\n    @Override\n    public String toString() {\n        if (safeToStringRepresentation == null) {\n            setLabelsIfRequired();\n            if (labels.length == 0) {\n                return \".\";\n            }\n\n            StringBuilder sb = new StringBuilder();\n            for (int i = labels.length - 1; i >= 0; i--) {\n                // Note that it is important that we append the result of DnsLabel.toString() to\n                // the StringBuilder. As only the result of toString() is the safe label\n                // representation.\n                String safeLabelRepresentation = labels[i].toString();\n                sb.append(safeLabelRepresentation);\n                if (i != 0) {\n                    sb.append('.');\n                }\n            }\n            safeToStringRepresentation = sb.toString();\n        }\n\n        return safeToStringRepresentation;\n    }\n\n    public static DnsName from(CharSequence name) {\n        return from(name.toString());\n    }\n\n    public static DnsName from(String name) {\n        return new DnsName(name, false);\n    }\n\n    /**\n     * Create a DNS name by \"concatenating\" the child under the parent name. The child can also be seen as the \"left\"\n     * part of the resulting DNS name and the parent is the \"right\" part.\n     * <p>\n     * For example using \"i.am.the.child\" as child and \"of.this.parent.example\" as parent, will result in a DNS name:\n     * \"i.am.the.child.of.this.parent.example\".\n     * </p>\n     *\n     * @param child the child DNS name.\n     * @param parent the parent DNS name.\n     * @return the resulting of DNS name.\n     */\n    public static DnsName from(DnsName child, DnsName parent) {\n        child.setLabelsIfRequired();\n        parent.setLabelsIfRequired();\n\n        DnsLabel[] rawLabels = new DnsLabel[child.rawLabels.length + parent.rawLabels.length];\n        System.arraycopy(parent.rawLabels, 0, rawLabels, 0, parent.rawLabels.length);\n        System.arraycopy(child.rawLabels, 0, rawLabels, parent.rawLabels.length, child.rawLabels.length);\n        return new DnsName(rawLabels, true);\n    }\n\n    public static DnsName from(CharSequence child, DnsName parent) {\n        DnsLabel childLabel = DnsLabel.from(child.toString());\n        return DnsName.from(childLabel, parent);\n    }\n\n    public static DnsName from(DnsLabel child, DnsName parent) {\n        parent.setLabelsIfRequired();\n\n        DnsLabel[] rawLabels = new DnsLabel[parent.rawLabels.length + 1];\n        System.arraycopy(parent.rawLabels, 0, rawLabels, 0, parent.rawLabels.length);\n        rawLabels[parent.rawLabels.length] = child;\n        return new DnsName(rawLabels, true);\n    }\n\n    public static DnsName from(DnsLabel grandchild, DnsLabel child, DnsName parent) {\n        parent.setBytesIfRequired();\n\n        DnsLabel[] rawLabels = new DnsLabel[parent.rawLabels.length + 2];\n        System.arraycopy(parent.rawLabels, 0, rawLabels, 0, parent.rawLabels.length);\n        rawLabels[parent.rawLabels.length] = child;\n        rawLabels[parent.rawLabels.length + 1] = grandchild;\n        return new DnsName(rawLabels, true);\n    }\n\n    public static DnsName from(DnsName... nameComponents) {\n        int labelCount = 0;\n        for (DnsName component : nameComponents) {\n            component.setLabelsIfRequired();\n            labelCount += component.rawLabels.length;\n        }\n\n        DnsLabel[] rawLabels = new DnsLabel[labelCount];\n        int destLabelPos = 0;\n        for (int i = nameComponents.length - 1; i >= 0; i--) {\n            DnsName component = nameComponents[i];\n            System.arraycopy(component.rawLabels, 0, rawLabels, destLabelPos, component.rawLabels.length);\n            destLabelPos += component.rawLabels.length;\n        }\n\n        return new DnsName(rawLabels, true);\n    }\n\n    public static DnsName from(String[] parts) {\n        DnsLabel[] rawLabels = DnsLabel.from(parts);\n\n        return new DnsName(rawLabels, true);\n    }\n\n    /**\n     * Parse a domain name starting at the current offset and moving the input\n     * stream pointer past this domain name (even if cross references occure).\n     *\n     * @param dis  The input stream.\n     * @param data The raw data (for cross references).\n     * @return The domain name string.\n     * @throws IOException Should never happen.\n     */\n    public static DnsName parse(DataInputStream dis, byte[] data)\n            throws IOException {\n        int c = dis.readUnsignedByte();\n        if ((c & 0xc0) == 0xc0) {\n            c = ((c & 0x3f) << 8) + dis.readUnsignedByte();\n            HashSet<Integer> jumps = new HashSet<Integer>();\n            jumps.add(c);\n            return parse(data, c, jumps);\n        }\n        if (c == 0) {\n            return DnsName.ROOT;\n        }\n        byte[] b = new byte[c];\n        dis.readFully(b);\n\n        String childLabelString = new String(b, StandardCharsets.US_ASCII);\n        DnsName child = new DnsName(childLabelString);\n\n        DnsName parent = parse(dis, data);\n        return DnsName.from(child, parent);\n    }\n\n    /**\n     * Parse a domain name starting at the given offset.\n     *\n     * @param data   The raw data.\n     * @param offset The offset.\n     * @param jumps  The list of jumps (by now).\n     * @return The parsed domain name.\n     * @throws IllegalStateException on cycles.\n     */\n     @SuppressWarnings(\"NonApiType\")\n    private static DnsName parse(byte[] data, int offset, HashSet<Integer> jumps)\n            throws IllegalStateException {\n        int c = data[offset] & 0xff;\n        if ((c & 0xc0) == 0xc0) {\n            c = ((c & 0x3f) << 8) + (data[offset + 1] & 0xff);\n            if (jumps.contains(c)) {\n                throw new IllegalStateException(\"Cyclic offsets detected.\");\n            }\n            jumps.add(c);\n            return parse(data, c, jumps);\n        }\n        if (c == 0) {\n            return DnsName.ROOT;\n        }\n\n        String childLabelString = new String(data, offset + 1, c, StandardCharsets.US_ASCII);\n        DnsName child = new DnsName(childLabelString);\n\n        DnsName parent = parse(data, offset + 1 + c, jumps);\n        return DnsName.from(child, parent);\n    }\n\n    @Override\n    public int compareTo(DnsName other) {\n        return ace.compareTo(other.ace);\n    }\n\n    @Override\n    public boolean equals(Object other) {\n        if (other == null) return false;\n\n        if (other instanceof DnsName) {\n            DnsName otherDnsName = (DnsName) other;\n            setBytesIfRequired();\n            otherDnsName.setBytesIfRequired();\n            return Arrays.equals(bytes, otherDnsName.bytes);\n        }\n\n        return false;\n    }\n\n    @Override\n    public int hashCode() {\n        if (hashCode == 0 && !isRootLabel()) {\n            setBytesIfRequired();\n            hashCode = Arrays.hashCode(bytes);\n        }\n        return hashCode;\n    }\n\n    public boolean isDirectChildOf(DnsName parent) {\n        setLabelsIfRequired();\n        parent.setLabelsIfRequired();\n        int parentLabelsCount = parent.labels.length;\n\n        if (labels.length - 1 != parentLabelsCount)\n            return false;\n\n        for (int i = 0; i < parent.labels.length; i++) {\n            if (!labels[i].equals(parent.labels[i]))\n                return false;\n        }\n\n        return true;\n    }\n\n    public boolean isChildOf(DnsName parent) {\n        setLabelsIfRequired();\n        parent.setLabelsIfRequired();\n\n        if (labels.length < parent.labels.length)\n            return false;\n\n        for (int i = 0; i < parent.labels.length; i++) {\n            if (!labels[i].equals(parent.labels[i]))\n                return false;\n        }\n\n        return true;\n    }\n\n    public int getLabelCount() {\n        setLabelsIfRequired();\n        return labels.length;\n    }\n\n    /**\n     * Get a copy of the labels of this DNS name. The resulting array will contain the labels in reverse order, that is,\n     * the top-level domain will be at res[0].\n     *\n     * @return an array of the labels in reverse order.\n     */\n    public DnsLabel[] getLabels() {\n        setLabelsIfRequired();\n        return labels.clone();\n    }\n\n\n    public DnsLabel getLabel(int labelNum) {\n        setLabelsIfRequired();\n        return labels[labelNum];\n    }\n\n    /**\n     * Get a copy of the raw labels of this DNS name. The resulting array will contain the labels in reverse order, that is,\n     * the top-level domain will be at res[0].\n     *\n     * @return an array of the raw labels in reverse order.\n     */\n    public DnsLabel[] getRawLabels() {\n        setLabelsIfRequired();\n        return rawLabels.clone();\n    }\n\n    public DnsName stripToLabels(int labelCount) {\n        setLabelsIfRequired();\n\n        if (labelCount > labels.length) {\n            throw new IllegalArgumentException();\n        }\n\n        if (labelCount == labels.length) {\n            return this;\n        }\n\n        if (labelCount == 0) {\n            return ROOT;\n        }\n\n        DnsLabel[] stripedLabels = Arrays.copyOfRange(rawLabels, 0, labelCount);\n\n        return new DnsName(stripedLabels, false);\n    }\n\n    /**\n     * Return the parent of this DNS label. Will return the root label if this label itself is the root label (because there is no parent of root).\n     * <p>\n     * For example:\n     * </p>\n     * <ul>\n     *  <li><code>\"foo.bar.org\".getParent() == \"bar.org\"</code></li>\n     *  <li><code> \".\".getParent() == \".\"</code></li>\n     * </ul>\n     * @return the parent of this DNS label.\n     */\n    public DnsName getParent() {\n        if (isRootLabel()) return ROOT;\n        return stripToLabels(getLabelCount() - 1);\n    }\n\n    public boolean isRootLabel() {\n        return ace.isEmpty() || ace.equals(\".\");\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/dnsname/InvalidDnsNameException.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsname;\n\nimport org.minidns.dnslabel.DnsLabel;\n\npublic abstract class InvalidDnsNameException extends IllegalStateException {\n\n    private static final long serialVersionUID = 1L;\n\n    protected final String ace;\n\n    protected InvalidDnsNameException(String ace) {\n        this.ace = ace;\n    }\n\n    public static class LabelTooLongException extends InvalidDnsNameException {\n        /**\n         * \n         */\n        private static final long serialVersionUID = 1L;\n\n        private final String label;\n\n        public LabelTooLongException(String ace, String label) {\n            super(ace);\n            this.label = label;\n        }\n\n        @Override\n        public String getMessage() {\n            return \"The DNS name '\" + ace + \"' contains the label '\" + label\n                    + \"' which exceeds the maximum label length of \" + DnsLabel.MAX_LABEL_LENGTH_IN_OCTETS + \" octets by \"\n                    + (label.length() - DnsLabel.MAX_LABEL_LENGTH_IN_OCTETS) + \" octets.\";\n        }\n    }\n\n    public static class DNSNameTooLongException extends InvalidDnsNameException {\n        /**\n         * \n         */\n        private static final long serialVersionUID = 1L;\n\n        private final byte[] bytes;\n\n        public DNSNameTooLongException(String ace, byte[] bytes) {\n            super(ace);\n            this.bytes = bytes;\n        }\n\n        @Override\n        public String getMessage() {\n            return \"The DNS name '\" + ace + \"' exceeds the maximum name length of \"\n                    + DnsName.MAX_DNSNAME_LENGTH_IN_OCTETS + \" octets by \"\n                    + (bytes.length - DnsName.MAX_DNSNAME_LENGTH_IN_OCTETS) + \" octets.\";\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/edns/Edns.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.edns;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.Data;\nimport org.minidns.record.OPT;\nimport org.minidns.record.Record;\nimport org.minidns.record.Record.TYPE;\n\n/**\n * EDNS - Extension Mechanism for DNS.\n *\n * @see <a href=\"https://tools.ietf.org/html/rfc6891\">RFC 6891 - Extension Mechanisms for DNS (EDNS(0))</a>\n *\n */\npublic class Edns {\n\n    /**\n     * Inform the dns server that the client supports DNSSEC.\n     */\n    public static final int FLAG_DNSSEC_OK = 0x8000;\n\n    /**\n     * The EDNS option code.\n     *\n     * @see <a href=\"http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11\">IANA - DNS EDNS0 Option Codes (OPT)</a>\n     */\n    public enum OptionCode {\n        UNKNOWN(-1, UnknownEdnsOption.class),\n        NSID(3, Nsid.class),\n        ;\n\n        private static Map<Integer, OptionCode> INVERSE_LUT = new HashMap<>(OptionCode.values().length);\n\n        static {\n            for (OptionCode optionCode : OptionCode.values()) {\n                INVERSE_LUT.put(optionCode.asInt, optionCode);\n            }\n        }\n\n        public final int asInt;\n        public final Class<? extends EdnsOption> clazz;\n\n        OptionCode(int optionCode, Class<? extends EdnsOption> clazz) {\n            this.asInt = optionCode;\n            this.clazz = clazz;\n        }\n\n        public static OptionCode from(int optionCode) {\n            OptionCode res = INVERSE_LUT.get(optionCode);\n            if (res == null) res = OptionCode.UNKNOWN;\n            return res;\n        }\n    }\n\n    public final int udpPayloadSize;\n\n    /**\n     * 8-bit extended return code.\n     *\n     * RFC 6891 § 6.1.3 EXTENDED-RCODE \n     */\n    public final int extendedRcode;\n\n    /**\n     * 8-bit version field.\n     *\n     * RFC 6891 § 6.1.3 VERSION\n     */\n    public final int version;\n\n    /**\n     * 16-bit flags.\n     *\n     * RFC 6891 § 6.1.4\n     */\n    public final int flags;\n\n    public final List<EdnsOption> variablePart;\n\n    public final boolean dnssecOk;\n\n    private Record<OPT> optRecord;\n\n    public Edns(Record<OPT> optRecord) {\n        assert optRecord.type == TYPE.OPT;\n        udpPayloadSize = optRecord.clazzValue;\n        extendedRcode = (int) ((optRecord.ttl >> 8) & 0xff);\n        version = (int) ((optRecord.ttl >> 16) & 0xff);\n        flags = (int) optRecord.ttl & 0xffff;\n\n        dnssecOk = (optRecord.ttl & FLAG_DNSSEC_OK) > 0;\n\n        OPT opt = optRecord.payloadData;\n        variablePart = opt.variablePart;\n        this.optRecord = optRecord;\n    }\n\n    public Edns(Builder builder) {\n        udpPayloadSize = builder.udpPayloadSize;\n        extendedRcode = builder.extendedRcode;\n        version = builder.version;\n        int flags = 0;\n        if (builder.dnssecOk) {\n            flags |= FLAG_DNSSEC_OK;\n        }\n        dnssecOk = builder.dnssecOk;\n        this.flags = flags;\n        if (builder.variablePart != null) {\n            variablePart = builder.variablePart;\n        } else {\n            variablePart = Collections.emptyList();\n        }\n    }\n\n    @SuppressWarnings({\"unchecked\", \"TypeParameterUnusedInFormals\"})\n    public <O extends EdnsOption> O getEdnsOption(OptionCode optionCode) {\n        for (EdnsOption o : variablePart) {\n            if (o.getOptionCode().equals(optionCode)) {\n                return (O) o;\n            }\n        }\n        return null;\n    }\n\n    public Record<OPT> asRecord() {\n        if (optRecord == null) {\n            long optFlags = flags;\n            optFlags |= extendedRcode << 8;\n            optFlags |= version << 16;\n            optRecord = new Record<OPT>(DnsName.ROOT, Record.TYPE.OPT, udpPayloadSize, optFlags, new OPT(variablePart));\n        }\n        return optRecord;\n    }\n\n    private String terminalOutputCache;\n\n    public String asTerminalOutput() {\n        if (terminalOutputCache == null) {\n            StringBuilder sb = new StringBuilder();\n            sb.append(\"EDNS: version: \").append(version).append(\", flags:\");\n            if (dnssecOk)\n                sb.append(\" do\");\n            sb.append(\"; udp: \").append(udpPayloadSize);\n            if (!variablePart.isEmpty()) {\n                sb.append('\\n');\n                Iterator<EdnsOption> it = variablePart.iterator();\n                while (it.hasNext()) {\n                    EdnsOption edns = it.next();\n                    sb.append(edns.getOptionCode()).append(\": \");\n                    sb.append(edns.asTerminalOutput());\n                    if (it.hasNext()) {\n                        sb.append('\\n');\n                    }\n                }\n            }\n            terminalOutputCache = sb.toString();\n        }\n        return terminalOutputCache;\n    }\n\n    @Override\n    public String toString() {\n        return asTerminalOutput();\n    }\n\n    public static Edns fromRecord(Record<? extends Data> record) {\n        if (record.type != TYPE.OPT) return null;\n\n        @SuppressWarnings(\"unchecked\")\n        Record<OPT> optRecord = (Record<OPT>) record;\n        return new Edns(optRecord);\n    }\n\n    public static Builder builder() {\n        return new Builder();\n    }\n\n    public static final class Builder {\n        private int udpPayloadSize;\n        private int extendedRcode;\n        private int version;\n        private boolean dnssecOk;\n        private List<EdnsOption> variablePart;\n\n        private Builder() {\n        }\n\n        public Builder setUdpPayloadSize(int udpPayloadSize) {\n            if (udpPayloadSize > 0xffff) {\n                throw new IllegalArgumentException(\"UDP payload size must not be greater than 65536, was \" + udpPayloadSize);\n            }\n            this.udpPayloadSize = udpPayloadSize;\n            return this;\n        }\n\n        public Builder setDnssecOk(boolean dnssecOk) {\n            this.dnssecOk = dnssecOk;\n            return this;\n        }\n\n        public Builder setDnssecOk() {\n            dnssecOk = true;\n            return this;\n        }\n\n        public Builder addEdnsOption(EdnsOption ednsOption) {\n            if (variablePart == null) {\n                variablePart = new ArrayList<>(4);\n            }\n            variablePart.add(ednsOption);\n            return this;\n        }\n\n        public Edns build() {\n            return new Edns(this);\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/edns/EdnsOption.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.edns;\n\nimport java.io.DataOutputStream;\nimport java.io.IOException;\n\nimport org.minidns.edns.Edns.OptionCode;\n\npublic abstract class EdnsOption {\n\n    public final int optionCode;\n    public final int optionLength;\n\n    protected final byte[] optionData;\n\n    protected EdnsOption(int optionCode, byte[] optionData) {\n        this.optionCode = optionCode;\n        this.optionLength = optionData.length;\n        this.optionData = optionData;\n    }\n\n    @SuppressWarnings(\"this-escape\")\n    protected EdnsOption(byte[] optionData) {\n        this.optionCode = getOptionCode().asInt;\n        this.optionLength = optionData.length;\n        this.optionData =  optionData;\n    }\n\n    public final void writeToDos(DataOutputStream dos) throws IOException {\n        dos.writeShort(optionCode);\n        dos.writeShort(optionLength);\n        dos.write(optionData);\n    }\n\n    public abstract OptionCode getOptionCode();\n\n    private String toStringCache;\n\n    @Override\n    public final String toString() {\n        if (toStringCache == null) {\n            toStringCache = toStringInternal().toString();\n        }\n        return toStringCache;\n    }\n\n    protected abstract CharSequence toStringInternal();\n\n    private String terminalOutputCache;\n\n    public final String asTerminalOutput() {\n        if (terminalOutputCache == null) {\n            terminalOutputCache = asTerminalOutputInternal().toString();\n        }\n        return terminalOutputCache;\n    }\n\n    protected abstract CharSequence asTerminalOutputInternal();\n\n    public static EdnsOption parse(int intOptionCode, byte[] optionData) {\n        OptionCode optionCode = OptionCode.from(intOptionCode);\n        EdnsOption res;\n        switch (optionCode) {\n        case NSID:\n            res = new Nsid(optionData);\n            break;\n        default:\n            res = new UnknownEdnsOption(intOptionCode, optionData);\n            break;\n        }\n        return res;\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/edns/Nsid.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.edns;\n\nimport java.nio.charset.StandardCharsets;\n\nimport org.minidns.edns.Edns.OptionCode;\nimport org.minidns.util.Hex;\n\npublic class Nsid extends EdnsOption {\n\n    public static final Nsid REQUEST = new Nsid();\n\n    private Nsid() {\n        this(new byte[0]);\n    }\n\n    public Nsid(byte[] payload) {\n        super(payload);\n    }\n\n    @Override\n    public OptionCode getOptionCode() {\n        return OptionCode.NSID;\n    }\n\n    @Override\n    protected CharSequence toStringInternal() {\n        String res = OptionCode.NSID + \": \";\n        res += new String(optionData, StandardCharsets.US_ASCII);\n        return res;\n    }\n\n    @Override\n    protected CharSequence asTerminalOutputInternal() {\n        return Hex.from(optionData);\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/edns/UnknownEdnsOption.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.edns;\n\nimport org.minidns.edns.Edns.OptionCode;\nimport org.minidns.util.Hex;\n\npublic class UnknownEdnsOption extends EdnsOption {\n\n    protected UnknownEdnsOption(int optionCode, byte[] optionData) {\n        super(optionCode, optionData);\n    }\n\n    @Override\n    public OptionCode getOptionCode() {\n        return OptionCode.UNKNOWN;\n    }\n\n    @Override\n    protected CharSequence asTerminalOutputInternal() {\n        return Hex.from(optionData);\n    }\n\n    @Override\n    protected CharSequence toStringInternal() {\n        return asTerminalOutputInternal();\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/idna/DefaultIdnaTransformator.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.idna;\n\nimport java.net.IDN;\n\nimport org.minidns.dnsname.DnsName;\n\npublic class DefaultIdnaTransformator implements IdnaTransformator {\n\n    @Override\n    public String toASCII(String input) {\n        // Special case if input is \".\", i.e. a string containing only a single dot character. This is a workaround for\n        // IDN.toASCII() implementations throwing an IllegalArgumentException on this input string (for example Android\n        // APIs level 26, see https://issuetracker.google.com/issues/113070416).\n        if (DnsName.ROOT.ace.equals(input)) {\n            return DnsName.ROOT.ace;\n        }\n\n        return IDN.toASCII(input);\n    }\n\n    @Override\n    public String toUnicode(String input) {\n        return IDN.toUnicode(input);\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/idna/IdnaTransformator.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.idna;\n\npublic interface IdnaTransformator {\n\n    String toASCII(String input);\n\n    String toUnicode(String input);\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/idna/MiniDnsIdna.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.idna;\n\npublic class MiniDnsIdna {\n\n    private static IdnaTransformator idnaTransformator = new DefaultIdnaTransformator();\n\n    public static String toASCII(String string) {\n        return idnaTransformator.toASCII(string);\n    }\n\n    public static String toUnicode(String string) {\n        return idnaTransformator.toUnicode(string);\n    }\n\n    public static void setActiveTransformator(IdnaTransformator idnaTransformator) {\n        if (idnaTransformator == null) {\n            throw new IllegalArgumentException();\n        }\n        MiniDnsIdna.idnaTransformator = idnaTransformator;\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/A.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\nimport java.net.Inet4Address;\n\nimport org.minidns.record.Record.TYPE;\nimport org.minidns.util.InetAddressUtil;\n\n/**\n * A record payload (ip pointer).\n */\npublic class A extends InternetAddressRR<Inet4Address> {\n\n    @Override\n    public TYPE getType() {\n        return TYPE.A;\n    }\n\n    public A(Inet4Address inet4Address) {\n        super(inet4Address);\n        assert ip.length == 4;\n    }\n\n    public A(int q1, int q2, int q3, int q4) {\n        super(new byte[] { (byte) q1, (byte) q2, (byte) q3, (byte) q4 });\n        if (q1 < 0 || q1 > 255 || q2 < 0 || q2 > 255 || q3 < 0 || q3 > 255 || q4 < 0 || q4 > 255) {\n            throw new IllegalArgumentException();\n        }\n    }\n\n    public A(byte[] ip) {\n        super(ip);\n        if (ip.length != 4) {\n            throw new IllegalArgumentException(\"IPv4 address in A record is always 4 byte\");\n        }\n    }\n\n    public A(CharSequence ipv4CharSequence) {\n        this(InetAddressUtil.ipv4From(ipv4CharSequence));\n    }\n\n    public static A parse(DataInputStream dis)\n            throws IOException {\n        byte[] ip = new byte[4];\n        dis.readFully(ip);\n        return new A(ip);\n    }\n\n    @Override\n    public String toString() {\n        return Integer.toString(ip[0] & 0xff) + \".\" +\n               Integer.toString(ip[1] & 0xff) + \".\" +\n               Integer.toString(ip[2] & 0xff) + \".\" +\n               Integer.toString(ip[3] & 0xff);\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/AAAA.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\nimport java.net.Inet6Address;\n\nimport org.minidns.record.Record.TYPE;\nimport org.minidns.util.InetAddressUtil;\n\n/**\n * AAAA payload (an ipv6 pointer).\n */\npublic class AAAA extends InternetAddressRR<Inet6Address> {\n\n    @Override\n    public TYPE getType() {\n        return TYPE.AAAA;\n    }\n\n    public AAAA(Inet6Address inet6address) {\n        super(inet6address);\n        assert ip.length == 16;\n    }\n\n    public AAAA(byte[] ip) {\n        super(ip);\n        if (ip.length != 16) {\n            throw new IllegalArgumentException(\"IPv6 address in AAAA record is always 16 byte\");\n        }\n    }\n\n    public AAAA(CharSequence ipv6CharSequence) {\n        this(InetAddressUtil.ipv6From(ipv6CharSequence));\n    }\n\n    public static AAAA parse(DataInputStream dis)\n            throws IOException {\n        byte[] ip = new byte[16];\n        dis.readFully(ip);\n        return new AAAA(ip);\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder sb = new StringBuilder();\n        for (int i = 0; i < ip.length; i += 2) {\n            if (i != 0) {\n                sb.append(':');\n            }\n            sb.append(Integer.toHexString(\n                ((ip[i] & 0xff) << 8) + (ip[i + 1] & 0xff)\n            ));\n        }\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/CNAME.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.Record.TYPE;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\n\n/**\n * CNAME payload (pointer to another domain / address).\n */\npublic class CNAME extends RRWithTarget {\n\n    public static CNAME parse(DataInputStream dis, byte[] data) throws IOException {\n        DnsName target = DnsName.parse(dis, data);\n        return new CNAME(target);\n    }\n\n    public CNAME(String target) {\n        this(DnsName.from(target));\n    }\n\n    public CNAME(DnsName target) {\n        super(target);\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.CNAME;\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/DLV.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\n\nimport org.minidns.constants.DnssecConstants.DigestAlgorithm;\nimport org.minidns.constants.DnssecConstants.SignatureAlgorithm;\n\n/**\n * DLV record payload.\n *\n * According to RFC4431, DLV has exactly the same format as DS records.\n */\npublic class DLV extends DelegatingDnssecRR {\n\n    public static DLV parse (DataInputStream dis, int length) throws IOException {\n        SharedData parsedData = DelegatingDnssecRR.parseSharedData(dis, length);\n        return new DLV(parsedData.keyTag, parsedData.algorithm, parsedData.digestType, parsedData.digest);\n    }\n\n    public DLV(int keyTag, byte algorithm, byte digestType, byte[] digest) {\n        super(keyTag, algorithm, digestType, digest);\n    }\n\n    public DLV(int keyTag, SignatureAlgorithm algorithm, DigestAlgorithm digestType, byte[] digest) {\n        super(keyTag, algorithm, digestType, digest);\n    }\n\n    @Override\n    public Record.TYPE getType() {\n        return Record.TYPE.DLV;\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/DNAME.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.Record.TYPE;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\n\n/**\n * A DNAME resource record.\n *\n * @see <a href=\"https://tools.ietf.org/html/rfc6672\">RFC 6672 - DNAME Redirection in the DNS</a>\n */\npublic class DNAME extends RRWithTarget {\n\n    public static DNAME parse(DataInputStream dis, byte[] data) throws IOException {\n        DnsName target = DnsName.parse(dis, data);\n        return new DNAME(target);\n    }\n\n    public DNAME(String target) {\n        this(DnsName.from(target));\n    }\n\n    public DNAME(DnsName target) {\n        super(target);\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.DNAME;\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/DNSKEY.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport org.minidns.constants.DnssecConstants.SignatureAlgorithm;\nimport org.minidns.record.Record.TYPE;\nimport org.minidns.util.Base64;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.util.Arrays;\n\n/**\n * DNSKEY record payload.\n */\npublic class DNSKEY extends Data {\n    /**\n     * Whether the key should be used as a secure entry point key.\n     *\n     * see RFC 3757\n     */\n    public static final short FLAG_SECURE_ENTRY_POINT = 0x1;\n\n    /**\n     * Whether the record holds a revoked key.\n     */\n    public static final short FLAG_REVOKE = 0x80;\n\n    /**\n     * Whether the record holds a DNS zone key.\n     */\n    public static final short FLAG_ZONE = 0x100;\n\n    /**\n     * Use the protocol defined in RFC 4034.\n     */\n    public static final byte PROTOCOL_RFC4034 = 3;\n\n    /**\n     * Bitmap of flags: {@link #FLAG_SECURE_ENTRY_POINT}, {@link #FLAG_REVOKE}, {@link #FLAG_ZONE}.\n     *\n     * @see <a href=\"https://www.iana.org/assignments/dnskey-flags/dnskey-flags.xhtml\">IANA - DNSKEY RR Flags</a>\n     */\n    public final short flags;\n\n    /**\n     * Must be {@link #PROTOCOL_RFC4034}.\n     */\n    public final byte protocol;\n\n    /**\n     * The public key's cryptographic algorithm used.\n     *\n     */\n    public final SignatureAlgorithm algorithm;\n\n    /**\n     * The byte value of the public key's cryptographic algorithm used.\n     *\n     */\n    public final byte algorithmByte;\n\n    /**\n     * The public key material. The format depends on the algorithm of the key being stored.\n     */\n    private final byte[] key;\n\n    /**\n     * This DNSKEY's key tag. Calculated just-in-time when using {@link #getKeyTag()}\n     */\n    private transient Integer keyTag;\n\n    public static DNSKEY parse(DataInputStream dis, int length) throws IOException {\n        short flags = dis.readShort();\n        byte protocol = dis.readByte();\n        byte algorithm = dis.readByte();\n        byte[] key = new byte[length - 4];\n        dis.readFully(key);\n        return new DNSKEY(flags, protocol, algorithm, key);\n    }\n\n    private DNSKEY(short flags, byte protocol, SignatureAlgorithm algorithm, byte algorithmByte, byte[] key) {\n        this.flags = flags;\n        this.protocol = protocol;\n\n        assert algorithmByte == (algorithm != null ? algorithm.number : algorithmByte);\n        this.algorithmByte = algorithmByte;\n        this.algorithm = algorithm != null ? algorithm : SignatureAlgorithm.forByte(algorithmByte);\n\n        this.key = key;\n    }\n\n    public DNSKEY(short flags, byte protocol, byte algorithm, byte[] key) {\n        this(flags, protocol, SignatureAlgorithm.forByte(algorithm), algorithm, key);\n    }\n\n    public DNSKEY(short flags, byte protocol, SignatureAlgorithm algorithm, byte[] key) {\n        this(flags, protocol, algorithm, algorithm.number, key);\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.DNSKEY;\n    }\n\n    /**\n     * Retrieve the key tag identifying this DNSKEY.\n     * The key tag is used within the DS and RRSIG record to distinguish multiple keys for the same name.\n     *\n     * This implementation is based on the reference implementation shown in RFC 4034 Appendix B.\n     *\n     * @return this DNSKEY's key tag\n     */\n    public /* unsigned short */ int getKeyTag() {\n        if (keyTag == null) {\n            byte[] recordBytes = toByteArray();\n            long ac = 0;\n\n            for (int i = 0; i < recordBytes.length; ++i) {\n                ac += ((i & 1) > 0) ? recordBytes[i] & 0xFFL : ((recordBytes[i] & 0xFFL) << 8);\n            }\n            ac += (ac >> 16) & 0xFFFF;\n            keyTag = (int) (ac & 0xFFFF);\n        }\n        return keyTag;\n    }\n\n    @Override\n    public void serialize(DataOutputStream dos) throws IOException {\n        dos.writeShort(flags);\n        dos.writeByte(protocol);\n        dos.writeByte(algorithmByte);\n        dos.write(key);\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder sb = new StringBuilder()\n                .append(flags).append(' ')\n                .append(protocol).append(' ')\n                .append(algorithm).append(' ')\n                .append(Base64.encodeToString(key));\n        return sb.toString();\n    }\n\n    public int getKeyLength() {\n        return key.length;\n    }\n\n    public byte[] getKey() {\n        return key.clone();\n    }\n\n    public DataInputStream getKeyAsDataInputStream() {\n        return new DataInputStream(new ByteArrayInputStream(key));\n    }\n\n    private transient String keyBase64Cache;\n\n    public String getKeyBase64() {\n        if (keyBase64Cache == null) {\n            keyBase64Cache = Base64.encodeToString(key);\n        }\n        return keyBase64Cache;\n    }\n\n    private transient BigInteger keyBigIntegerCache;\n\n    public BigInteger getKeyBigInteger() {\n        if (keyBigIntegerCache == null) {\n            keyBigIntegerCache = new BigInteger(key);\n        }\n        return keyBigIntegerCache;\n    }\n\n    public boolean keyEquals(byte[] otherKey) {\n        return Arrays.equals(key, otherKey);\n    }\n\n    public boolean isSecureEntryPoint() {\n        return (flags & FLAG_SECURE_ENTRY_POINT) == 1;\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/DS.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport org.minidns.constants.DnssecConstants.DigestAlgorithm;\nimport org.minidns.constants.DnssecConstants.SignatureAlgorithm;\nimport org.minidns.record.Record.TYPE;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\n/**\n * DS (Delegation Signer) record payload.\n *\n * @see <a href=\"https://tools.ietf.org/html/rfc4034#section-5\">RFC 4034 § 5</a>\n */\npublic class DS extends DelegatingDnssecRR {\n\n    public static DS parse(DataInputStream dis, int length) throws IOException {\n        SharedData parsedData = DelegatingDnssecRR.parseSharedData(dis, length);\n        return new DS(parsedData.keyTag, parsedData.algorithm, parsedData.digestType, parsedData.digest);\n    }\n\n    public DS(int keyTag, byte algorithm, byte digestType, byte[] digest) {\n        super(keyTag, algorithm, digestType, digest);\n    }\n\n    public DS(int keyTag, SignatureAlgorithm algorithm, byte digestType, byte[] digest) {\n        super(keyTag, algorithm, digestType, digest);\n    }\n\n    public DS(int keyTag, SignatureAlgorithm algorithm, DigestAlgorithm digestType, byte[] digest) {\n        super(keyTag, algorithm, digestType, digest);\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.DS;\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/Data.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.Arrays;\n\nimport org.minidns.record.Record.TYPE;\n\n/**\n * Generic payload class.\n */\npublic abstract class Data {\n\n    /**\n     * The payload type.\n     * @return The payload type.\n     */\n    public abstract TYPE getType();\n\n    /**\n     * The internal method used to serialize Data subclasses.\n     *\n     * @param dos the output stream to serialize to.\n     * @throws IOException if an I/O error occurs.\n     */\n    protected abstract void serialize(DataOutputStream dos) throws IOException;\n\n    private byte[] bytes;\n\n    private void setBytes() {\n        if (bytes != null) return;\n\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        DataOutputStream dos = new DataOutputStream(baos);\n        try {\n            serialize(dos);\n        } catch (IOException e) {\n            // Should never happen.\n            throw new AssertionError(e);\n        }\n        bytes = baos.toByteArray();\n    }\n\n    public final int length() {\n        setBytes();\n        return bytes.length;\n    }\n\n    public final void toOutputStream(OutputStream outputStream) throws IOException {\n        DataOutputStream dataOutputStream = new DataOutputStream(outputStream);\n        toOutputStream(dataOutputStream);\n    }\n\n    /**\n     * Write the binary representation of this payload to the given {@link DataOutputStream}.\n     *\n     * @param dos the DataOutputStream to write to.\n     * @throws IOException if an I/O error occurs.\n     */\n    public final void toOutputStream(DataOutputStream dos) throws IOException {\n        setBytes();\n        dos.write(bytes);\n    }\n\n    public final byte[] toByteArray() {\n        setBytes();\n        return bytes.clone();\n    }\n\n    private transient Integer hashCodeCache;\n\n    @Override\n    public final int hashCode() {\n        if (hashCodeCache == null) {\n            setBytes();\n            hashCodeCache = Arrays.hashCode(bytes);\n        }\n        return hashCodeCache;\n    }\n\n    @Override\n    public final boolean equals(Object other) {\n        if (!(other instanceof Data)) {\n            return false;\n        }\n        if (other == this) {\n            return true;\n        }\n        Data otherData = (Data) other;\n        otherData.setBytes();\n        setBytes();\n\n        return Arrays.equals(bytes, otherData.bytes);\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/DelegatingDnssecRR.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.util.Arrays;\nimport java.util.Locale;\n\nimport org.minidns.constants.DnssecConstants.DigestAlgorithm;\nimport org.minidns.constants.DnssecConstants.SignatureAlgorithm;\n\n/**\n * DS (Delegation Signer) record payload.\n *\n * @see <a href=\"https://tools.ietf.org/html/rfc4034#section-5\">RFC 4034 § 5</a>\n */\npublic abstract class DelegatingDnssecRR extends Data {\n\n    /**\n     * The key tag value of the DNSKEY RR that validates this signature.\n     */\n    public final int /* unsigned short */ keyTag;\n\n    /**\n     * The cryptographic algorithm used to create the signature. If MiniDNS\n     * isn't aware of the signature algorithm, then this field will be\n     * <code>null</code>.\n     * \n     * @see #algorithmByte\n     */\n    public final SignatureAlgorithm algorithm;\n\n    /**\n     * The byte value of the cryptographic algorithm used to create the signature.\n     */\n    public final byte algorithmByte;\n\n    /**\n     * The algorithm used to construct the digest. If MiniDNS\n     * isn't aware of the digest algorithm, then this field will be\n     * <code>null</code>.\n     * \n     * @see #digestTypeByte\n     */\n    public final DigestAlgorithm digestType;\n\n    /**\n     * The byte value of algorithm used to construct the digest.\n     */\n    public final byte digestTypeByte;\n\n    /**\n     * The digest build from a DNSKEY.\n     */\n    protected final byte[] digest;\n\n    protected static SharedData parseSharedData(DataInputStream dis, int length) throws IOException {\n        int keyTag = dis.readUnsignedShort();\n        byte algorithm = dis.readByte();\n        byte digestType = dis.readByte();\n        byte[] digest = new byte[length - 4];\n        if (dis.read(digest) != digest.length) throw new IOException();\n        return new SharedData(keyTag, algorithm, digestType, digest);\n    }\n\n    protected static final class SharedData {\n        final int keyTag;\n        final byte algorithm;\n        final byte digestType;\n        final byte[] digest;\n\n        private SharedData(int keyTag, byte algorithm, byte digestType, byte[] digest) {\n            this.keyTag = keyTag;\n            this.algorithm = algorithm;\n            this.digestType = digestType;\n            this.digest = digest;\n        }\n    }\n\n    protected DelegatingDnssecRR(int keyTag, SignatureAlgorithm algorithm, byte algorithmByte, DigestAlgorithm digestType, byte digestTypeByte, byte[] digest) {\n        this.keyTag = keyTag;\n\n        assert algorithmByte == (algorithm != null ? algorithm.number : algorithmByte);\n        this.algorithmByte = algorithmByte;\n        this.algorithm = algorithm != null ? algorithm : SignatureAlgorithm.forByte(algorithmByte);\n\n        assert digestTypeByte == (digestType != null ? digestType.value : digestTypeByte);\n        this.digestTypeByte = digestTypeByte;\n        this.digestType = digestType != null ? digestType : DigestAlgorithm.forByte(digestTypeByte);\n\n        assert digest != null;\n        this.digest = digest;\n    }\n\n    protected DelegatingDnssecRR(int keyTag, byte algorithm, byte digestType, byte[] digest) {\n        this(keyTag, null, algorithm, null, digestType, digest);\n    }\n\n    protected DelegatingDnssecRR(int keyTag, SignatureAlgorithm algorithm, DigestAlgorithm digestType, byte[] digest) {\n        this(keyTag, algorithm, algorithm.number, digestType, digestType.value, digest);\n    }\n\n    protected DelegatingDnssecRR(int keyTag, SignatureAlgorithm algorithm, byte digestType, byte[] digest) {\n        this(keyTag, algorithm, algorithm.number, null, digestType, digest);\n    }\n\n    @Override\n    public void serialize(DataOutputStream dos) throws IOException {\n        dos.writeShort(keyTag);\n        dos.writeByte(algorithmByte);\n        dos.writeByte(digestTypeByte);\n        dos.write(digest);\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder sb = new StringBuilder()\n                .append(keyTag).append(' ')\n                .append(algorithm).append(' ')\n                .append(digestType).append(' ')\n                .append(new BigInteger(1, digest).toString(16).toUpperCase(Locale.ROOT));\n        return sb.toString();\n    }\n\n    private transient BigInteger digestBigIntCache;\n\n    public BigInteger getDigestBigInteger() {\n        if (digestBigIntCache == null) {\n            digestBigIntCache = new BigInteger(1, digest);\n        }\n        return digestBigIntCache;\n    }\n\n    private transient String digestHexCache;\n\n    public String getDigestHex() {\n        if (digestHexCache == null) {\n            digestHexCache = getDigestBigInteger().toString(16).toUpperCase(Locale.ROOT);\n        }\n        return digestHexCache;\n    }\n\n    public boolean digestEquals(byte[] otherDigest) {\n        return Arrays.equals(digest, otherDigest);\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/InternetAddressRR.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.net.Inet4Address;\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\n\n/**\n * A resource record representing a internet address. Provides {@link #getInetAddress()}.\n */\npublic abstract class InternetAddressRR<IA extends InetAddress> extends Data {\n\n\n    /**\n     * Target IP.\n     */\n    protected final byte[] ip;\n\n    /**\n     * Cache for the {@link InetAddress} this record presents.\n     */\n    private transient IA inetAddress;\n\n    protected InternetAddressRR(byte[] ip) {\n        this.ip = ip;\n    }\n\n    protected InternetAddressRR(IA inetAddress) {\n        this(inetAddress.getAddress());\n        this.inetAddress = inetAddress;\n    }\n\n    @Override\n    public final void serialize(DataOutputStream dos) throws IOException {\n        dos.write(ip);\n    }\n\n    /**\n     * Allocates a new byte buffer and fills the buffer with the bytes representing the IP address of this resource record.\n     *\n     * @return a new byte buffer containing the bytes of the IP.\n     */\n    public final byte[] getIp() {\n        return ip.clone();\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public final IA getInetAddress() {\n        if (inetAddress == null) {\n            try {\n                inetAddress = (IA) InetAddress.getByAddress(ip);\n            } catch (UnknownHostException e) {\n                throw new IllegalStateException(e);\n            }\n        }\n        return inetAddress;\n    }\n\n    public static InternetAddressRR<? extends InetAddress> from(InetAddress inetAddress) {\n        if (inetAddress instanceof Inet4Address) {\n            return new A((Inet4Address) inetAddress);\n        }\n\n        return new AAAA((Inet6Address) inetAddress);\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/MX.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\n\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.Record.TYPE;\n\n/**\n * MX record payload (mail service pointer).\n */\npublic class MX extends Data {\n\n    /**\n     * The priority of this service. Lower values mean higher priority.\n     */\n    public final int priority;\n\n    /**\n     * The name of the target server.\n     */\n    public final DnsName target;\n\n    /**\n     * The name of the target server.\n     *\n     * @deprecated use {@link #target} instead.\n     */\n    @Deprecated\n    public final DnsName name;\n\n    public static MX parse(DataInputStream dis, byte[] data)\n        throws IOException {\n        int priority = dis.readUnsignedShort();\n        DnsName name = DnsName.parse(dis, data);\n        return new MX(priority, name);\n    }\n\n    public MX(int priority, String name) {\n        this(priority, DnsName.from(name));\n    }\n\n    public MX(int priority, DnsName name) {\n        this.priority = priority;\n        this.target = name;\n        this.name = target;\n    }\n\n    @Override\n    public void serialize(DataOutputStream dos) throws IOException {\n        dos.writeShort(priority);\n        target.writeToStream(dos);\n    }\n\n    @Override\n    public String toString() {\n        return priority + \" \" + target + '.';\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.MX;\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/NS.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\n\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.Record.TYPE;\n\n/**\n * Nameserver record.\n */\npublic class NS extends RRWithTarget {\n\n    public static NS parse(DataInputStream dis, byte[] data) throws IOException {\n        DnsName target = DnsName.parse(dis, data);\n        return new NS(target);\n    }\n\n    public NS(DnsName name) {\n        super(name);\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.NS;\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/NSEC.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.Record.TYPE;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.logging.Logger;\n\n/**\n * NSEC record payload.\n */\npublic class NSEC extends Data {\n\n    private static final Logger LOGGER = Logger.getLogger(NSEC.class.getName());\n\n    /**\n     * The next owner name that contains a authoritative data or a delegation point.\n     */\n    public final DnsName next;\n\n    private final byte[] typeBitmap;\n\n    /**\n     * The RR types existing at the owner name.\n     */\n    public final List<TYPE> types;\n\n    public static NSEC parse(DataInputStream dis, byte[] data, int length) throws IOException {\n        DnsName next = DnsName.parse(dis, data);\n\n        byte[] typeBitmap = new byte[length - next.size()];\n        if (dis.read(typeBitmap) != typeBitmap.length) throw new IOException();\n        List<TYPE> types = readTypeBitMap(typeBitmap);\n        return new NSEC(next, types);\n    }\n\n    public NSEC(String next, List<TYPE> types) {\n        this(DnsName.from(next), types);\n    }\n\n    public NSEC(String next, TYPE... types) {\n        this(DnsName.from(next), Arrays.asList(types));\n    }\n\n    public NSEC(DnsName next, List<TYPE> types) {\n        this.next = next;\n        this.types = Collections.unmodifiableList(types);\n        this.typeBitmap = createTypeBitMap(types);\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.NSEC;\n    }\n\n    @Override\n    public void serialize(DataOutputStream dos) throws IOException {\n        next.writeToStream(dos);\n        dos.write(typeBitmap);\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder sb = new StringBuilder()\n                .append(next).append('.');\n        for (TYPE type : types) {\n            sb.append(' ').append(type);\n        }\n        return sb.toString();\n    }\n\n    @SuppressWarnings(\"NarrowingCompoundAssignment\")\n    static byte[] createTypeBitMap(List<TYPE> types) {\n        List<Integer> typeList = new ArrayList<Integer>(types.size());\n        for (TYPE type : types) {\n            typeList.add(type.getValue());\n        }\n        Collections.sort(typeList);\n\n        ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        DataOutputStream dos = new DataOutputStream(baos);\n\n        try {\n            int windowBlock = -1;\n            byte[] bitmap = null;\n            for (Integer type : typeList) {\n                if (windowBlock == -1 || (type >> 8) != windowBlock) {\n                    if (windowBlock != -1) writeOutBlock(bitmap, dos);\n                    windowBlock = type >> 8;\n                    dos.writeByte(windowBlock);\n                    bitmap = new byte[32];\n                }\n                int a = (type >> 3) % 32;\n                int b = type % 8;\n                bitmap[a] |= (byte) (128 >> b);\n            }\n            if (windowBlock != -1) writeOutBlock(bitmap, dos);\n        } catch (IOException e) {\n            // Should never happen.\n            throw new RuntimeException(e);\n        }\n\n        return baos.toByteArray();\n    }\n\n    private static void writeOutBlock(byte[] values, DataOutputStream dos) throws IOException {\n        int n = 0;\n        for (int i = 0; i < values.length; i++) {\n            if (values[i] != 0) n = i + 1;\n        }\n        dos.writeByte(n);\n        for (int i = 0; i < n; i++) {\n            dos.writeByte(values[i]);\n        }\n    }\n\n    // TODO: This method should probably just return List<Integer> so that unknown types can be act on later.\n    static List<TYPE> readTypeBitMap(byte[] typeBitmap) throws IOException {\n        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(typeBitmap));\n        int read = 0;\n        ArrayList<TYPE> typeList = new ArrayList<TYPE>();\n        while (typeBitmap.length > read) {\n            int windowBlock = dis.readUnsignedByte();\n            int bitmapLength = dis.readUnsignedByte();\n            for (int i = 0; i < bitmapLength; i++) {\n                int b = dis.readUnsignedByte();\n                for (int j = 0; j < 8; j++) {\n                    if (((b >> j) & 0x1) > 0) {\n                        int typeInt = (windowBlock << 8) + (i * 8) + (7 - j);\n                        TYPE type = TYPE.getType(typeInt);\n                        if (type == TYPE.UNKNOWN) {\n                            LOGGER.warning(\"Skipping unknown type in type bitmap: \" + typeInt);\n                            continue;\n                        }\n                        typeList.add(type);\n                    }\n                }\n            }\n            read += bitmapLength + 2;\n        }\n        return typeList;\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/NSEC3.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport org.minidns.dnslabel.DnsLabel;\nimport org.minidns.record.Record.TYPE;\nimport org.minidns.util.Base32;\n\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\n\n/**\n * NSEC3 record payload.\n */\npublic class NSEC3 extends Data {\n\n    /**\n     * This Flag indicates whether this NSEC3 RR may cover unsigned\n     * delegations.\n     */\n    public static final byte FLAG_OPT_OUT = 0x1;\n\n    private static final Map<Byte, HashAlgorithm> HASH_ALGORITHM_LUT = new HashMap<>();\n\n    /**\n     * DNSSEC NSEC3 Hash Algorithms.\n     *\n     * @see <a href=\n     *      \"https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml#dnssec-nsec3-parameters-3\">\n     *      IANA DNSSEC NSEC3 Hash Algorithms</a>\n     */\n    public enum HashAlgorithm {\n        RESERVED(0, \"Reserved\"),\n        SHA1(1, \"SHA-1\"),\n        ;\n\n        HashAlgorithm(int value, String description) {\n            if (value < 0 || value > 255) {\n                throw new IllegalArgumentException();\n            }\n            this.value = (byte) value;\n            this.description = description;\n            HASH_ALGORITHM_LUT.put(this.value, this);\n        }\n\n        public final byte value;\n        public final String description;\n\n        public static HashAlgorithm forByte(byte b) {\n            return HASH_ALGORITHM_LUT.get(b);\n        }\n    }\n\n    /**\n     * The cryptographic hash algorithm used. If MiniDNS\n     * isn't aware of the hash algorithm, then this field will be\n     * <code>null</code>.\n     * \n     * @see #hashAlgorithmByte\n     */\n    public final HashAlgorithm hashAlgorithm;\n\n    /**\n     * The byte value of the cryptographic hash algorithm used.\n     */\n    public final byte hashAlgorithmByte;\n\n    /**\n     * Bitmap of flags: {@link #FLAG_OPT_OUT}.\n     */\n    public final byte flags;\n\n    /**\n     * The number of iterations the hash algorithm is applied.\n     */\n    public final int /* unsigned short */ iterations;\n\n    /**\n     * The salt appended to the next owner name before hashing.\n     */\n    private final byte[] salt;\n\n    /**\n     * The next hashed owner name in hash order.\n     */\n    private final byte[] nextHashed;\n\n    private final byte[] typeBitmap;\n\n    /**\n     * The RR types existing at the original owner name.\n     */\n    public final List<TYPE> types;\n\n    public static NSEC3 parse(DataInputStream dis, int length) throws IOException {\n        byte hashAlgorithm = dis.readByte();\n        byte flags = dis.readByte();\n        int iterations = dis.readUnsignedShort();\n        int saltLength = dis.readUnsignedByte();\n        byte[] salt = new byte[saltLength];\n        if (dis.read(salt) != salt.length) throw new IOException();\n        int hashLength = dis.readUnsignedByte();\n        byte[] nextHashed = new byte[hashLength];\n        if (dis.read(nextHashed) != nextHashed.length) throw new IOException();\n        byte[] typeBitmap = new byte[length - (6 + saltLength + hashLength)];\n        if (dis.read(typeBitmap) != typeBitmap.length) throw new IOException();\n        List<TYPE> types = NSEC.readTypeBitMap(typeBitmap);\n        return new NSEC3(hashAlgorithm, flags, iterations, salt, nextHashed, types);\n    }\n\n    private NSEC3(HashAlgorithm hashAlgorithm, byte hashAlgorithmByte, byte flags, int iterations, byte[] salt, byte[] nextHashed, List<TYPE> types) {\n        assert hashAlgorithmByte == (hashAlgorithm != null ? hashAlgorithm.value : hashAlgorithmByte);\n        this.hashAlgorithmByte = hashAlgorithmByte;\n        this.hashAlgorithm = hashAlgorithm != null ? hashAlgorithm : HashAlgorithm.forByte(hashAlgorithmByte);\n\n        this.flags = flags;\n        this.iterations = iterations;\n        this.salt = salt;\n        this.nextHashed = nextHashed;\n        this.types = types;\n        this.typeBitmap = NSEC.createTypeBitMap(types);\n    }\n\n    public NSEC3(byte hashAlgorithm, byte flags, int iterations, byte[] salt, byte[] nextHashed, List<TYPE> types) {\n        this(null, hashAlgorithm, flags, iterations, salt, nextHashed, types);\n    }\n\n    public NSEC3(byte hashAlgorithm, byte flags, int iterations, byte[] salt, byte[] nextHashed, TYPE... types) {\n        this(null, hashAlgorithm, flags, iterations, salt, nextHashed, Arrays.asList(types));\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.NSEC3;\n    }\n\n    @Override\n    public void serialize(DataOutputStream dos) throws IOException {\n        dos.writeByte(hashAlgorithmByte);\n        dos.writeByte(flags);\n        dos.writeShort(iterations);\n        dos.writeByte(salt.length);\n        dos.write(salt);\n        dos.writeByte(nextHashed.length);\n        dos.write(nextHashed);\n        dos.write(typeBitmap);\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder sb = new StringBuilder()\n                .append(hashAlgorithm).append(' ')\n                .append(flags).append(' ')\n                .append(iterations).append(' ')\n                .append(salt.length == 0 ? \"-\" : new BigInteger(1, salt).toString(16).toUpperCase(Locale.ROOT)).append(' ')\n                .append(Base32.encodeToString(nextHashed));\n        for (TYPE type : types) {\n            sb.append(' ').append(type);\n        }\n        return sb.toString();\n    }\n\n    public byte[] getSalt() {\n        return salt.clone();\n    }\n\n    public int getSaltLength() {\n        return salt.length;\n    }\n\n    public byte[] getNextHashed() {\n        return nextHashed.clone();\n    }\n\n    private String nextHashedBase32Cache;\n\n    public String getNextHashedBase32() {\n        if (nextHashedBase32Cache == null) {\n            nextHashedBase32Cache = Base32.encodeToString(nextHashed);\n        }\n        return nextHashedBase32Cache;\n    }\n\n    private DnsLabel nextHashedDnsLabelCache;\n\n    public DnsLabel getNextHashedDnsLabel() {\n        if (nextHashedDnsLabelCache == null) {\n            String nextHashedBase32 = getNextHashedBase32();\n            nextHashedDnsLabelCache = DnsLabel.from(nextHashedBase32);\n        }\n        return nextHashedDnsLabelCache;\n    }\n\n    public void copySaltInto(byte[] dest, int destPos) {\n        System.arraycopy(salt, 0, dest, destPos, salt.length);\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/NSEC3PARAM.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport org.minidns.record.NSEC3.HashAlgorithm;\nimport org.minidns.record.Record.TYPE;\n\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.util.Locale;\n\n/**\n * NSEC3PARAM record payload.\n */\npublic class NSEC3PARAM extends Data {\n\n    /**\n     * The cryptographic hash algorithm used.\n     * \n     */\n    public final HashAlgorithm hashAlgorithm;\n\n    /**\n     * The cryptographic hash algorithm used.\n     * \n     */\n    public final byte hashAlgorithmByte;\n\n    public final byte flags;\n\n    /**\n     * The number of iterations the hash algorithm is applied.\n     */\n    public final int /* unsigned short */ iterations;\n\n    /**\n     * The salt appended to the next owner name before hashing.\n     */\n    private final byte[] salt;\n\n    public static NSEC3PARAM parse(DataInputStream dis) throws IOException {\n        byte hashAlgorithm = dis.readByte();\n        byte flags = dis.readByte();\n        int iterations = dis.readUnsignedShort();\n        int saltLength = dis.readUnsignedByte();\n        byte[] salt = new byte[saltLength];\n        if (dis.read(salt) != salt.length && salt.length != 0) throw new IOException();\n        return new NSEC3PARAM(hashAlgorithm, flags, iterations, salt);\n    }\n\n    private NSEC3PARAM(HashAlgorithm hashAlgorithm, byte hashAlgorithmByte, byte flags, int iterations, byte[] salt) {\n        assert hashAlgorithmByte == (hashAlgorithm != null ? hashAlgorithm.value : hashAlgorithmByte);\n        this.hashAlgorithmByte = hashAlgorithmByte;\n        this.hashAlgorithm = hashAlgorithm != null ? hashAlgorithm : HashAlgorithm.forByte(hashAlgorithmByte);\n\n        this.flags = flags;\n        this.iterations = iterations;\n        this.salt = salt;\n    }\n\n    NSEC3PARAM(byte hashAlgorithm, byte flags, int iterations, byte[] salt) {\n        this(null, hashAlgorithm, flags, iterations, salt);\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.NSEC3PARAM;\n    }\n\n    @Override\n    public void serialize(DataOutputStream dos) throws IOException {\n        dos.writeByte(hashAlgorithmByte);\n        dos.writeByte(flags);\n        dos.writeShort(iterations);\n        dos.writeByte(salt.length);\n        dos.write(salt);\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder sb = new StringBuilder()\n                .append(hashAlgorithm).append(' ')\n                .append(flags).append(' ')\n                .append(iterations).append(' ')\n                .append(salt.length == 0 ? \"-\" : new BigInteger(1, salt).toString(16).toUpperCase(Locale.ROOT));\n        return sb.toString();\n    }\n\n    public int getSaltLength() {\n        return salt.length;\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/OPENPGPKEY.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport org.minidns.util.Base64;\n\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\n\npublic class OPENPGPKEY extends Data {\n\n    private final byte[] publicKeyPacket;\n\n    public static OPENPGPKEY parse(DataInputStream dis, int length) throws IOException {\n        byte[] publicKeyPacket = new byte[length];\n        dis.readFully(publicKeyPacket);\n        return new OPENPGPKEY(publicKeyPacket);\n    }\n\n    OPENPGPKEY(byte[] publicKeyPacket) {\n        this.publicKeyPacket = publicKeyPacket;\n    }\n\n    @Override\n    public Record.TYPE getType() {\n        return Record.TYPE.OPENPGPKEY;\n    }\n\n    @Override\n    public void serialize(DataOutputStream dos) throws IOException {\n        dos.write(publicKeyPacket);\n    }\n\n    @Override\n    public String toString() {\n        return getPublicKeyPacketBase64();\n    }\n\n    private transient String publicKeyPacketBase64Cache;\n\n    public String getPublicKeyPacketBase64() {\n        if (publicKeyPacketBase64Cache == null) {\n            publicKeyPacketBase64Cache = Base64.encodeToString(publicKeyPacket);\n        }\n        return publicKeyPacketBase64Cache;\n    }\n\n    public byte[] getPublicKeyPacket() {\n        return publicKeyPacket.clone();\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/OPT.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport org.minidns.edns.EdnsOption;\nimport org.minidns.record.Record.TYPE;\n\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * OPT payload (see RFC 2671 for details).\n */\npublic class OPT extends Data {\n\n    public final List<EdnsOption> variablePart;\n\n    public OPT() {\n        this(Collections.<EdnsOption>emptyList());\n    }\n\n    public OPT(List<EdnsOption> variablePart) {\n        this.variablePart = Collections.unmodifiableList(variablePart);\n    }\n\n    public static OPT parse(DataInputStream dis, int payloadLength) throws IOException {\n        List<EdnsOption> variablePart;\n        if (payloadLength == 0) {\n            variablePart = Collections.emptyList();\n        } else {\n            int payloadLeft = payloadLength;\n            variablePart = new ArrayList<>(4);\n            while (payloadLeft > 0) {\n                int optionCode = dis.readUnsignedShort();\n                int optionLength = dis.readUnsignedShort();\n                byte[] optionData = new byte[optionLength];\n                dis.read(optionData);\n                EdnsOption ednsOption = EdnsOption.parse(optionCode, optionData);\n                variablePart.add(ednsOption);\n                payloadLeft -= 2 + 2 + optionLength;\n                // Assert that payloadLeft never becomes negative\n                assert payloadLeft >= 0;\n            }\n        }\n        return new OPT(variablePart);\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.OPT;\n    }\n\n    @Override\n    protected void serialize(DataOutputStream dos) throws IOException {\n        for (EdnsOption endsOption : variablePart) {\n            endsOption.writeToDos(dos);\n        }\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/PTR.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\n\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.Record.TYPE;\n\n/**\n * A PTR record is handled like a CNAME.\n */\npublic class PTR extends RRWithTarget {\n\n    public static PTR parse(DataInputStream dis, byte[] data) throws IOException {\n        DnsName target = DnsName.parse(dis, data);\n        return new PTR(target);\n    }\n\n    PTR(String name) {\n        this(DnsName.from(name));\n    }\n\n    PTR(DnsName name) {\n        super(name);\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.PTR;\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/RRSIG.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport org.minidns.constants.DnssecConstants.SignatureAlgorithm;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.Record.TYPE;\nimport org.minidns.util.Base64;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\nimport java.util.TimeZone;\n\n/**\n * RRSIG record payload.\n */\npublic class RRSIG extends Data {\n\n    /**\n     * The type of RRset covered by this signature.\n     */\n    public final TYPE typeCovered;\n\n    /**\n     * The cryptographic algorithm used to create the signature.\n     */\n    public final SignatureAlgorithm algorithm;\n\n    /**\n     * The cryptographic algorithm used to create the signature.\n     */\n    public final byte algorithmByte;\n\n    /**\n     * The number of labels in the original RRSIG RR owner name.\n     */\n    public final byte labels;\n\n    /**\n     * The TTL of the covered RRset.\n     */\n    public final long /* unsigned int */ originalTtl;\n\n    /**\n     * The date and time this RRSIG records expires.\n     */\n    public final Date signatureExpiration;\n\n    /**\n     * The date and time this RRSIG records starts to be valid.\n     */\n    public final Date signatureInception;\n\n    /**\n     * The key tag value of the DNSKEY RR that validates this signature.\n     */\n    public final int /* unsigned short */  keyTag;\n\n    /**\n     * The owner name of the DNSKEY RR that a validator is supposed to use.\n     */\n    public final DnsName signerName;\n\n    /**\n     * Signature that covers RRSIG RDATA (excluding the signature field) and RRset data.\n     */\n    private final byte[] signature;\n\n    @SuppressWarnings(\"JavaUtilDate\")\n    public static RRSIG parse(DataInputStream dis, byte[] data, int length) throws IOException {\n        TYPE typeCovered = TYPE.getType(dis.readUnsignedShort());\n        byte algorithm = dis.readByte();\n        byte labels = dis.readByte();\n        long originalTtl = dis.readInt() & 0xFFFFFFFFL;\n        Date signatureExpiration = new Date((dis.readInt() & 0xFFFFFFFFL) * 1000);\n        Date signatureInception = new Date((dis.readInt() & 0xFFFFFFFFL) * 1000);\n        int keyTag = dis.readUnsignedShort();\n        DnsName signerName = DnsName.parse(dis, data);\n        int sigSize = length - signerName.size() - 18;\n        byte[] signature = new byte[sigSize];\n        if (dis.read(signature) != signature.length) throw new IOException();\n        return new RRSIG(typeCovered, null, algorithm, labels, originalTtl, signatureExpiration, signatureInception, keyTag, signerName,\n                signature);\n    }\n\n    private  RRSIG(TYPE typeCovered, SignatureAlgorithm algorithm, byte algorithmByte, byte labels, long originalTtl, Date signatureExpiration, \n            Date signatureInception, int keyTag, DnsName signerName, byte[] signature) {\n        this.typeCovered = typeCovered;\n\n        assert algorithmByte == (algorithm != null ? algorithm.number : algorithmByte);\n        this.algorithmByte = algorithmByte;\n        this.algorithm = algorithm != null ? algorithm : SignatureAlgorithm.forByte(algorithmByte);\n\n        this.labels = labels;\n        this.originalTtl = originalTtl;\n        this.signatureExpiration = signatureExpiration;\n        this.signatureInception = signatureInception;\n        this.keyTag = keyTag;\n        this.signerName = signerName;\n        this.signature = signature;\n    }\n\n    public RRSIG(TYPE typeCovered, int algorithm, byte labels, long originalTtl, Date signatureExpiration, \n            Date signatureInception, int keyTag, DnsName signerName, byte[] signature) {\n            this(typeCovered, null, (byte) algorithm, labels, originalTtl, signatureExpiration, signatureInception, keyTag, signerName, signature);\n    }\n\n    public RRSIG(TYPE typeCovered, int algorithm, byte labels, long originalTtl, Date signatureExpiration, \n            Date signatureInception, int keyTag, String signerName, byte[] signature) {\n            this(typeCovered, null, (byte) algorithm, labels, originalTtl, signatureExpiration, signatureInception, keyTag, DnsName.from(signerName), signature);\n    }\n\n    public RRSIG(TYPE typeCovered, SignatureAlgorithm algorithm, byte labels,\n            long originalTtl, Date signatureExpiration, Date signatureInception,\n            int keyTag, DnsName signerName, byte[] signature) {\n        this(typeCovered, algorithm.number, labels, originalTtl, signatureExpiration, signatureInception,\n                keyTag, signerName, signature);\n    }\n\n    public RRSIG(TYPE typeCovered, SignatureAlgorithm algorithm, byte labels,\n            long originalTtl, Date signatureExpiration, Date signatureInception,\n            int keyTag, String signerName, byte[] signature) {\n        this(typeCovered, algorithm.number, labels, originalTtl, signatureExpiration, signatureInception,\n                keyTag, DnsName.from(signerName), signature);\n    }\n\n    public byte[] getSignature() {\n        return signature.clone();\n    }\n\n    public DataInputStream getSignatureAsDataInputStream() {\n        return new DataInputStream(new ByteArrayInputStream(signature));\n    }\n\n    public int getSignatureLength() {\n        return signature.length;\n    }\n\n    private transient String base64SignatureCache;\n\n    public String getSignatureBase64() {\n        if (base64SignatureCache == null) {\n            base64SignatureCache = Base64.encodeToString(signature);\n        }\n        return base64SignatureCache;\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.RRSIG;\n    }\n\n    @Override\n    public void serialize(DataOutputStream dos) throws IOException {\n        writePartialSignature(dos);\n        dos.write(signature);\n    }\n\n    @SuppressWarnings(\"JavaUtilDate\")\n    public void writePartialSignature(DataOutputStream dos) throws IOException {\n        dos.writeShort(typeCovered.getValue());\n        dos.writeByte(algorithmByte);\n        dos.writeByte(labels);\n        dos.writeInt((int) originalTtl);\n        dos.writeInt((int) (signatureExpiration.getTime() / 1000));\n        dos.writeInt((int) (signatureInception.getTime() / 1000));\n        dos.writeShort(keyTag);\n        signerName.writeToStream(dos);\n    }\n\n    @Override\n    public String toString() {\n        SimpleDateFormat dateFormat = new SimpleDateFormat(\"yyyyMMddHHmmss\");\n        dateFormat.setTimeZone(TimeZone.getTimeZone(\"UTC\"));\n        StringBuilder sb = new StringBuilder()\n                .append(typeCovered).append(' ')\n                .append(algorithm).append(' ')\n                .append(labels).append(' ')\n                .append(originalTtl).append(' ')\n                .append(dateFormat.format(signatureExpiration)).append(' ')\n                .append(dateFormat.format(signatureInception)).append(' ')\n                .append(keyTag).append(' ')\n                .append(signerName).append(\". \")\n                .append(getSignatureBase64());\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/RRWithTarget.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.DataOutputStream;\nimport java.io.IOException;\n\nimport org.minidns.dnsname.DnsName;\n\n/**\n * A resource record pointing to a target.\n */\npublic abstract class RRWithTarget extends Data {\n\n    public final DnsName target;\n\n    /**\n     * The target of this resource record.\n     * @deprecated {@link #target} instead.\n     */\n    @Deprecated\n    public final DnsName name;\n\n    @Override\n    public void serialize(DataOutputStream dos) throws IOException {\n        target.writeToStream(dos);\n    }\n\n    protected RRWithTarget(DnsName target) {\n        this.target = target;\n        this.name = target;\n    }\n\n    @Override\n    public String toString() {\n        return target + \".\";\n    }\n\n    public final DnsName getTarget() {\n        return target;\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/Record.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsname.DnsName;\n\n/**\n * A generic DNS record.\n */\npublic final class Record<D extends Data> {\n\n    /**\n     * The resource record type.\n     * \n     * @see <a href=\n     *      \"http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4\">\n     *      IANA DNS Parameters - Resource Record (RR) TYPEs</a>\n     */\n    public enum TYPE {\n        UNKNOWN(-1),\n        A(1, A.class),\n        NS(2, NS.class),\n        MD(3),\n        MF(4),\n        CNAME(5, CNAME.class),\n        SOA(6, SOA.class),\n        MB(7),\n        MG(8),\n        MR(9),\n        NULL(10),\n        WKS(11),\n        PTR(12, PTR.class),\n        HINFO(13),\n        MINFO(14),\n        MX(15, MX.class),\n        TXT(16, TXT.class),\n        RP(17),\n        AFSDB(18),\n        X25(19),\n        ISDN(20),\n        RT(21),\n        NSAP(22),\n        NSAP_PTR(23),\n        SIG(24),\n        KEY(25),\n        PX(26),\n        GPOS(27),\n        AAAA(28, AAAA.class),\n        LOC(29),\n        NXT(30),\n        EID(31),\n        NIMLOC(32),\n        SRV(33, SRV.class),\n        ATMA(34),\n        NAPTR(35),\n        KX(36),\n        CERT(37),\n        A6(38),\n        DNAME(39, DNAME.class),\n        SINK(40),\n        OPT(41, OPT.class),\n        APL(42),\n        DS(43, DS.class),\n        SSHFP(44),\n        IPSECKEY(45),\n        RRSIG(46, RRSIG.class),\n        NSEC(47, NSEC.class),\n        DNSKEY(48, DNSKEY.class),\n        DHCID(49),\n        NSEC3(50, NSEC3.class),\n        NSEC3PARAM(51, NSEC3PARAM.class),\n        TLSA(52, TLSA.class),\n        HIP(55),\n        NINFO(56),\n        RKEY(57),\n        TALINK(58),\n        CDS(59),\n        CDNSKEY(60),\n        OPENPGPKEY(61, OPENPGPKEY.class),\n        CSYNC(62),\n        SPF(99),\n        UINFO(100),\n        UID(101),\n        GID(102),\n        UNSPEC(103),\n        NID(104),\n        L32(105),\n        L64(106),\n        LP(107),\n        EUI48(108),\n        EUI64(109),\n        TKEY(249),\n        TSIG(250),\n        IXFR(251),\n        AXFR(252),\n        MAILB(253),\n        MAILA(254),\n        ANY(255),\n        URI(256),\n        CAA(257),\n        TA(32768),\n        DLV(32769, DLV.class),\n        ;\n\n        /**\n         * The value of this DNS record type.\n         */\n        private final int value;\n\n        private final Class<?> dataClass;\n\n        /**\n         * Internal lookup table to map values to types.\n         */\n        private static final Map<Integer, TYPE> INVERSE_LUT = new HashMap<>();\n\n        private static final Map<Class<?>, TYPE> DATA_LUT = new HashMap<>();\n\n        static {\n            // Initialize the reverse lookup table.\n            for (TYPE t : TYPE.values()) {\n                INVERSE_LUT.put(t.getValue(), t);\n                if (t.dataClass != null) {\n                    DATA_LUT.put(t.dataClass, t);\n                }\n            }\n        }\n\n        /**\n         * Create a new record type.\n         * \n         * @param value The binary value of this type.\n         */\n        TYPE(int value) {\n            this(value, null);\n        }\n\n        /**\n         * Create a new record type.\n         *\n         * @param <D> The class for this type.\n         * @param dataClass The class for this type.\n         * @param value The binary value of this type.\n         */\n        <D extends Data> TYPE(int value, Class<D> dataClass) {\n            this.value = value;\n            this.dataClass = dataClass;\n        }\n\n        /**\n         * Retrieve the binary value of this type.\n         * @return The binary value.\n         */\n        public int getValue() {\n            return value;\n        }\n\n        /**\n         * Get the {@link Data} class for this type.\n         *\n         * @param <D> The class for this type.\n         * @return the {@link Data} class for this type.\n         */\n        @SuppressWarnings(\"unchecked\")\n        public <D extends Data> Class<D> getDataClass() {\n            return (Class<D>) dataClass;\n        }\n\n        /**\n         * Retrieve the symbolic type of the binary value.\n         * @param value The binary type value.\n         * @return The symbolic tpye.\n         */\n        public static TYPE getType(int value) {\n            TYPE type = INVERSE_LUT.get(value);\n            if (type == null) return UNKNOWN;\n            return type;\n        }\n\n        /**\n         * Retrieve the type for a given {@link Data} class.\n         *\n         * @param <D> The class for this type.\n         * @param dataClass the class to lookup the type for.\n         * @return the type for the given data class.\n         */\n        public static <D extends Data> TYPE getType(Class<D> dataClass) {\n            return DATA_LUT.get(dataClass);\n        }\n    }\n\n    /**\n     * The symbolic class of a DNS record (usually {@link CLASS#IN} for Internet).\n     *\n     * @see <a href=\"http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2\">IANA Domain Name System (DNS) Parameters - DNS CLASSes</a>\n     */\n    public enum CLASS {\n\n        /**\n         * The Internet class. This is the most common class used by todays DNS systems.\n         */\n        IN(1),\n\n        /**\n         * The Chaos class.\n         */\n        CH(3),\n\n        /**\n         * The Hesiod class.\n         */\n        HS(4),\n        NONE(254),\n        ANY(255);\n\n        /**\n         * Internal reverse lookup table to map binary class values to symbolic\n         * names.\n         */\n        private static final HashMap<Integer, CLASS> INVERSE_LUT =\n                                            new HashMap<Integer, CLASS>();\n\n        static {\n            // Initialize the interal reverse lookup table.\n            for (CLASS c : CLASS.values()) {\n                INVERSE_LUT.put(c.getValue(), c);\n            }\n        }\n\n        /**\n         * The binary value of this dns class.\n         */\n        private final int value;\n\n        /**\n         * Create a new DNS class based on a binary value.\n         * @param value The binary value of this DNS class.\n         */\n        CLASS(int value) {\n            this.value = value;\n        }\n\n        /**\n         * Retrieve the binary value of this DNS class.\n         * @return The binary value of this DNS class.\n         */\n        public int getValue() {\n            return value;\n        }\n\n        /**\n         * Retrieve the symbolic DNS class for a binary class value.\n         * @param value The binary DNS class value.\n         * @return The symbolic class instance.\n         */\n        public static CLASS getClass(int value) {\n            return INVERSE_LUT.get(value);\n        }\n\n    }\n\n    /**\n     * The generic name of this record.\n     */\n    public final DnsName name;\n\n    /**\n     * The type (and payload type) of this record.\n     */\n    public final TYPE type;\n\n    /**\n     * The record class (usually CLASS.IN).\n     */\n    public final CLASS clazz;\n\n    /**\n     * The value of the class field of a RR.\n     * \n     * According to RFC 2671 (OPT RR) this is not necessarily representable\n     * using clazz field and unicastQuery bit\n     */\n    public final int clazzValue;\n\n    /**\n     * The ttl of this record.\n     */\n    public final long ttl;\n\n    /**\n     * The payload object of this record.\n     */\n    public final D payloadData;\n\n    /**\n     * MDNS defines the highest bit of the class as the unicast query bit.\n     */\n    public final boolean unicastQuery;\n\n    /**\n     * Parse a given record based on the full message data and the current\n     * stream position.\n     *\n     * @param dis The DataInputStream positioned at the first record byte.\n     * @param data The full message data.\n     * @return the record which was parsed.\n     * @throws IOException In case of malformed replies.\n     */\n    public static Record<Data> parse(DataInputStream dis, byte[] data) throws IOException {\n        DnsName name = DnsName.parse(dis, data);\n        int typeValue = dis.readUnsignedShort();\n        TYPE type = TYPE.getType(typeValue);\n        int clazzValue = dis.readUnsignedShort();\n        CLASS clazz = CLASS.getClass(clazzValue & 0x7fff);\n        boolean unicastQuery = (clazzValue & 0x8000) > 0;\n        long ttl = (((long) dis.readUnsignedShort()) << 16) +\n                   dis.readUnsignedShort();\n        int payloadLength = dis.readUnsignedShort();\n        Data payloadData;\n        switch (type) {\n            case SOA:\n                payloadData = SOA.parse(dis, data);\n                break;\n            case SRV:\n                payloadData = SRV.parse(dis, data);\n                break;\n            case MX:\n                payloadData = MX.parse(dis, data);\n                break;\n            case AAAA:\n                payloadData = AAAA.parse(dis);\n                break;\n            case A:\n                payloadData = A.parse(dis);\n                break;\n            case NS:\n                payloadData = NS.parse(dis, data);\n                break;\n            case CNAME:\n                payloadData = CNAME.parse(dis, data);\n                break;\n            case DNAME:\n                payloadData = DNAME.parse(dis, data);\n                break;\n            case PTR:\n                payloadData = PTR.parse(dis, data);\n                break;\n            case TXT:\n                payloadData = TXT.parse(dis, payloadLength);\n                break;\n            case OPT:\n                payloadData = OPT.parse(dis, payloadLength);\n                break;\n            case DNSKEY:\n                payloadData = DNSKEY.parse(dis, payloadLength);\n                break;\n            case RRSIG:\n                payloadData = RRSIG.parse(dis, data, payloadLength);\n                break;\n            case DS:\n                payloadData = DS.parse(dis, payloadLength);\n                break;\n            case NSEC:\n                payloadData = NSEC.parse(dis, data, payloadLength);\n                break;\n            case NSEC3:\n                payloadData = NSEC3.parse(dis, payloadLength);\n                break;\n            case NSEC3PARAM:\n                payloadData = NSEC3PARAM.parse(dis);\n                break;\n            case TLSA:\n                payloadData = TLSA.parse(dis, payloadLength);\n                break;\n            case OPENPGPKEY:\n                payloadData = OPENPGPKEY.parse(dis, payloadLength);\n                break;\n            case DLV:\n                payloadData = DLV.parse(dis, payloadLength);\n                break;\n            case UNKNOWN:\n            default:\n                payloadData = UNKNOWN.parse(dis, payloadLength, type);\n                break;\n        }\n        return new Record<>(name, type, clazz, clazzValue, ttl, payloadData, unicastQuery);\n    }\n\n    public Record(DnsName name, TYPE type, CLASS clazz, long ttl, D payloadData, boolean unicastQuery) {\n        this(name, type, clazz, clazz.getValue() + (unicastQuery ? 0x8000 : 0), ttl, payloadData, unicastQuery);\n    }\n\n    public Record(String name, TYPE type, CLASS clazz, long ttl, D payloadData, boolean unicastQuery) {\n        this(DnsName.from(name), type, clazz, ttl, payloadData, unicastQuery);\n    }\n\n    public Record(String name, TYPE type, int clazzValue, long ttl, D payloadData) {\n        this(DnsName.from(name), type, CLASS.NONE, clazzValue, ttl, payloadData, false);\n    }\n\n    public Record(DnsName name, TYPE type, int clazzValue, long ttl, D payloadData) {\n        this(name, type, CLASS.NONE, clazzValue, ttl, payloadData, false);\n    }\n\n    private Record(DnsName name, TYPE type, CLASS clazz, int clazzValue, long ttl, D payloadData, boolean unicastQuery) {\n        this.name = name;\n        this.type = type;\n        this.clazz = clazz;\n        this.clazzValue = clazzValue;\n        this.ttl = ttl;\n        this.payloadData = payloadData;\n        this.unicastQuery = unicastQuery;\n    }\n\n    public void toOutputStream(OutputStream outputStream) throws IOException {\n        if (payloadData == null) {\n            throw new IllegalStateException(\"Empty Record has no byte representation\");\n        }\n\n        DataOutputStream dos = new DataOutputStream(outputStream);\n\n        name.writeToStream(dos);\n        dos.writeShort(type.getValue());\n        dos.writeShort(clazzValue);\n        dos.writeInt((int) ttl);\n\n        dos.writeShort(payloadData.length());\n        payloadData.toOutputStream(dos);\n    }\n\n    private transient byte[] bytes;\n\n    public byte[] toByteArray() {\n        if (bytes == null) {\n            int totalSize = name.size()\n                    + 10 // 2 byte short type + 2 byte short classValue + 4 byte int ttl + 2 byte short payload length.\n                    + payloadData.length();\n            ByteArrayOutputStream baos = new ByteArrayOutputStream(totalSize);\n            DataOutputStream dos = new DataOutputStream(baos);\n            try {\n                toOutputStream(dos);\n            } catch (IOException e) {\n                // Should never happen.\n                throw new AssertionError(e);\n            }\n            bytes = baos.toByteArray();\n        }\n        return bytes.clone();\n    }\n\n    /**\n     * Retrieve a textual representation of this resource record.\n     * @return String\n     */\n    @Override\n    public String toString() {\n        return name.getRawAce() + \".\\t\" + ttl + '\\t' + clazz + '\\t' + type + '\\t' + payloadData;\n    }\n\n    /**\n     * Check if this record answers a given query.\n     * @param q The query.\n     * @return True if this record is a valid answer.\n     */\n    public boolean isAnswer(Question q) {\n        return (q.type == type || q.type == TYPE.ANY) &&\n               (q.clazz == clazz || q.clazz == CLASS.ANY) &&\n               q.name.equals(name);\n    }\n\n    /**\n     * See if this query/response was a unicast query (highest class bit set).\n     * @return True if it is a unicast query/response record.\n     */\n    public boolean isUnicastQuery() {\n        return unicastQuery;\n    }\n\n    /**\n     * The payload data, usually a subclass of data (A, AAAA, CNAME, ...).\n     * @return The payload data.\n     */\n    public D getPayload() {\n        return payloadData;\n    }\n\n    /**\n     * Retrieve the record ttl.\n     * @return The record ttl.\n     */\n    public long getTtl() {\n        return ttl;\n    }\n\n    /**\n     * Get the question asking for this resource record. This will return <code>null</code> if the record is not retrievable, i.e.\n     * {@link TYPE#OPT}.\n     *\n     * @return the question for this resource record or <code>null</code>.\n     */\n    public Question getQuestion() {\n        switch (type) {\n        case OPT:\n            // OPT records are not retrievable.\n            return null;\n        case RRSIG:\n            RRSIG rrsig = (RRSIG) payloadData;\n            return new Question(name, rrsig.typeCovered, clazz);\n        default:\n            return new Question(name, type, clazz);\n        }\n    }\n\n    public DnsMessage.Builder getQuestionMessage() {\n        Question question = getQuestion();\n        if (question == null) {\n            return null;\n        }\n        return question.asMessageBuilder();\n    }\n\n    private transient Integer hashCodeCache;\n\n    @Override\n    public int hashCode() {\n        if (hashCodeCache == null) {\n            int hashCode = 1;\n            hashCode = 37 * hashCode + name.hashCode();\n            hashCode = 37 * hashCode + type.hashCode();\n            hashCode = 37 * hashCode + clazz.hashCode();\n            hashCode = 37 * hashCode + payloadData.hashCode();\n            hashCodeCache = hashCode;\n        }\n        return hashCodeCache;\n    }\n\n    @Override\n    public boolean equals(Object other) {\n        if (!(other instanceof Record)) {\n            return false;\n        }\n        if (other == this) {\n            return true;\n        }\n        Record<?> otherRecord = (Record<?>) other;\n        if (!name.equals(otherRecord.name)) return false;\n        if (type != otherRecord.type) return false;\n        if (clazz != otherRecord.clazz) return false;\n        // Note that we do not compare the TTL here, since we consider two Records with everything but the TTL equal to\n        // be equal too.\n        if (!payloadData.equals(otherRecord.payloadData)) return false;\n\n        return true;\n    }\n\n    /**\n     * Return the record if possible as record with the given {@link Data} class. If the record does not hold payload of\n     * the given data class type, then {@code null} will be returned.\n     *\n     * @param dataClass a class of the {@link Data} type.\n     * @param <E> a subtype of {@link Data}.\n     * @return the record with a specialized payload type or {@code null}.\n     * @see #as(Class)\n     */\n    @SuppressWarnings(\"unchecked\")\n    public <E extends Data> Record<E> ifPossibleAs(Class<E> dataClass) {\n        if (type.dataClass == dataClass) {\n            return (Record<E>) this;\n        }\n        return null;\n    }\n\n    /**\n     * Return the record as record with the given {@link Data} class. If the record does not hold payload of\n     * the given data class type, then a {@link IllegalArgumentException} will be thrown.\n     *\n     * @param dataClass a class of the {@link Data} type.\n     * @param <E> a subtype of {@link Data}.\n     * @return the record with a specialized payload type.\n     * @see #ifPossibleAs(Class)\n     */\n    public <E extends Data> Record<E> as(Class<E> dataClass) {\n        Record<E> eRecord = ifPossibleAs(dataClass);\n        if (eRecord == null) {\n            throw new IllegalArgumentException(\"The instance \" + this + \" can not be cast to a Record with\" + dataClass);\n        }\n        return eRecord;\n    }\n\n    public static <E extends Data> void filter(Collection<Record<E>> result, Class<E> dataClass,\n            Collection<Record<? extends Data>> input) {\n        for (Record<? extends Data> record : input) {\n            Record<E> filteredRecord = record.ifPossibleAs(dataClass);\n            if (filteredRecord == null)\n                continue;\n\n            result.add(filteredRecord);\n        }\n    }\n\n    public static <E extends Data> List<Record<E>> filter(Class<E> dataClass,\n            Collection<Record<? extends Data>> input) {\n        List<Record<E>> result = new ArrayList<>(input.size());\n        filter(result, dataClass, input);\n        return result;\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/SOA.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.Record.TYPE;\n\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\n\n/**\n * SOA (start of authority) record payload.\n */\npublic class SOA extends Data {\n\n    /**\n     * The domain name of the name server that was the original or primary source of data for this zone.\n     */\n    public final DnsName mname;\n\n    /**\n     * A domain name which specifies the mailbox of the person responsible for this zone.\n     */\n    public final DnsName rname;\n\n    /**\n     * The unsigned 32 bit version number of the original copy of the zone.  Zone transfers preserve this value.  This\n     * value wraps and should be compared using sequence space arithmetic.\n     */\n    public final long /* unsigned int */ serial;\n\n    /**\n     * A 32 bit time interval before the zone should be refreshed.\n     */\n    public final int refresh;\n\n    /**\n     * A 32 bit time interval that should elapse before a failed refresh should be retried.\n     */\n    public final int retry;\n\n    /**\n     * A 32 bit time value that specifies the upper limit on the time interval that can elapse before the zone is no\n     * longer authoritative.\n     */\n    public final int expire;\n\n    /**\n     * The unsigned 32 bit minimum TTL field that should be exported with any RR from this zone.\n     */\n    public final long /* unsigned int */ minimum;\n\n    public static SOA parse(DataInputStream dis, byte[] data)\n            throws IOException {\n        DnsName mname = DnsName.parse(dis, data);\n        DnsName rname = DnsName.parse(dis, data);\n        long serial = dis.readInt() & 0xFFFFFFFFL;\n        int refresh = dis.readInt();\n        int retry = dis.readInt();\n        int expire = dis.readInt();\n        long minimum = dis.readInt() & 0xFFFFFFFFL;\n        return new SOA(mname, rname, serial, refresh, retry, expire, minimum);\n    }\n\n    public SOA(String mname, String rname, long serial, int refresh, int retry, int expire, long minimum) {\n        this(DnsName.from(mname), DnsName.from(rname), serial, refresh, retry, expire, minimum);\n    }\n\n    public SOA(DnsName mname, DnsName rname, long serial, int refresh, int retry, int expire, long minimum) {\n        this.mname = mname;\n        this.rname = rname;\n        this.serial = serial;\n        this.refresh = refresh;\n        this.retry = retry;\n        this.expire = expire;\n        this.minimum = minimum;\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.SOA;\n    }\n\n    @Override\n    public void serialize(DataOutputStream dos) throws IOException {\n        mname.writeToStream(dos);\n        rname.writeToStream(dos);\n        dos.writeInt((int) serial);\n        dos.writeInt(refresh);\n        dos.writeInt(retry);\n        dos.writeInt(expire);\n        dos.writeInt((int) minimum);\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder sb = new StringBuilder()\n                .append(mname).append(\". \")\n                .append(rname).append(\". \")\n                .append(serial).append(' ')\n                .append(refresh).append(' ')\n                .append(retry).append(' ')\n                .append(expire).append(' ')\n                .append(minimum);\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/SRV.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\n\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.Record.TYPE;\n\n/**\n * SRV record payload (service pointer).\n */\npublic class SRV extends RRWithTarget implements Comparable<SRV> {\n\n    /**\n     * The priority of this service. Lower values mean higher priority.\n     */\n    public final int priority;\n\n    /**\n     * The weight of this service. Services with the same priority should be\n     * balanced based on weight.\n     */\n    public final int weight;\n\n    /**\n     * The target port.\n     */\n    public final int port;\n\n    public static SRV parse(DataInputStream dis, byte[] data)\n        throws IOException {\n        int priority = dis.readUnsignedShort();\n        int weight = dis.readUnsignedShort();\n        int port = dis.readUnsignedShort();\n        DnsName target = DnsName.parse(dis, data);\n        return new SRV(priority, weight, port, target);\n    }\n\n    public SRV(int priority, int weight, int port, String target) {\n        this(priority, weight, port, DnsName.from(target));\n    }\n\n    public SRV(int priority, int weight, int port, DnsName target) {\n        super(target);\n        this.priority = priority;\n        this.weight = weight;\n        this.port = port;\n    }\n\n    /**\n     * Check if the service is available at this domain. This checks f the target points to the root label. As per RFC\n     * 2782 the service is decidedly not available if there is only a single SRV answer pointing to the root label. From\n     * RFC 2782:\n     *\n     * <blockquote>A Target of \".\" means that the service is decidedly not available at this domain.</blockquote>\n     *\n     * @return true if the service is available at this domain.\n     */\n    public boolean isServiceAvailable() {\n        return !target.isRootLabel();\n    }\n\n    @Override\n    public void serialize(DataOutputStream dos) throws IOException {\n        dos.writeShort(priority);\n        dos.writeShort(weight);\n        dos.writeShort(port);\n        super.serialize(dos);\n    }\n\n    @Override\n    public String toString() {\n        return priority + \" \" + weight + \" \" + port + \" \" + target + \".\";\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.SRV;\n    }\n\n    @Override\n    public int compareTo(SRV other) {\n        int res = other.priority - this.priority;\n        if (res == 0) {\n            res = this.weight - other.weight;\n        }\n        return res;\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/TLSA.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class TLSA extends Data {\n\n    private static final Map<Byte, CertUsage> CERT_USAGE_LUT = new HashMap<>();\n\n    /**\n     * The certificate usage field.\n     *\n     * @see <a href=\"https://tools.ietf.org/html/rfc6698#section-2.1.1\">RFC 6698 § 2.1.1</a>\n     *\n     */\n    public enum CertUsage {\n\n        /**\n         * The given <b>CA</b> certificate (or its public key) MUST be found in at least\n         * one PKIX path to the end entity certificate.\n         *\n         * <p>\n         * PKIX-TA(0)\n         * </p>\n         */\n        caConstraint((byte) 0),\n\n        /**\n         * The given certificate (or its public key) MUST match the end entity\n         * certificate and MUST pass PKIX validation. Note that the requirement to pass\n         * PKIX validation is what makes this different from\n         * {@link #domainIssuedCertificate}.\n         *\n         * <p>\n         * PKIX-EE(1)\n         * </p>\n         */\n        serviceCertificateConstraint((byte) 1),\n\n        /**\n         * The given certificate (or its public key) MUST be used as trust anchor when\n         * validating the end entity certificate.\n         *\n         * <p>\n         * DANE-TA(2)\n         * </p>\n         */\n        trustAnchorAssertion((byte) 2),\n\n        /**\n         * The given certificate (or its public key) MUST match the end entity\n         * certificate. Unlike {@link #serviceCertificateConstraint}, this does not\n         * require PKIX validation.\n         *\n         * <p>\n         * DANE-EE(3)\n         * </p>\n         */\n        domainIssuedCertificate((byte) 3),\n        ;\n\n        public final byte byteValue;\n\n        CertUsage(byte byteValue) {\n            this.byteValue = byteValue;\n            CERT_USAGE_LUT.put(byteValue, this);\n        }\n    }\n\n    private static final Map<Byte, Selector> SELECTOR_LUT = new HashMap<>();\n\n    public enum Selector {\n        fullCertificate((byte) 0),\n        subjectPublicKeyInfo((byte) 1),\n        ;\n\n        public final byte byteValue;\n\n         Selector(byte byteValue) {\n            this.byteValue = byteValue;\n            SELECTOR_LUT.put(byteValue, this);\n        }\n    }\n\n    private static final Map<Byte, MatchingType> MATCHING_TYPE_LUT = new HashMap<>();\n\n    public enum MatchingType {\n        noHash((byte) 0),\n        sha256((byte) 1),\n        sha512((byte) 2),\n        ;\n\n        public final byte byteValue;\n\n        MatchingType(byte byteValue) {\n            this.byteValue = byteValue;\n            MATCHING_TYPE_LUT.put(byteValue, this);\n        }\n    }\n\n    static {\n        // Ensure that the LUTs are initialized.\n        CertUsage.values();\n        Selector.values();\n        MatchingType.values();\n    }\n\n    /**\n     * The provided association that will be used to match the certificate presented in\n     * the TLS handshake.\n     */\n    public final byte certUsageByte;\n\n    public final CertUsage certUsage;\n\n    /**\n     * Which part of the TLS certificate presented by the server will be matched against the\n     * association data.\n     */\n    public final byte selectorByte;\n\n    public final Selector selector;\n\n    /**\n     * How the certificate association is presented.\n     */\n    public final byte matchingTypeByte;\n\n    public final MatchingType matchingType;\n\n    /**\n     * The \"certificate association data\" to be matched.\n     */\n    private final byte[] certificateAssociation;\n\n    public static TLSA parse(DataInputStream dis, int length) throws IOException {\n        byte certUsage = dis.readByte();\n        byte selector = dis.readByte();\n        byte matchingType = dis.readByte();\n        byte[] certificateAssociation = new byte[length - 3];\n        if (dis.read(certificateAssociation) != certificateAssociation.length) throw new IOException();\n        return new TLSA(certUsage, selector, matchingType, certificateAssociation);\n    }\n\n    TLSA(byte certUsageByte, byte selectorByte, byte matchingTypeByte, byte[] certificateAssociation) {\n        this.certUsageByte = certUsageByte;\n        this.certUsage = CERT_USAGE_LUT.get(certUsageByte);\n\n        this.selectorByte = selectorByte;\n        this.selector = SELECTOR_LUT.get(selectorByte);\n\n        this.matchingTypeByte = matchingTypeByte;\n        this.matchingType = MATCHING_TYPE_LUT.get(matchingTypeByte);\n\n        this.certificateAssociation = certificateAssociation;\n    }\n\n    @Override\n    public Record.TYPE getType() {\n        return Record.TYPE.TLSA;\n    }\n\n    @Override\n    public void serialize(DataOutputStream dos) throws IOException {\n        dos.writeByte(certUsageByte);\n        dos.writeByte(selectorByte);\n        dos.writeByte(matchingTypeByte);\n        dos.write(certificateAssociation);\n    }\n\n    @Override\n    @SuppressWarnings(\"UnnecessaryStringBuilder\")\n    public String toString() {\n        return new StringBuilder()\n                .append(certUsageByte).append(' ')\n                .append(selectorByte).append(' ')\n                .append(matchingTypeByte).append(' ')\n                .append(new BigInteger(1, certificateAssociation).toString(16)).toString();\n    }\n\n    public byte[] getCertificateAssociation() {\n        return certificateAssociation.clone();\n    }\n\n    public boolean certificateAssociationEquals(byte[] otherCertificateAssociation) {\n        return Arrays.equals(certificateAssociation, otherCertificateAssociation);\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/TXT.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\n\nimport org.minidns.record.Record.TYPE;\n\n/**\n *  A TXT record. Actually a binary blob containing extents, each of which is a one-byte count\n *  followed by that many bytes of data, which can usually be interpreted as ASCII strings\n *  but not always.\n */\npublic class TXT extends Data {\n\n    private final byte[] blob;\n\n    public static TXT parse(DataInputStream dis, int length) throws IOException {\n        byte[] blob = new byte[length];\n        dis.readFully(blob);\n        return new TXT(blob);\n    }\n\n    public TXT(byte[] blob) {\n        this.blob = blob;\n    }\n\n    public byte[] getBlob() {\n        return blob.clone();\n    }\n\n    private transient String textCache;\n\n    public String getText() {\n        if (textCache == null) {\n            StringBuilder sb = new StringBuilder();\n            Iterator<String> it = getCharacterStrings().iterator();\n            while (it.hasNext()) {\n                sb.append(it.next());\n                if (it.hasNext()) {\n                    sb.append(\" / \");\n                }\n            }\n            textCache = sb.toString();\n        }\n        return textCache;\n    }\n\n    private transient List<String> characterStringsCache;\n\n    public List<String> getCharacterStrings() {\n        if (characterStringsCache == null) {\n            List<byte[]> extents = getExtents();\n            List<String> characterStrings = new ArrayList<>(extents.size());\n            for (byte[] extent : extents) {\n                characterStrings.add(new String(extent, StandardCharsets.UTF_8));\n            }\n\n            characterStringsCache = Collections.unmodifiableList(characterStrings);\n        }\n        return characterStringsCache;\n    }\n\n    public List<byte[]> getExtents() {\n        ArrayList<byte[]> extents = new ArrayList<byte[]>();\n        int segLength = 0;\n        for (int used = 0; used < blob.length; used += segLength) {\n            segLength = 0x00ff & blob[used];\n            int end = ++used + segLength;\n            byte[] extent = Arrays.copyOfRange(blob, used, end);\n            extents.add(extent);\n        }\n        return extents;\n    }\n\n    @Override\n    public void serialize(DataOutputStream dos) throws IOException {\n        dos.write(blob);\n    }\n\n    @Override\n    public TYPE getType() {\n        return TYPE.TXT;\n    }\n\n    @Override\n    public String toString() {\n        return \"\\\"\" + getText() + \"\\\"\";\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/record/UNKNOWN.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\n\nimport org.minidns.record.Record.TYPE;\n\npublic final class UNKNOWN extends Data {\n\n    private final TYPE type;\n    private final byte[] data;\n\n    private UNKNOWN(DataInputStream dis, int payloadLength, TYPE type) throws IOException {\n        this.type = type;\n        this.data = new byte[payloadLength];\n        dis.readFully(data);\n    }\n\n    @Override\n    public TYPE getType() {\n        return type;\n    }\n\n    @Override\n    public void serialize(DataOutputStream dos) throws IOException {\n        dos.write(data);\n    }\n\n    public static UNKNOWN parse(DataInputStream dis, int payloadLength, TYPE type)\n            throws IOException {\n        return new UNKNOWN(dis, payloadLength, type);\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/util/Base32.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\n/**\n * Very minimal Base32 encoder.\n */\npublic final class Base32 {\n    private static final String ALPHABET = \"0123456789ABCDEFGHIJKLMNOPQRSTUV\";\n    private static final String PADDING = \"======\";\n\n    /**\n     * Do not allow to instantiate Base32\n     */\n    private Base32() {\n    }\n\n    public static String encodeToString(byte[] bytes) {\n        int paddingCount = (int) (8 - (bytes.length % 5) * 1.6) % 8;\n        byte[] padded = new byte[bytes.length + paddingCount];\n        System.arraycopy(bytes, 0, padded, 0, bytes.length);\n        StringBuilder sb = new StringBuilder();\n        for (int i = 0; i < bytes.length; i += 5) {\n            long j = ((long) (padded[i] & 0xff) << 32) + ((long) (padded[i + 1] & 0xff) << 24)\n                    + ((padded[i + 2] & 0xff) << 16) + ((padded[i + 3] & 0xff) << 8) + (padded[i + 4] & 0xff);\n            sb.append(ALPHABET.charAt((int) ((j >> 35) & 0x1f))).append(ALPHABET.charAt((int) ((j >> 30) & 0x1f)))\n                    .append(ALPHABET.charAt((int) ((j >> 25) & 0x1f))).append(ALPHABET.charAt((int) ((j >> 20) & 0x1f)))\n                    .append(ALPHABET.charAt((int) ((j >> 15) & 0x1f))).append(ALPHABET.charAt((int) ((j >> 10) & 0x1f)))\n                    .append(ALPHABET.charAt((int) ((j >> 5) & 0x1f))).append(ALPHABET.charAt((int) (j & 0x1f)));\n        }\n        return sb.substring(0, sb.length() - paddingCount) + PADDING.substring(0, paddingCount);\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/util/Base64.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\n/**\n * Very minimal Base64 encoder.\n */\npublic final class Base64 {\n    private static final String ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n    private static final String PADDING = \"==\";\n\n    /**\n     * Do not allow to instantiate Base64\n     */\n    private Base64() {\n    }\n\n    public static String encodeToString(byte[] bytes) {\n        int paddingCount = (3 - (bytes.length % 3)) % 3;\n        byte[] padded = new byte[bytes.length + paddingCount];\n        System.arraycopy(bytes, 0, padded, 0, bytes.length);\n        StringBuilder sb = new StringBuilder();\n        for (int i = 0; i < bytes.length; i += 3) {\n            int j = ((padded[i] & 0xff) << 16) + ((padded[i + 1] & 0xff) << 8) + (padded[i + 2] & 0xff);\n            sb.append(ALPHABET.charAt((j >> 18) & 0x3f)).append(ALPHABET.charAt((j >> 12) & 0x3f))\n                    .append(ALPHABET.charAt((j >> 6) & 0x3f)).append(ALPHABET.charAt(j & 0x3f));\n        }\n        return sb.substring(0, sb.length() - paddingCount) + PADDING.substring(0, paddingCount);\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/util/CallbackRecipient.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\n/**\n * A recipient of success and exception callbacks.\n *\n * @param <V> the type of the success value.\n * @param <E> the type of the exception.\n */\npublic interface  CallbackRecipient<V, E> {\n\n    CallbackRecipient<V, E> onSuccess(SuccessCallback<V> successCallback);\n\n    CallbackRecipient<V, E> onError(ExceptionCallback<E> exceptionCallback);\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/util/CollectionsUtil.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\nimport java.util.Iterator;\nimport java.util.Random;\nimport java.util.Set;\n\npublic class CollectionsUtil {\n\n    public static <T> T getRandomFrom(Set<T> set, Random random) {\n        int randomIndex = random.nextInt(set.size());\n        Iterator<T> iterator = set.iterator();\n        for (int i = 0; i < randomIndex; i++) {\n            if (!iterator.hasNext()) break;\n            iterator.next();\n        }\n        return iterator.next();\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/util/ExceptionCallback.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\npublic interface ExceptionCallback<E> {\n\n    void processException(E exception);\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/util/Hex.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\npublic class Hex {\n\n    public static StringBuilder from(byte[] bytes) {\n        StringBuilder sb = new StringBuilder(bytes.length * 2);\n        for (byte b : bytes) {\n            sb.append(String.format(\"%02X \", b));\n        }\n        return sb;\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/util/InetAddressUtil.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\nimport java.net.Inet4Address;\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.regex.Pattern;\n\nimport org.minidns.dnsname.DnsName;\n\npublic class InetAddressUtil {\n\n    public static Inet4Address ipv4From(CharSequence cs) {\n        InetAddress inetAddress;\n        try {\n            inetAddress = InetAddress.getByName(cs.toString());\n        } catch (UnknownHostException e) {\n            throw new IllegalArgumentException(e);\n        }\n        if (inetAddress instanceof Inet4Address) {\n            return (Inet4Address) inetAddress;\n        }\n        throw new IllegalArgumentException();\n    }\n\n    public static Inet6Address ipv6From(CharSequence cs) {\n        InetAddress inetAddress;\n        try {\n            inetAddress = InetAddress.getByName(cs.toString());\n        } catch (UnknownHostException e) {\n            throw new IllegalArgumentException(e);\n        }\n        if (inetAddress instanceof Inet6Address) {\n            return (Inet6Address) inetAddress;\n        }\n        throw new IllegalArgumentException();\n    }\n\n    // IPV4_REGEX from http://stackoverflow.com/a/46168/194894 by Kevin Wong (http://stackoverflow.com/users/4792/kevin-wong) licensed under\n    // CC BY-SA 3.0.\n    private static final Pattern IPV4_PATTERN = Pattern.compile(\"\\\\A(25[0-5]|2[0-4]\\\\d|[0-1]?\\\\d?\\\\d)(\\\\.(25[0-5]|2[0-4]\\\\d|[0-1]?\\\\d?\\\\d)){3}\\\\z\");\n\n    public static boolean isIpV4Address(CharSequence address) {\n        if (address == null) {\n            return false;\n        }\n        return IPV4_PATTERN.matcher(address).matches();\n    }\n\n    // IPv6 Regular Expression from http://stackoverflow.com/a/17871737/194894 by David M. Syzdek\n    // (http://stackoverflow.com/users/903194/david-m-syzdek) licensed under CC BY-SA 3.0.\n    private static final Pattern IPV6_PATTERN = Pattern.compile(\n            \"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\");\n\n    public static boolean isIpV6Address(CharSequence address) {\n        if (address == null) {\n            return false;\n        }\n        return IPV6_PATTERN.matcher(address).matches();\n    }\n\n    public static boolean isIpAddress(CharSequence address) {\n        return isIpV6Address(address) || isIpV4Address(address);\n    }\n\n    public static InetAddress convertToInetAddressIfPossible(CharSequence address) {\n        if (!isIpAddress(address)) {\n            return null;\n        }\n\n        String addressString = address.toString();\n        try {\n            return InetAddress.getByName(addressString);\n        } catch (UnknownHostException e) {\n            // Should never happen.\n            throw new AssertionError(e);\n        }\n    }\n\n    public static DnsName reverseIpAddressOf(Inet6Address inet6Address) {\n        final String ipAddress = inet6Address.getHostAddress();\n        final String[] ipAddressParts = ipAddress.split(\":\");\n\n        String[] parts = new String[32];\n        int currentPartNum = 0;\n        for (int i = ipAddressParts.length - 1; i >= 0; i--) {\n            final String currentPart = ipAddressParts[i];\n            final int missingPlaces = 4 - currentPart.length();\n            for (int j = 0; j < missingPlaces; j++) {\n                parts[currentPartNum++] = \"0\";\n            }\n            for (int j = 0; j < currentPart.length(); j++) {\n                parts[currentPartNum++] = Character.toString(currentPart.charAt(j));\n            }\n        }\n\n        return DnsName.from(parts);\n    }\n\n    public static DnsName reverseIpAddressOf(Inet4Address inet4Address) {\n        final String[] ipAddressParts = inet4Address.getHostAddress().split(\"\\\\.\");\n        assert ipAddressParts.length == 4;\n\n        return DnsName.from(ipAddressParts);\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/util/MultipleIoException.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\nimport java.io.IOException;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\n\npublic final class MultipleIoException extends IOException {\n\n    /**\n     * \n     */\n    private static final long serialVersionUID = -5932211337552319515L;\n\n    private final List<IOException> ioExceptions;\n\n    private MultipleIoException(List<? extends IOException> ioExceptions) {\n        super(getMessage(ioExceptions));\n        assert !ioExceptions.isEmpty();\n        this.ioExceptions = Collections.unmodifiableList(ioExceptions);\n    }\n\n    public List<IOException> getExceptions() {\n        return ioExceptions;\n    }\n\n    private static String getMessage(Collection<? extends Exception> exceptions) {\n        StringBuilder sb = new StringBuilder();\n        Iterator<? extends Exception> it = exceptions.iterator();\n        while (it.hasNext()) {\n            sb.append(it.next().getMessage());\n            if (it.hasNext()) {\n                sb.append(\", \");\n            }\n        }\n        return sb.toString();\n    }\n\n    public static void throwIfRequired(List<? extends IOException> ioExceptions) throws IOException {\n        if (ioExceptions == null || ioExceptions.isEmpty()) {\n            return;\n        }\n        if (ioExceptions.size() == 1) {\n            throw ioExceptions.get(0);\n        }\n        throw new MultipleIoException(ioExceptions);\n    }\n\n    public static IOException toIOException(List<? extends IOException> ioExceptions) {\n        int size = ioExceptions.size();\n        if (size == 1) {\n            return ioExceptions.get(0);\n        } else if (size > 1) {\n            return new MultipleIoException(ioExceptions);\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/util/NameUtil.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\nimport org.minidns.dnsname.DnsName;\n\n/**\n * Utilities related to internationalized domain names and dns name handling.\n */\npublic final class NameUtil {\n\n    /**\n     * Check if two internationalized domain names are equal, possibly causing\n     * a serialization of both domain names.\n     *\n     * @param name1 The first domain name.\n     * @param name2 The second domain name.\n     * @return True if both domain names are the same.\n     */\n    @SuppressWarnings(\"ReferenceEquality\")\n    public static boolean idnEquals(String name1, String name2) {\n        if (name1 == name2) return true; // catches null, null\n        if (name1 == null) return false;\n        if (name2 == null) return false;\n        if (name1.equals(name2)) return true;\n\n        return DnsName.from(name1).compareTo(DnsName.from(name2)) == 0;\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/util/PlatformDetection.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\npublic class PlatformDetection {\n\n    private static Boolean android;\n\n    public static boolean isAndroid() {\n        if (android == null) {\n            try {\n                Class.forName(\"android.Manifest\"); // throws execption when not on Android\n                android = true;\n            } catch (Exception e) {\n                android = false;\n            }\n        }\n        return android;\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/util/SafeCharSequence.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\npublic class SafeCharSequence implements CharSequence {\n\n    @Override\n    public final int length() {\n        return toSafeString().length();\n    }\n\n    @Override\n    public final char charAt(int index) {\n        return toSafeString().charAt(index);\n    }\n\n    @Override\n    public final CharSequence subSequence(int start, int end) {\n        return toSafeString().subSequence(end, end);\n    }\n\n    public String toSafeString() {\n        // The default implementation assumes that toString() returns a safe\n        // representation. Subclasses may override toSafeString() if this assumption is\n        // not correct.\n        return toString();\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/util/SrvUtil.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.SortedMap;\nimport java.util.TreeMap;\n\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.SRV;\n\npublic class SrvUtil {\n\n    /**\n     * Sort the given collection of {@link SRV} resource records by their priority and weight.\n     * <p>\n     * Sorting by priority is easy. Sorting the buckets of SRV records with the same priority by weight requires to choose those records\n     * randomly but taking the weight into account.\n     * </p>\n     *\n     * @param srvRecords\n     *            a collection of SRV records.\n     * @return a sorted list of the given records.\n     */\n    @SuppressWarnings({\"MixedMutabilityReturnType\", \"JdkObsolete\"})\n    public static List<SRV> sortSrvRecords(Collection<SRV> srvRecords) {\n        // RFC 2782, Usage rules: \"If there is precisely one SRV RR, and its Target is \".\"\n        // (the root domain), abort.\"\n        if (srvRecords.size() == 1 && srvRecords.iterator().next().target.equals(DnsName.ROOT)) {\n            return Collections.emptyList();\n        }\n\n        // Create the priority buckets.\n        SortedMap<Integer, List<SRV>> buckets = new TreeMap<>();\n        for (SRV srvRecord : srvRecords) {\n            Integer priority = srvRecord.priority;\n            List<SRV> bucket = buckets.get(priority);\n            if (bucket == null) {\n                bucket = new LinkedList<>();\n                buckets.put(priority, bucket);\n            }\n            bucket.add(srvRecord);\n        }\n\n        List<SRV> sortedSrvRecords = new ArrayList<>(srvRecords.size());\n\n        for (List<SRV> bucket : buckets.values()) {\n            // The list of buckets will be sorted by priority, thanks to SortedMap. We now have determine the order of\n            // the SRV records with the same priority, i.e., within the same bucket, by their weight. This is done by\n            // creating an array 'totals' which reflects the percentage of the SRV RRs weight by the total weight of all\n            // SRV RRs in the bucket. For every entry in the bucket, we choose one using a random number and the sum of\n            // all weights left in the bucket. We then select RRs position based on the according index of the selected\n            // value in the 'total' array. This ensures that its weight is taken into account.\n            int bucketSize;\n            while ((bucketSize = bucket.size()) > 0) {\n                int[] totals = new int[bucketSize];\n\n                int zeroWeight = 1;\n                for (SRV srv : bucket) {\n                    if (srv.weight > 0) {\n                        zeroWeight = 0;\n                        break;\n                    }\n                }\n\n                int bucketWeightSum = 0, count = 0;\n                for (SRV srv : bucket) {\n                    bucketWeightSum += srv.weight + zeroWeight;\n                    totals[count++] = bucketWeightSum;\n                }\n\n                int selectedPosition;\n                if (bucketWeightSum == 0) {\n                    // If total priority is 0, then the sum of all weights in this priority bucket is 0. So we simply\n                    // select one of the weights randomly as the other algorithm performed in the else block is unable\n                    // to handle this case.\n                    selectedPosition = (int) (Math.random() * bucketSize);\n                } else {\n                    double rnd = Math.random() * bucketWeightSum;\n                    selectedPosition = bisect(totals, rnd);\n                }\n\n                SRV choosenSrvRecord = bucket.remove(selectedPosition);\n                sortedSrvRecords.add(choosenSrvRecord);\n            }\n        }\n\n        return sortedSrvRecords;\n    }\n\n    // TODO This is not yet really bisection just a stupid linear search.\n    private static int bisect(int[] array, double value) {\n        int pos = 0;\n        for (int element : array) {\n            if (value < element)\n                break;\n            pos++;\n        }\n        return pos;\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/main/java/org/minidns/util/SuccessCallback.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\npublic interface SuccessCallback<T> {\n\n    void onSuccess(T result);\n\n}\n"
  },
  {
    "path": "minidns-core/src/test/java/org/minidns/Assert.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\n\npublic class Assert {\n\n    public static void assertCsEquals(CharSequence expected, CharSequence actual) {\n        assertCsEquals(null, expected, actual);\n    }\n\n    public static void assertCsEquals(String message, CharSequence expected, CharSequence actual) {\n        if (expected != null && actual != null) {\n            assertEquals(expected.toString(), actual.toString(), message);\n        } else {\n            assertEquals(expected, actual, message);\n        }\n    }\n\n    public static <T> void assertArrayContentEquals(T[] expect, Collection<? extends T> value) {\n        assertEquals(expect.length, value.size());\n        List<T> list = new ArrayList<>(Arrays.asList(expect));\n        for (Object type : value) {\n            assertTrue(list.remove(type));\n        }\n        assertTrue(list.isEmpty());\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/test/java/org/minidns/dnslabel/DnsLabelTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnslabel;\n\nimport static org.minidns.Assert.assertCsEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.minidns.idna.MiniDnsIdna;\n\npublic class DnsLabelTest {\n\n    @Test\n    public void simpleNonReservedLdhLabelTest() {\n        final String nonReservedLdhLabelString = \"test\";\n        final DnsLabel label = DnsLabel.from(nonReservedLdhLabelString);\n\n        assertEquals(nonReservedLdhLabelString, label.label);\n        assertTrue(label instanceof NonReservedLdhLabel);\n        assertEquals(\"NonReservedLdhLabel\", label.getLabelType());\n    }\n\n    @Test\n    public void aLabelTest() {\n        final String uLabelString = \"müller\";\n        final String aLabelString = MiniDnsIdna.toASCII(uLabelString);\n        final DnsLabel label = DnsLabel.from(aLabelString);\n\n        assertEquals(aLabelString, label.label);\n        assertTrue(label instanceof ALabel);\n        assertEquals(uLabelString, label.getInternationalizedRepresentation());\n        assertEquals(\"ALabel\", label.getLabelType());\n    }\n\n    @Test\n    public void fakeALabelTest() {\n        final String fakeALabelString = \"xn--mller-va\";\n        final DnsLabel label = DnsLabel.from(fakeALabelString);\n\n        assertEquals(fakeALabelString, label.label);\n        assertTrue(label instanceof FakeALabel);\n        assertEquals(\"FakeALabel\", label.getLabelType());\n    }\n\n    @Test\n    public void underscoreLabelTest() {\n        final String underscoreLabelString = \"_tcp\";\n        final DnsLabel label = DnsLabel.from(underscoreLabelString);\n\n        assertEquals(underscoreLabelString, label.label);\n        assertTrue(label instanceof UnderscoreLabel);\n        assertEquals(\"UnderscoreLabel\", label.getLabelType());\n    }\n\n    @Test\n    public void leadingHyphenLabelTest() {\n        final String leadingHyphenLabelString = \"-foo\";\n        final DnsLabel label = DnsLabel.from(leadingHyphenLabelString);\n\n        assertEquals(leadingHyphenLabelString, label.label);\n        assertTrue(label instanceof LeadingOrTrailingHyphenLabel);\n        assertEquals(\"LeadingOrTrailingHyphenLabel\", label.getLabelType());\n    }\n\n    @Test\n    public void trailingHyphenLabelTest() {\n        final String trailingHyphenLabelString = \"bar-\";\n        final DnsLabel label = DnsLabel.from(trailingHyphenLabelString);\n\n        assertEquals(trailingHyphenLabelString, label.label);\n        assertTrue(label instanceof LeadingOrTrailingHyphenLabel);\n        assertEquals(\"LeadingOrTrailingHyphenLabel\", label.getLabelType());\n    }\n\n    @Test\n    public void otherNonLdhLabelTest() {\n        final String otherNonLdhLabelString = \"w@$abi\";\n        final DnsLabel label = DnsLabel.from(otherNonLdhLabelString);\n\n        assertEquals(otherNonLdhLabelString, label.label);\n        assertTrue(label instanceof OtherNonLdhLabel);\n        assertEquals(\"OtherNonLdhLabel\", label.getLabelType());\n    }\n\n    @Test\n    public void dnsLabelWildcardStringTest() {\n        assertEquals(\"*\", DnsLabel.WILDCARD_LABEL.toString());\n    }\n\n    @Test\n    public void escapeUnsafeCharactersTest() {\n        assertCsEquals(\"foo●bar\", DnsLabel.from(\"foo.bar\"));\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/test/java/org/minidns/dnsmessage/DnsMessageTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsmessage;\n\nimport org.minidns.constants.DnssecConstants.DigestAlgorithm;\nimport org.minidns.constants.DnssecConstants.SignatureAlgorithm;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.edns.Edns;\nimport org.minidns.record.A;\nimport org.minidns.record.AAAA;\nimport org.minidns.record.RRWithTarget;\nimport org.minidns.record.Record;\nimport org.minidns.record.DNSKEY;\nimport org.minidns.record.DS;\nimport org.minidns.record.Data;\nimport org.minidns.record.MX;\nimport org.minidns.record.NS;\nimport org.minidns.record.NSEC;\nimport org.minidns.record.NSEC3;\nimport org.minidns.record.NSEC3.HashAlgorithm;\nimport org.minidns.record.Record.CLASS;\nimport org.minidns.record.Record.TYPE;\nimport org.minidns.record.OPT;\nimport org.minidns.record.RRSIG;\nimport org.minidns.record.SOA;\nimport org.minidns.record.SRV;\nimport org.minidns.record.TXT;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport java.util.TreeMap;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.minidns.Assert.assertArrayContentEquals;\nimport static org.minidns.Assert.assertCsEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class DnsMessageTest {\n\n    DnsMessage getMessageFromResource(final String resourceFileName) throws IOException {\n        DnsMessage result;\n        try (InputStream inputStream = getClass().getResourceAsStream(resourceFileName);\n                ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {\n\n            // TODO: There should be a more efficient way to read the resource file as reading byte per byte.\n            for (int readBytes = inputStream.read(); readBytes >= 0; readBytes = inputStream.read())\n                outputStream.write(readBytes);\n\n            result = new DnsMessage(outputStream.toByteArray());\n        }\n\n        assertNotNull(result);\n\n        return result;\n    }\n\n    @Test\n    public void testALookup() throws Exception {\n        DnsMessage m = getMessageFromResource(\"sun-a\");\n        assertFalse(m.authoritativeAnswer);\n        List<Record<? extends Data>> answers = m.answerSection;\n        assertEquals(2, answers.size());\n\n        Record<? extends Data> cname = answers.get(0);\n        Record<? extends Data> a = answers.get(1);\n\n        assertTrue(cname.getPayload() instanceof RRWithTarget);\n        assertEquals(TYPE.CNAME, cname.getPayload().getType());\n        assertCsEquals(\"legacy-sun.oraclegha.com\",\n                     ((RRWithTarget) cname.getPayload()).target);\n\n        assertCsEquals(\"legacy-sun.oraclegha.com\", a.name);\n        assertTrue(a.getPayload() instanceof A);\n        assertEquals(TYPE.A, a.getPayload().getType());\n        assertCsEquals(\"156.151.59.35\", a.getPayload().toString());\n    }\n\n\n    @Test\n    public void testAAAALookup() throws Exception {\n        DnsMessage m = getMessageFromResource(\"google-aaaa\");\n        assertFalse(m.authoritativeAnswer);\n        List<Record<? extends Data>> answers = m.answerSection;\n        assertEquals(1, answers.size());\n        Record<? extends Data> answer = answers.get(0);\n        assertCsEquals(\"google.com\", answer.name);\n        assertTrue(answer.getPayload() instanceof AAAA);\n        assertEquals(TYPE.AAAA, answer.getPayload().getType());\n        assertCsEquals(\"2a00:1450:400c:c02:0:0:0:8a\", answer.getPayload().toString());\n    }\n\n\n    @Test\n    public void testMXLookup() throws Exception {\n        DnsMessage m = getMessageFromResource(\"gmail-mx\");\n        assertFalse(m.authoritativeAnswer);\n        List<Record<? extends Data>> answers = m.answerSection;\n        assertEquals(5, answers.size());\n        Map<Integer, DnsName> mxes = new TreeMap<>();\n        for (Record<? extends Data> r : answers) {\n            assertCsEquals(\"gmail.com\", r.name);\n            Data d = r.getPayload();\n            assertTrue(d instanceof MX);\n            assertEquals(TYPE.MX, d.getType());\n            mxes.put(((MX) d).priority, ((MX) d).target);\n        }\n        assertCsEquals(\"gmail-smtp-in.l.google.com\", mxes.get(5));\n        assertCsEquals(\"alt1.gmail-smtp-in.l.google.com\", mxes.get(10));\n        assertCsEquals(\"alt2.gmail-smtp-in.l.google.com\", mxes.get(20));\n        assertCsEquals(\"alt3.gmail-smtp-in.l.google.com\", mxes.get(30));\n        assertCsEquals(\"alt4.gmail-smtp-in.l.google.com\", mxes.get(40));\n    }\n\n\n    @Test\n    public void testSRVLookup() throws Exception {\n        DnsMessage m = getMessageFromResource(\"gpn-srv\");\n        assertFalse(m.authoritativeAnswer);\n        List<Record<? extends Data>> answers = m.answerSection;\n        assertEquals(1, answers.size());\n        Record<? extends Data> answer = answers.get(0);\n        assertTrue(answer.getPayload() instanceof SRV);\n        assertEquals(TYPE.SRV, answer.getPayload().getType());\n        SRV r = (SRV) answer.getPayload();\n        assertCsEquals(\"raven.toroid.org\", r.target);\n        assertEquals(5222, r.port);\n        assertEquals(0, r.priority);\n    }\n\n    @Test\n    public void testTXTLookup() throws Exception {\n        DnsMessage m = getMessageFromResource(\"codinghorror-txt\");\n        HashSet<String> txtToBeFound = new HashSet<>();\n        txtToBeFound.add(\"google-site-verification=2oV3cW79A6icpGf-JbLGY4rP4_omL4FOKTqRxb-Dyl4\");\n        txtToBeFound.add(\"keybase-site-verification=dKxf6T30x5EbNIUpeJcbWxUABJEnVWzQ3Z3hCumnk10\");\n        txtToBeFound.add(\"v=spf1 include:spf.mandrillapp.com ~all\");\n        List<Record<? extends Data>> answers = m.answerSection;\n        for (Record<? extends Data> r : answers) {\n            assertCsEquals(\"codinghorror.com\", r.name);\n            Data d = r.getPayload();\n            assertTrue(d instanceof TXT);\n            assertEquals(TYPE.TXT, d.getType());\n            TXT txt = (TXT) d;\n            assertTrue(txtToBeFound.contains(txt.getText()));\n            txtToBeFound.remove(txt.getText());\n        }\n        assertEquals(txtToBeFound.size(), 0);\n    }\n\n    @Test\n    public void testTXTMultiCharacterStringLookup() throws IOException {\n        DnsMessage dnsMessage = getMessageFromResource(\"gmail-domainkey-txt\");\n        assertEquals(1, dnsMessage.answerSection.size());\n\n        Record<?> answerRecord = dnsMessage.answerSection.get(0);\n\n        assertEquals(TYPE.TXT, answerRecord.type);\n        Record<TXT> txtRecord = answerRecord.as(TXT.class);\n\n        List<String> characterStrings = txtRecord.payloadData.getCharacterStrings();\n        assertEquals(2, characterStrings.size());\n        assertEquals(\n                \"k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAviPGBk4ZB64UfSqWyAicdR7lodhytae+EYRQVtKDhM+1mXjEqRtP/pDT3sBhazkmA48n2k5NJUyMEoO8nc2r6sUA+/Dom5jRBZp6qDKJOwjJ5R/OpHamlRG+YRJQqR\",\n                characterStrings.get(0));\n        assertEquals(\n                \"tqEgSiJWG7h7efGYWmh4URhFM9k9+rmG/CwCgwx7Et+c8OMlngaLl04/bPmfpjdEyLWyNimk761CX6KymzYiRDNz1MOJOJ7OzFaS4PFbVLn0m5mf0HVNtBpPwWuCNvaFVflUYxEyblbB6h/oWOPGbzoSgtRA47SHV53SwZjIsVpbq4LxUW9IxAEwYzGcSgZ4n5Q8X8TndowsDUzoccPFGhdwIDAQAB\",\n                characterStrings.get(1));\n\n        String text = txtRecord.payloadData.getText();\n        assertEquals(\n                \"k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAviPGBk4ZB64UfSqWyAicdR7lodhytae+EYRQVtKDhM+1mXjEqRtP/pDT3sBhazkmA48n2k5NJUyMEoO8nc2r6sUA+/Dom5jRBZp6qDKJOwjJ5R/OpHamlRG+YRJQqR / tqEgSiJWG7h7efGYWmh4URhFM9k9+rmG/CwCgwx7Et+c8OMlngaLl04/bPmfpjdEyLWyNimk761CX6KymzYiRDNz1MOJOJ7OzFaS4PFbVLn0m5mf0HVNtBpPwWuCNvaFVflUYxEyblbB6h/oWOPGbzoSgtRA47SHV53SwZjIsVpbq4LxUW9IxAEwYzGcSgZ4n5Q8X8TndowsDUzoccPFGhdwIDAQAB\",\n                text);\n    }\n\n    @Test\n    public void testSoaLookup() throws Exception {\n        DnsMessage m = getMessageFromResource(\"oracle-soa\");\n        assertFalse(m.authoritativeAnswer);\n        List<Record<? extends Data>> answers = m.answerSection;\n        assertEquals(1, answers.size());\n        Record<? extends Data> answer = answers.get(0);\n        assertTrue(answer.getPayload() instanceof SOA);\n        assertEquals(TYPE.SOA, answer.getPayload().getType());\n        SOA soa = (SOA) answer.getPayload();\n        assertCsEquals(\"orcldns1.ultradns.com\", soa.mname);\n        assertCsEquals(\"hostmaster⧷@oracle.com\", soa.rname);\n        assertEquals(2015032404L, soa.serial);\n        assertEquals(10800, soa.refresh);\n        assertEquals(3600, soa.retry);\n        assertEquals(1209600, soa.expire);\n        assertEquals(900L, soa.minimum);\n    }\n\n    @Test\n    public void testComNsLookup() throws Exception {\n        DnsMessage m = getMessageFromResource(\"com-ns\");\n        assertFalse(m.authoritativeAnswer);\n        assertFalse(m.authenticData);\n        assertTrue(m.recursionDesired);\n        assertTrue(m.recursionAvailable);\n        assertTrue(m.qr);\n        List<Record<? extends Data>> answers = m.answerSection;\n        assertEquals(13, answers.size());\n        for (Record<? extends Data> answer : answers) {\n            assertCsEquals(\"com\", answer.name);\n            assertEquals(Record.CLASS.IN, answer.clazz);\n            assertEquals(TYPE.NS, answer.type);\n            assertEquals(112028, answer.ttl);\n            assertTrue(((NS) answer.payloadData).target.ace.endsWith(\".gtld-servers.net\"));\n        }\n        List<Record<? extends Data>> arr = m.additionalSection;\n        assertEquals(1, arr.size());\n        Edns edns = Edns.fromRecord(arr.get(0));\n        assertEquals(4096, edns.udpPayloadSize);\n        assertEquals(0, edns.version);\n    }\n\n    @Test\n    public void testRootDnskeyLookup() throws Exception {\n        DnsMessage m = getMessageFromResource(\"root-dnskey\");\n        assertFalse(m.authoritativeAnswer);\n        assertTrue(m.recursionDesired);\n        assertTrue(m.recursionAvailable);\n        List<Record<? extends Data>> answers = m.answerSection;\n        assertEquals(3, answers.size());\n        for (int i = 0; i < answers.size(); i++) {\n            Record<? extends Data> answer = answers.get(i);\n            assertCsEquals(\".\", answer.name);\n            assertEquals(19593, answer.getTtl());\n            assertEquals(TYPE.DNSKEY, answer.type);\n            assertEquals(TYPE.DNSKEY, answer.getPayload().getType());\n            DNSKEY dnskey = (DNSKEY) answer.getPayload();\n            assertEquals(3, dnskey.protocol);\n            assertEquals(SignatureAlgorithm.RSASHA256, dnskey.algorithm);\n            assertTrue((dnskey.flags & DNSKEY.FLAG_ZONE) > 0);\n            assertEquals(dnskey.getKeyTag(), dnskey.getKeyTag());\n            switch (i) {\n                case 0:\n                    assertTrue((dnskey.flags & DNSKEY.FLAG_SECURE_ENTRY_POINT) > 0);\n                    assertEquals(260, dnskey.getKeyLength());\n                    assertEquals(19036, dnskey.getKeyTag());\n                    break;\n                case 1:\n                    assertEquals(DNSKEY.FLAG_ZONE, dnskey.flags);\n                    assertEquals(132, dnskey.getKeyLength());\n                    assertEquals(48613, dnskey.getKeyTag());\n                    break;\n                case 2:\n                    assertEquals(DNSKEY.FLAG_ZONE, dnskey.flags);\n                    assertEquals(132, dnskey.getKeyLength());\n                    assertEquals(1518, dnskey.getKeyTag());\n                    break;\n            }\n        }\n        List<Record<? extends Data>> arr = m.additionalSection;\n        assertEquals(1, arr.size());\n        Record<? extends Data> opt = arr.get(0);\n        Edns edns = Edns.fromRecord(opt);\n        assertEquals(512, edns.udpPayloadSize);\n        assertEquals(0, edns.version);\n    }\n\n    @Test\n    public void testComDsAndRrsigLookup() throws Exception {\n        DnsMessage m = getMessageFromResource(\"com-ds-rrsig\");\n        assertFalse(m.authoritativeAnswer);\n        assertTrue(m.recursionDesired);\n        assertTrue(m.recursionAvailable);\n        List<Record<? extends Data>> answers = m.answerSection;\n        assertEquals(2, answers.size());\n\n        assertEquals(TYPE.DS, answers.get(0).type);\n        assertEquals(TYPE.DS, answers.get(0).payloadData.getType());\n        DS ds = (DS) answers.get(0).payloadData;\n        assertEquals(30909, ds.keyTag);\n        assertEquals(SignatureAlgorithm.RSASHA256, ds.algorithm);\n        assertEquals(DigestAlgorithm.SHA256, ds.digestType);\n        assertCsEquals(\"E2D3C916F6DEEAC73294E8268FB5885044A833FC5459588F4A9184CFC41A5766\",\n                ds.getDigestHex());\n\n        assertEquals(TYPE.RRSIG, answers.get(1).type);\n        assertEquals(TYPE.RRSIG, answers.get(1).payloadData.getType());\n        RRSIG rrsig = (RRSIG) answers.get(1).payloadData;\n        assertEquals(TYPE.DS, rrsig.typeCovered);\n        assertEquals(SignatureAlgorithm.RSASHA256, rrsig.algorithm);\n        assertEquals(1, rrsig.labels);\n        assertEquals(86400, rrsig.originalTtl);\n        SimpleDateFormat dateFormat = new SimpleDateFormat(\"yyyyMMddHHmmss\");\n        dateFormat.setTimeZone(TimeZone.getTimeZone(\"UTC\"));\n        assertCsEquals(\"20150629170000\", dateFormat.format(rrsig.signatureExpiration));\n        assertCsEquals(\"20150619160000\", dateFormat.format(rrsig.signatureInception));\n        assertEquals(48613, rrsig.keyTag);\n        assertCsEquals(\".\", rrsig.signerName);\n        assertEquals(128, rrsig.getSignatureLength());\n\n        List<Record<? extends Data>> arr = m.additionalSection;\n        assertEquals(1, arr.size());\n        assertEquals(TYPE.OPT, arr.get(0).getPayload().getType());\n        Record<? extends Data> opt = arr.get(0);\n        Edns edns = Edns.fromRecord(opt);\n        assertEquals(512, edns.udpPayloadSize);\n        assertEquals(0, edns.version);\n        assertTrue(edns.dnssecOk);\n    }\n\n    @Test\n    public void testExampleNsecLookup() throws Exception {\n        DnsMessage m = getMessageFromResource(\"example-nsec\");\n        List<Record<? extends Data>> answers = m.answerSection;\n        assertEquals(1, answers.size());\n        assertEquals(TYPE.NSEC, answers.get(0).type);\n        assertEquals(TYPE.NSEC, answers.get(0).payloadData.getType());\n        NSEC nsec = (NSEC) answers.get(0).getPayload();\n        assertCsEquals(\"www.example.com\", nsec.next);\n        ArrayList<TYPE> types = new ArrayList<>(Arrays.asList(\n                TYPE.A, TYPE.NS, TYPE.SOA, TYPE.TXT,\n                TYPE.AAAA, TYPE.RRSIG, TYPE.NSEC, TYPE.DNSKEY));\n\n        for (TYPE type : nsec.types) {\n            assertTrue(types.remove(type));\n        }\n\n        assertTrue(types.isEmpty());\n    }\n\n    @Test\n    public void testComNsec3Lookup() throws Exception {\n        DnsMessage m = getMessageFromResource(\"com-nsec3\");\n        assertEquals(0, m.answerSection.size());\n        List<Record<? extends Data>> records = m.authoritySection;\n        assertEquals(8, records.size());\n        for (Record<? extends Data> record : records) {\n            if (record.type == TYPE.NSEC3) {\n                assertEquals(TYPE.NSEC3, record.getPayload().getType());\n                NSEC3 nsec3 = (NSEC3) record.payloadData;\n                assertEquals(HashAlgorithm.SHA1, nsec3.hashAlgorithm);\n                assertEquals(1, nsec3.flags);\n                assertEquals(0, nsec3.iterations);\n                assertEquals(0, nsec3.getSaltLength());\n                switch (record.name.ace) {\n                    case \"CK0POJMG874LJREF7EFN8430QVIT8BSM.com\":\n                        assertCsEquals(\"CK0QFMDQRCSRU0651QLVA1JQB21IF7UR\", nsec3.getNextHashedBase32());\n                        assertArrayContentEquals(new TYPE[] {TYPE.NS, TYPE.SOA, TYPE.RRSIG, TYPE.DNSKEY, TYPE.NSEC3PARAM}, nsec3.types);\n                        break;\n                    case \"V2I33UBTHNVNSP9NS85CURCLSTFPTE24.com\":\n                        assertCsEquals(\"V2I4KPUS7NGDML5EEJU3MVHO26GKB6PA\", nsec3.getNextHashedBase32());\n                        assertArrayContentEquals(new TYPE[] {TYPE.NS, TYPE.DS, TYPE.RRSIG}, nsec3.types);\n                        break;\n                    case \"3RL20VCNK6KV8OT9TDIJPI0JU1SS6ONS.com\":\n                        assertCsEquals(\"3RL3UFVFRUE94PV5888AIC2TPS0JA9V2\", nsec3.getNextHashedBase32());\n                        assertArrayContentEquals(new TYPE[] {TYPE.NS, TYPE.DS, TYPE.RRSIG}, nsec3.types);\n                        break;\n                }\n            }\n        }\n    }\n\n    @Test\n    public void testMessageSelfQuestionReconstruction() throws Exception {\n        DnsMessage.Builder dmb = DnsMessage.builder();\n        dmb.setQuestion(new Question(\"www.example.com\", TYPE.A));\n        dmb.setRecursionDesired(true);\n        dmb.setId(42);\n        dmb.setQrFlag(true);\n        DnsMessage message = new DnsMessage(dmb.build().toArray());\n\n        assertEquals(1, message.questions.size());\n        assertEquals(0, message.answerSection.size());\n        assertEquals(0, message.additionalSection.size());\n        assertEquals(0, message.authoritySection.size());\n        assertTrue(message.recursionDesired);\n        assertTrue(message.qr);\n        assertEquals(42, message.id);\n        assertCsEquals(\"www.example.com\", message.questions.get(0).name);\n        assertEquals(TYPE.A, message.questions.get(0).type);\n    }\n\n    @Test\n    public void testMessageSelfEasyAnswersReconstruction() throws Exception {\n        DnsMessage.Builder dmb = DnsMessage.builder();\n        dmb.addAnswer(record(\"www.example.com\", a(\"127.0.0.1\")))\n           .addAnswer(record(\"www.example.com\", ns(\"example.com\")));\n        dmb.setRecursionAvailable(true);\n        dmb.setCheckingDisabled(true);\n        dmb.setQrFlag(false);\n        dmb.setId(43);\n        DnsMessage message = new DnsMessage(dmb.build().toArray());\n\n        assertEquals(0, message.questions.size());\n        assertEquals(2, message.answerSection.size());\n        assertEquals(0, message.additionalSection.size());\n        assertEquals(0, message.authoritySection.size());\n        assertTrue(message.recursionAvailable);\n        assertFalse(message.authenticData);\n        assertTrue(message.checkingDisabled);\n        assertFalse(message.qr);\n        assertEquals(43, message.id);\n        assertCsEquals(\"www.example.com\", message.answerSection.get(0).name);\n        assertEquals(TYPE.A, message.answerSection.get(0).type);\n        assertCsEquals(\"127.0.0.1\", message.answerSection.get(0).payloadData.toString());\n        assertCsEquals(\"www.example.com\", message.answerSection.get(1).name);\n        assertEquals(TYPE.NS, message.answerSection.get(1).type);\n        assertCsEquals(\"example.com.\", message.answerSection.get(1).payloadData.toString());\n    }\n\n    @Test\n    public void testMessageSelfComplexReconstruction() throws Exception {\n        DnsMessage.Builder dmb = DnsMessage.builder();\n        dmb.addQuestion(new Question(\"www.example.com\", TYPE.NS));\n        dmb.addAnswer(record(\"www.example.com\", ns(\"ns.example.com\")));\n        dmb.addAdditionalResourceRecord(record(\"ns.example.com\", a(\"127.0.0.1\")));\n        dmb.addNameserverRecords(record(\"ns.example.com\", aaaa(\"2001::1\")));\n        dmb.setOpcode(DnsMessage.OPCODE.QUERY);\n        dmb.setResponseCode(DnsMessage.RESPONSE_CODE.NO_ERROR);\n        dmb.setRecursionAvailable(false);\n        dmb.setAuthoritativeAnswer(true);\n        dmb.setAuthenticData(true);\n        dmb.setQrFlag(false);\n        dmb.setId(43);\n        DnsMessage message = new DnsMessage(dmb.build().toArray());\n\n        assertEquals(1, message.questions.size());\n        assertEquals(1, message.answerSection.size());\n        assertEquals(1, message.additionalSection.size());\n        assertEquals(1, message.authoritySection.size());\n\n        assertFalse(message.recursionAvailable);\n        assertTrue(message.authenticData);\n        assertFalse(message.checkingDisabled);\n        assertFalse(message.qr);\n        assertTrue(message.authoritativeAnswer);\n        assertEquals(43, message.id);\n        assertEquals(DnsMessage.OPCODE.QUERY, message.opcode);\n        assertEquals(DnsMessage.RESPONSE_CODE.NO_ERROR, message.responseCode);\n\n        assertCsEquals(\"www.example.com\", message.questions.get(0).name);\n        assertEquals(TYPE.NS, message.questions.get(0).type);\n\n        assertCsEquals(\"www.example.com\", message.answerSection.get(0).name);\n        assertEquals(TYPE.NS, message.answerSection.get(0).type);\n        assertCsEquals(\"ns.example.com.\", message.answerSection.get(0).payloadData.toString());\n\n        assertCsEquals(\"ns.example.com\", message.additionalSection.get(0).name);\n        assertEquals(TYPE.A, message.additionalSection.get(0).type);\n        assertCsEquals(\"127.0.0.1\", message.additionalSection.get(0).payloadData.toString());\n\n        assertCsEquals(\"ns.example.com\", message.authoritySection.get(0).name);\n        assertEquals(TYPE.AAAA, message.authoritySection.get(0).type);\n        assertCsEquals(\"2001:0:0:0:0:0:0:1\", message.authoritySection.get(0).payloadData.toString());\n    }\n\n    @Test\n    public void testMessageSelfTruncatedReconstruction() throws Exception {\n        DnsMessage.Builder dmb = DnsMessage.builder();\n        dmb.setTruncated(true);\n        dmb.setQrFlag(false);\n        dmb.setId(44);\n        DnsMessage message = new DnsMessage(dmb.build().toArray());\n        assertEquals(44, message.id);\n        assertFalse(message.qr);\n        assertTrue(message.truncated);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testMessageSelfOptRecordReconstructione() throws Exception {\n        DnsMessage.Builder m = DnsMessage.builder();\n        m.addAdditionalResourceRecord(record(\"www.example.com\", a(\"127.0.0.1\")));\n        m.getEdnsBuilder().setUdpPayloadSize(512).setDnssecOk();\n        DnsMessage message = new DnsMessage(m.build().toArray());\n\n        assertEquals(2, message.additionalSection.size());\n        assertCsEquals(\"www.example.com\", message.additionalSection.get(0).name);\n        assertEquals(TYPE.A, message.additionalSection.get(0).type);\n        assertCsEquals(\"127.0.0.1\", message.additionalSection.get(0).payloadData.toString());\n        assertCsEquals(\"EDNS: version: 0, flags: do; udp: 512\", new Edns((Record<OPT>) message.additionalSection.get(1)).toString());\n    }\n\n    @Test\n    public void testEmptyMessageToString() throws Exception {\n        // toString() should never throw an exception or be null\n        DnsMessage message = DnsMessage.builder().build();\n        assertNotNull(message.toString());\n    }\n\n    @Test\n    public void testFilledMessageToString() throws Exception {\n        // toString() should never throw an exception or be null\n        DnsMessage.Builder message = DnsMessage.builder();\n        message.setOpcode(DnsMessage.OPCODE.QUERY);\n        message.setResponseCode(DnsMessage.RESPONSE_CODE.NO_ERROR);\n        message.setId(1337);\n        message.setAuthoritativeAnswer(true);\n        message.addQuestion(new Question(\"www.example.com\", TYPE.A));\n        message.addAnswer(record(\"www.example.com\", a(\"127.0.0.1\")));\n        message.addNameserverRecords(record(\"example.com\", ns(\"ns.example.com\")));\n        message.addAdditionalResourceRecord(record(\"ns.example.com\", a(\"127.0.0.1\")));\n        message.getEdnsBuilder().setUdpPayloadSize(512);\n        assertNotNull(message.build().toString());\n    }\n\n    @Test\n    public void testEmptyMessageTerminalOutput() throws Exception {\n        // asTerminalOutput() follows a certain design, however it might change in the future.\n        // Once asTerminalOutput() is changed, it might be required to update this test routine.\n        DnsMessage.Builder message = DnsMessage.builder();\n        message.setOpcode(DnsMessage.OPCODE.QUERY);\n        message.setResponseCode(DnsMessage.RESPONSE_CODE.NO_ERROR);\n        message.setId(1337);\n        assertNotNull(message.build().asTerminalOutput());\n    }\n\n    @Test\n    public void testFilledMessageTerminalOutput() throws Exception {\n        // asTerminalOutput() follows a certain design, however it might change in the future.\n        // Once asTerminalOutput() is changed, it might be required to update this test routine.\n        DnsMessage.Builder message = DnsMessage.builder();\n        message.setOpcode(DnsMessage.OPCODE.QUERY);\n        message.setResponseCode(DnsMessage.RESPONSE_CODE.NO_ERROR);\n        message.setId(1337);\n        message.setAuthoritativeAnswer(true);\n        message.addQuestion(new Question(\"www.example.com\", TYPE.A));\n        message.addAnswer(record(\"www.example.com\", a(\"127.0.0.1\")));\n        message.addNameserverRecords(record(\"example.com\", ns(\"ns.example.com\")));\n        message.addAdditionalResourceRecord(record(\"ns.example.com\", a(\"127.0.0.1\")));\n        message.getEdnsBuilder().setUdpPayloadSize(512);\n        assertNotNull(message.build().asTerminalOutput());\n    }\n\n    public static Record<Data> record(String name, long ttl, Data data) {\n        return new Record<>(name, data.getType(), CLASS.IN, ttl, data, false);\n    }\n\n    public static Record<Data> record(DnsName name, long ttl, Data data) {\n        return new Record<>(name, data.getType(), CLASS.IN, ttl, data, false);\n    }\n\n    public static Record<Data> record(String name, Data data) {\n        return record(name, 3600, data);\n    }\n\n    public static A a(CharSequence ipv4CharSequence) {\n        return new A(ipv4CharSequence);\n    }\n\n    public static NS ns(String name) {\n        return ns(DnsName.from(name));\n    }\n\n    public static NS ns(DnsName name) {\n        return new NS(name);\n    }\n\n    public static AAAA aaaa(CharSequence ipv6CharSequence) {\n        return new AAAA(ipv6CharSequence);\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/test/java/org/minidns/dnsname/DnsNameTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnsname;\n\nimport static org.minidns.Assert.assertCsEquals;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.DataInputStream;\nimport java.io.IOException;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.minidns.dnslabel.DnsLabel;\n\npublic class DnsNameTest {\n\n    @Test\n    public void sizeTest() {\n        assertEquals(1, DnsName.from(\"\").size());\n        assertEquals(13, DnsName.from(\"example.com\").size());\n        assertEquals(16, DnsName.from(\"dömäin\").size());\n        assertEquals(24, DnsName.from(\"dömäin.example\").size());\n    }\n\n    @Test\n    public void toByteArrayTest() {\n        assertArrayEquals(new byte[] {0}, DnsName.from(\"\").getBytes());\n        assertArrayEquals(new byte[] {7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0}, DnsName.from(\"example\").getBytes());\n        assertArrayEquals(new byte[] {7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0}, DnsName.from(\"example.com\").getBytes());\n        assertArrayEquals(new byte[] {14, 'x', 'n', '-', '-', 'd', 'm', 'i', 'n', '-', 'm', 'o', 'a', '0', 'i', 0}, DnsName.from(\"dömäin\").getBytes());\n    }\n\n    @Test\n    public void parseTest() throws IOException {\n        byte[] test = new byte[] {7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0};\n        assertCsEquals(\"example\", DnsName.parse(new DataInputStream(new ByteArrayInputStream(test)), test));\n        test = new byte[] {7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0};\n        assertCsEquals(\"example.com\", DnsName.parse(new DataInputStream(new ByteArrayInputStream(test)), test));\n    }\n\n    @Test\n    public void parseLabelWillNullByteTest() throws IOException {\n        byte[] test = new byte[] {6, 'v', 'i', 'c', 't', 'i', 'm', 4, 'o', 'r', 'g', 0, 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'o', 'r', 'g', 0};\n\n        DnsName dnsName = parse(test);\n        assertCsEquals(\"victim.org␀.example.org\", dnsName);\n\n        DnsLabel orgWithoutNullByte = dnsName.getLabel(0);\n        assertArrayEquals(new char[] { 'o', 'r', 'g'}, orgWithoutNullByte.getRawLabel().toCharArray());\n\n        DnsLabel orgWithNullByte = dnsName.getLabel(2);\n        assertArrayEquals(new char[] { 'o', 'r', 'g', '\\0'}, orgWithNullByte.getRawLabel().toCharArray());\n        assertArrayEquals(new char[] { 'o', 'r', 'g', '␀'}, orgWithNullByte.toString().toCharArray());\n    }\n\n    private static DnsName parse(byte[] bytes) throws IOException {\n        return DnsName.parse(new DataInputStream(new ByteArrayInputStream(bytes)), bytes);\n    }\n\n    @Test\n    public void constructInvalid() throws IOException {\n        DnsName.from(\"foo\\\\0nullbar\");\n    }\n\n    @Test\n    public void equalsTest() {\n        assertEquals(DnsName.from(\"\"), DnsName.from(\".\"));\n    }\n\n    @Test\n    public void testStripToParts() {\n        assertCsEquals(DnsName.from(\"www.example.com\"), DnsName.from(\"www.example.com\").stripToLabels(3));\n        assertCsEquals(DnsName.from(\"example.com\"), DnsName.from(\"www.example.com\").stripToLabels(2));\n        assertCsEquals(DnsName.from(\"com\"), DnsName.from(\"www.example.com\").stripToLabels(1));\n        assertCsEquals(DnsName.from(\".\"), DnsName.from(\"www.example.com\").stripToLabels(0));\n    }\n\n    @Test\n    public void testStripToPartsIllegal() {\n        assertThrows(IllegalArgumentException.class, () ->\n            DnsName.from(\"\").stripToLabels(1)\n        );\n    }\n\n    @Test\n    public void testStripToPartsIllegalLong() {\n        assertThrows(IllegalArgumentException.class, () ->\n            DnsName.from(\"example.com\").stripToLabels(3)\n       );\n    }\n\n    @Test\n    public void testConcact() {\n        String leftString = \"foo.bar.de\";\n        String rightString = \"example.org\";\n        DnsName left = DnsName.from(leftString);\n        DnsName right = DnsName.from(rightString);\n\n        DnsName concated = DnsName.from(left, right);\n        DnsName expected = DnsName.from(leftString + '.' + rightString);\n        assertEquals(expected, concated);\n    }\n\n    @Test\n    public void testFromVarargs() {\n        String leftString = \"leftmost.left\";\n        String middleString = \"leftMiddle.middle.rightMiddle\";\n        String rightString = \"right.rightMost\";\n        DnsName left = DnsName.from(leftString);\n        DnsName middle = DnsName.from(middleString);\n        DnsName right = DnsName.from(rightString);\n\n        DnsName name = DnsName.from(left, middle, right);\n\n        String completeString = leftString + '.' + middleString + '.' + rightString;\n        assertEquals(name.getRawAce(), completeString);\n\n        DnsName expected = DnsName.from(completeString);\n        assertEquals(name, expected);\n    }\n\n    @Test\n    public void caseInsenstiveCompare() {\n        DnsName lowercase = DnsName.from(\"cs.fau.de\");\n        DnsName uppercase = DnsName.from(\"CS.fau.de\");\n\n        assertEquals(lowercase, uppercase);\n    }\n\n    @Test\n    public void rawFieldsKeepCase() {\n        String mixedCaseDnsName = \"UP.low.UP.low.UP\";\n        DnsName mixedCase = DnsName.from(mixedCaseDnsName);\n\n        assertEquals(mixedCaseDnsName, mixedCase.getRawAce());\n    }\n\n    @Test\n    public void getLabelsTest() {\n        final String tldLabelString = \"tld\";\n        final String secondLevelString = \"second-level-domain\";\n        final String thirdLevelString = \"third-level-domain\";\n        final String dnsNameString = thirdLevelString + '.' + secondLevelString + '.' + tldLabelString;\n        final DnsName dnsName = DnsName.from(dnsNameString);\n\n        DnsLabel[] labels = dnsName.getLabels();\n        assertEquals(tldLabelString, labels[0].label);\n        assertEquals(secondLevelString, labels[1].label);\n        assertEquals(thirdLevelString, labels[2].label);\n    }\n\n    @Test\n    public void trailingDotDnsNameFromTest() {\n        final String trailingDotDnsName = \"foo.bar.\";\n        DnsName dnsName = DnsName.from(trailingDotDnsName);\n        assertEquals(\"foo.bar\", dnsName.ace);\n    }\n\n    @Test\n    public void fromWithChild() {\n        DnsName parent = DnsName.from(\"example.org\");\n        String child = \"foo\";\n        DnsName dnsName = DnsName.from(child, parent);\n        assertEquals(\"foo.example.org\", dnsName.ace);\n    }\n\n    @Test\n    public void fromWithChildAndGrandchild() {\n        DnsName parent = DnsName.from(\"example.org\");\n        DnsLabel grandChild = DnsLabel.from(\"foo\");\n        DnsLabel child = DnsLabel.from(\"bar\");\n        DnsName dnsName = DnsName.from(grandChild, child, parent);\n        assertEquals(\"foo.bar.example.org\", dnsName.ace);\n    }\n\n    @Test\n    public void getHostpartLabel() {\n        DnsName dnsName = DnsName.from(\"foo.example.org\");\n        DnsLabel hostpart = dnsName.getHostpartLabel();\n        assertEquals(\"foo\", hostpart.toString());\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/test/java/org/minidns/record/RecordsTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport org.minidns.constants.DnssecConstants.DigestAlgorithm;\nimport org.minidns.constants.DnssecConstants.SignatureAlgorithm;\nimport org.minidns.record.NSEC3.HashAlgorithm;\nimport org.minidns.record.Record.TYPE;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.DataInputStream;\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.Date;\nimport java.util.List;\n\nimport static org.minidns.Assert.assertCsEquals;\nimport static org.minidns.Assert.assertArrayContentEquals;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n/**\n * These are some tests for all records.\n *\n * The tests main purpose is to test if the output of toByteArray() is parsed into it's original value.\n *\n * Additionally, toString() is tested to be RFC compliant.\n */\npublic class RecordsTest {\n    @Test\n    public void testARecord() throws Exception {\n        A a = new A(new byte[] {127, 0, 0, 1});\n        assertEquals(\"127.0.0.1\", a.toString());\n        assertEquals(TYPE.A, a.getType());\n        byte[] ab = a.toByteArray();\n        a = A.parse(new DataInputStream(new ByteArrayInputStream(ab)));\n        assertArrayEquals(new byte[] {127, 0, 0, 1}, a.getIp());\n    }\n\n    @Test\n    public void testARecordInvalidIp() throws Exception {\n        assertThrows(IllegalArgumentException.class, () -> \n            new A(new byte[42])\n        );\n    }\n\n    @Test\n    public void testAAAARecord() throws Exception {\n        AAAA aaaa = new AAAA(new byte[] {0x20, 0x01, 0x0d, (byte) 0xb8, (byte) 0x85, (byte) 0xa3, 0x08, (byte) 0xd3, 0x13, 0x19, (byte) 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x44});\n        // Note: there are multiple valid representations of the IPv6 address due to optional reductions.\n        assertEquals(\"2001:db8:85a3:8d3:1319:8a2e:370:7344\", aaaa.toString());\n        assertEquals(TYPE.AAAA, aaaa.getType());\n        byte[] aaaab  = aaaa.toByteArray();\n        aaaa = AAAA.parse(new DataInputStream(new ByteArrayInputStream(aaaab)));\n        assertArrayEquals(new byte[] {0x20, 0x01, 0x0d, (byte) 0xb8, (byte) 0x85, (byte) 0xa3, 0x08, (byte) 0xd3, 0x13, 0x19, (byte) 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x44}, aaaa.getIp());\n    }\n\n    @Test\n    public void testAAAARecordInvalidIp() throws Exception {\n        assertThrows(IllegalArgumentException.class, () -> \n            new AAAA(new byte[42])\n        );\n    }\n\n    @Test\n    public void testCnameRecord() throws Exception {\n        CNAME cname = new CNAME(\"www.example.com\");\n        assertEquals(\"www.example.com.\", cname.toString());\n        assertEquals(TYPE.CNAME, cname.getType());\n        byte[] cnameb = cname.toByteArray();\n        cname = CNAME.parse(new DataInputStream(new ByteArrayInputStream(cnameb)), cnameb);\n        assertCsEquals(\"www.example.com\", cname.target);\n    }\n\n    @Test\n    public void testDlvRecord() throws Exception {\n        DLV dlv = new DLV(42, (byte) 8, (byte) 2, new byte[] {0x13, 0x37});\n        assertEquals(\"42 RSASHA256 SHA256 1337\", dlv.toString());\n        assertEquals(TYPE.DLV, dlv.getType());\n        byte[] dlvb = dlv.toByteArray();\n        dlv = DLV.parse(new DataInputStream(new ByteArrayInputStream(dlvb)), dlvb.length);\n        assertEquals(42, dlv.keyTag);\n        assertEquals(SignatureAlgorithm.RSASHA256, dlv.algorithm);\n        assertEquals(DigestAlgorithm.SHA256, dlv.digestType);\n        assertArrayEquals(new byte[] {0x13, 0x37}, dlv.digest);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Test\n    public void testDnskeyRecord() throws Exception {\n        DNSKEY dnskey = new DNSKEY(DNSKEY.FLAG_ZONE, (byte) 3, (byte) 1, new byte[] {42});\n        // TODO: Compare with real Base64 once done\n        assertEquals(\"256 3 RSAMD5 \" + dnskey.getKeyBase64(), dnskey.toString());\n        assertEquals(TYPE.DNSKEY, dnskey.getType());\n        byte[] dnskeyb = dnskey.toByteArray();\n        dnskey = DNSKEY.parse(new DataInputStream(new ByteArrayInputStream(dnskeyb)), dnskeyb.length);\n        assertEquals(256, dnskey.flags);\n        assertEquals(3, dnskey.protocol);\n        assertEquals(SignatureAlgorithm.RSAMD5, dnskey.algorithm);\n        assertArrayEquals(new byte[] {42}, dnskey.getKey());\n    }\n\n    @Test\n    public void testDnskeyRecordWithUnknownSignatureAlgorithm() throws Exception {\n        byte unknownSignatureAlgorithm = (byte) 255;\n        DNSKEY dnskey = new DNSKEY(DNSKEY.FLAG_ZONE, DNSKEY.PROTOCOL_RFC4034, unknownSignatureAlgorithm, new byte[] {42});\n        assertEquals(unknownSignatureAlgorithm, dnskey.algorithmByte);\n        assertNull(dnskey.algorithm);\n        byte[] dnskeyb = dnskey.toByteArray();\n        dnskey = DNSKEY.parse(new DataInputStream(new ByteArrayInputStream(dnskeyb)), dnskeyb.length);\n        assertEquals(unknownSignatureAlgorithm, dnskey.algorithmByte);\n        assertNull(dnskey.algorithm);\n    }\n\n    @Test\n    public void testDsRecord() throws Exception {\n        DS ds = new DS(42, (byte) 8, (byte) 2, new byte[] {0x13, 0x37});\n        assertEquals(\"42 RSASHA256 SHA256 1337\", ds.toString());\n        assertEquals(TYPE.DS, ds.getType());\n        byte[] dsb = ds.toByteArray();\n        ds = DS.parse(new DataInputStream(new ByteArrayInputStream(dsb)), dsb.length);\n        assertEquals(42, ds.keyTag);\n        assertEquals(SignatureAlgorithm.RSASHA256, ds.algorithm);\n        assertEquals(DigestAlgorithm.SHA256, ds.digestType);\n        assertArrayEquals(new byte[] {0x13, 0x37}, ds.digest);\n    }\n\n    @Test\n    public void testMxRecord() throws Exception {\n        MX mx = new MX(10, \"mx.example.com\");\n        assertEquals(\"10 mx.example.com.\", mx.toString());\n        assertEquals(TYPE.MX, mx.getType());\n        byte[] mxb = mx.toByteArray();\n        mx = MX.parse(new DataInputStream(new ByteArrayInputStream(mxb)), mxb);\n        assertEquals(10, mx.priority);\n        assertCsEquals(\"mx.example.com\", mx.target);\n    }\n\n    @Test\n    public void testNsecRecord() throws Exception {\n        NSEC nsec = new NSEC(\"example.com\", new TYPE[] {TYPE.A, TYPE.RRSIG, TYPE.DLV});\n        assertEquals(\"example.com. A RRSIG DLV\", nsec.toString());\n        assertEquals(TYPE.NSEC, nsec.getType());\n        byte[] nsecb = nsec.toByteArray();\n        nsec = NSEC.parse(new DataInputStream(new ByteArrayInputStream(nsecb)), nsecb, nsecb.length);\n        assertCsEquals(\"example.com\", nsec.next);\n        assertArrayContentEquals(new TYPE[] {TYPE.A, TYPE.RRSIG, TYPE.DLV}, nsec.types);\n    }\n\n    @Test\n    public void testNsecTypeBitmapEmpty() throws IOException {\n        List<TYPE> emptyTypes = Collections.emptyList();\n        assertEquals(0, NSEC.readTypeBitMap(NSEC.createTypeBitMap(emptyTypes)).size());\n    }\n\n    @Test\n    public void testNsec3Record() throws Exception {\n        NSEC3 nsec3 = new NSEC3((byte) 1, (byte) 1, 1, new byte[] {0x13, 0x37}, new byte[] {0x42, 0x42, 0x42, 0x42, 0x42}, new TYPE[] {TYPE.A});\n        assertEquals(\"SHA1 1 1 1337 89144GI2 A\", nsec3.toString());\n        assertEquals(TYPE.NSEC3, nsec3.getType());\n        byte[] nsec3b = nsec3.toByteArray();\n        nsec3 = NSEC3.parse(new DataInputStream(new ByteArrayInputStream(nsec3b)), nsec3b.length);\n        assertEquals(HashAlgorithm.SHA1, nsec3.hashAlgorithm);\n        assertEquals(1, nsec3.flags);\n        assertEquals(1, nsec3.iterations);\n        assertArrayEquals(new byte[] {0x13, 0x37}, nsec3.getSalt());\n        assertArrayEquals(new byte[] {0x42, 0x42, 0x42, 0x42, 0x42}, nsec3.getNextHashed());\n        assertArrayContentEquals(new TYPE[] {TYPE.A}, nsec3.types);\n\n        assertEquals(\"SHA1 1 1 - \", new NSEC3((byte) 1, (byte) 1, 1, new byte[0], new byte[0], new TYPE[0]).toString());\n    }\n\n    @Test\n    public void testNsec3ParamRecord() throws Exception {\n        NSEC3PARAM nsec3param = new NSEC3PARAM((byte) 1, (byte) 1, 1, new byte[0]);\n        assertEquals(\"SHA1 1 1 -\", nsec3param.toString());\n        assertEquals(TYPE.NSEC3PARAM, nsec3param.getType());\n        byte[] nsec3paramb = nsec3param.toByteArray();\n        nsec3param = NSEC3PARAM.parse(new DataInputStream(new ByteArrayInputStream(nsec3paramb)));\n        assertEquals(\"SHA-1\", nsec3param.hashAlgorithm.description);\n        assertEquals(1, nsec3param.hashAlgorithmByte);\n        assertEquals(1, nsec3param.flags);\n        assertEquals(1, nsec3param.iterations);\n        assertEquals(0, nsec3param.getSaltLength());\n\n        assertEquals(\"SHA1 1 1 1337\", new NSEC3PARAM((byte) 1, (byte) 1, 1, new byte[] {0x13, 0x37}).toString());\n    }\n\n    @Test\n    public void testOpenpgpkeyRecord() throws Exception {\n        OPENPGPKEY openpgpkey = new OPENPGPKEY(new byte[] {0x13, 0x37});\n        assertEquals(\"Ezc=\", openpgpkey.toString());\n        assertEquals(TYPE.OPENPGPKEY, openpgpkey.getType());\n        byte[] openpgpkeyb = openpgpkey.toByteArray();\n        openpgpkey = OPENPGPKEY.parse(new DataInputStream(new ByteArrayInputStream(openpgpkeyb)), openpgpkeyb.length);\n        assertArrayEquals(new byte[] {0x13, 0x37}, openpgpkey.getPublicKeyPacket());\n    }\n\n    @Test\n    public void testPtrRecord() throws Exception {\n        PTR ptr = new PTR(\"ptr.example.com\");\n        assertEquals(\"ptr.example.com.\", ptr.toString());\n        assertEquals(TYPE.PTR, ptr.getType());\n        byte[] ptrb = ptr.toByteArray();\n        ptr = PTR.parse(new DataInputStream(new ByteArrayInputStream(ptrb)), ptrb);\n        assertCsEquals(\"ptr.example.com\", ptr.target);\n    }\n\n    @Test\n    @SuppressWarnings(\"JavaUtilDate\")\n    public void testRrsigRecord() throws Exception {\n        RRSIG rrsig = new RRSIG(TYPE.A, (byte) 8, (byte) 2, 3600, new Date(1000), new Date(0), 42, \"example.com\", new byte[] {42});\n        // TODO: Compare with real Base64 once done\n        assertEquals(\"A RSASHA256 2 3600 19700101000001 19700101000000 42 example.com. \" + rrsig.getSignatureBase64(), rrsig.toString());\n        assertEquals(TYPE.RRSIG, rrsig.getType());\n        byte[] rrsigb = rrsig.toByteArray();\n        rrsig = RRSIG.parse(new DataInputStream(new ByteArrayInputStream(rrsigb)), rrsigb, rrsigb.length);\n        assertEquals(TYPE.A, rrsig.typeCovered);\n        assertEquals(SignatureAlgorithm.RSASHA256, rrsig.algorithm);\n        assertEquals(2, rrsig.labels);\n        assertEquals(3600, rrsig.originalTtl);\n        assertEquals(new Date(1000), rrsig.signatureExpiration);\n        assertEquals(new Date(0), rrsig.signatureInception);\n        assertEquals(42, rrsig.keyTag);\n        assertCsEquals(\"example.com\", rrsig.signerName);\n        assertArrayEquals(new byte[] {42}, rrsig.getSignature());\n    }\n\n    @Test\n    public void testSoaRecord() throws Exception {\n        SOA soa = new SOA(\"sns.dns.icann.org\", \"noc.dns.icann.org\", 2015060341, 7200, 3600, 1209600, 3600);\n        assertEquals(\"sns.dns.icann.org. noc.dns.icann.org. 2015060341 7200 3600 1209600 3600\", soa.toString());\n        assertEquals(TYPE.SOA, soa.getType());\n        byte[] soab = soa.toByteArray();\n        soa = SOA.parse(new DataInputStream(new ByteArrayInputStream(soab)), soab);\n        assertCsEquals(\"sns.dns.icann.org\", soa.mname);\n        assertCsEquals(\"noc.dns.icann.org\", soa.rname);\n        assertEquals(2015060341, soa.serial);\n        assertEquals(7200, soa.refresh);\n        assertEquals(3600, soa.retry);\n        assertEquals(1209600, soa.expire);\n        assertEquals(3600, soa.minimum);\n    }\n\n    @Test\n    public void testSrvRecord() throws Exception {\n        SRV srv = new SRV(30, 31, 5222, \"hermes.jabber.org\");\n        assertEquals(\"30 31 5222 hermes.jabber.org.\", srv.toString());\n        assertEquals(TYPE.SRV, srv.getType());\n        byte[] srvb = srv.toByteArray();\n        srv = SRV.parse(new DataInputStream(new ByteArrayInputStream(srvb)), srvb);\n        assertEquals(30, srv.priority);\n        assertEquals(31, srv.weight);\n        assertEquals(5222, srv.port);\n        assertCsEquals(\"hermes.jabber.org\", srv.target);\n    }\n\n    @Test\n    public void testTlsaRecord() throws Exception {\n        TLSA tlsa = new TLSA((byte) 1, (byte) 1, (byte) 1, new byte[] {0x13, 0x37});\n        assertEquals(\"1 1 1 1337\", tlsa.toString());\n        assertEquals(TYPE.TLSA, tlsa.getType());\n        byte[] tlsab = tlsa.toByteArray();\n        tlsa = TLSA.parse(new DataInputStream(new ByteArrayInputStream(tlsab)), tlsab.length);\n        assertEquals(1, tlsa.certUsageByte);\n        assertEquals(1, tlsa.selectorByte);\n        assertEquals(1, tlsa.matchingTypeByte);\n        assertArrayEquals(new byte[] {0x13, 0x37}, tlsa.getCertificateAssociation());\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/test/java/org/minidns/record/TLSATest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.record;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\nimport org.junit.jupiter.api.Test;\n\npublic class TLSATest {\n\n    @Test\n    public void ensureTlsaLutsAreInitialized() {\n        TLSA tlsa = new TLSA((byte) 3, (byte) 1, (byte) 2, new byte[] { 0x13, 0x37 });\n\n        assertEquals(3, tlsa.certUsageByte);\n        assertNotNull(tlsa.certUsage);\n\n        assertEquals(1, tlsa.selectorByte);\n        assertNotNull(tlsa.selector);\n\n        assertEquals(2, tlsa.matchingTypeByte);\n        assertNotNull(tlsa.matchingType);\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/test/java/org/minidns/util/Base32Test.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class Base32Test {\n    @Test\n    public void testEncodeToString() {\n        assertEquals(\"\", Base32.encodeToString(new byte[] {}));\n        assertEquals(\"0410====\", Base32.encodeToString(new byte[] {1, 2}));\n        assertEquals(\"891K8HA6\", Base32.encodeToString(new byte[] {0x42, 0x43, 0x44, 0x45, 0x46}));\n        assertEquals(\"VS0FU07V03VG0===\", Base32.encodeToString(new byte[] {-1, 0, -1, 0, -1, 0, -1, 0}));\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/test/java/org/minidns/util/Base64Test.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class Base64Test {\n    @Test\n    public void testEncodeToString() {\n        assertEquals(\"\", Base64.encodeToString(new byte[] {}));\n        assertEquals(\"Qg==\", Base64.encodeToString(new byte[] {0x42}));\n        assertEquals(\"AQID\", Base64.encodeToString(new byte[] {1, 2, 3}));\n        assertEquals(\"CAIGAP8B/wA=\", Base64.encodeToString(new byte[] {8, 2, 6, 0, -1, 1, -1, 0}));\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/test/java/org/minidns/util/InetAddressUtilTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.net.Inet4Address;\nimport java.net.Inet6Address;\n\nimport org.junit.jupiter.api.Test;\nimport org.minidns.dnsname.DnsName;\n\npublic class InetAddressUtilTest {\n\n    // @formatter:off\n    private static final String[] VALID_IPV6 = new String[] {\n            \"2a03:4000:2:2f5::1\",\n            \"2001:0db8:85a3:08d3:1319:8a2e:0370:7344\"\n    };\n    // @formatter:on\n\n    // @formatter:off\n    private static final String[] ZERO_COMPRESSED_IPV6 = new String[] {\n            \"2001:db8:0:0:0::1\",\n            \"2001:db8:0:0::1\",\n            \"2001:db8:0::1\",\n            \"2001:db8::1\"\n            };\n    // @formatter:on\n\n    // @formatter:off\n    private static final String[] VALID_IPV4 = new String[] {\n            \"192.168.0.1\",\n            \"127.0.0.1\"\n    };\n    // @formatter:on\n\n    // @formatter:off\n    private static final String[] INVALID_IP = new String[] {\n            \"2001:0db8:85a3:08d3:1319:8a2e:0370:7344:3212\",\n            \"2001:db8:0:0:1\",\n            \"0.0.1\",\n            \"1.2.3.4.5\",\n            \"foo.example\",\n            \"foo.bar.baz.example\",\n    };\n    // @formatter:on\n\n    @Test\n    public void testValidIpv6() {\n        assertAllValidIpv6(VALID_IPV6);\n        assertAllValidIpv6(ZERO_COMPRESSED_IPV6);\n    }\n\n    @Test\n    public void testInvalidIpv6() {\n        assertAllInvalidIpv6(INVALID_IP);\n    }\n\n    @Test\n    public void testValidIpv4() {\n        assertAllValidIpv4(VALID_IPV4);\n    }\n\n    @Test\n    public void testInvalidIpv4() {\n        assertAllInvalidIpv4(INVALID_IP);\n    }\n\n    private static void assertAllValidIpv6(String... addresses) {\n        for (String address : addresses) {\n            if (!InetAddressUtil.isIpV6Address(address)) {\n                throw new AssertionError(address + \" is not a valid IPv6 Address\");\n            }\n        }\n    }\n\n    private static void assertAllValidIpv4(String... addresses) {\n        for (String address : addresses) {\n            if (!InetAddressUtil.isIpV4Address(address)) {\n                throw new AssertionError(address + \" is not a valid IPv4 Address\");\n            }\n        }\n    }\n\n    private static void assertAllInvalidIpv6(String... addresses) {\n        for (String address : addresses) {\n            if (InetAddressUtil.isIpV6Address(address)) {\n                throw new AssertionError(\n                        address + \" is believed to be valid IPv6 Address by isIpv6Address(), although it should not be one.\");\n            }\n        }\n    }\n\n    private static void assertAllInvalidIpv4(String... addresses) {\n        for (String address : addresses) {\n            if (InetAddressUtil.isIpV4Address(address)) {\n                throw new AssertionError(\n                        address + \" is believed to be valid IPv4 Address by isIpv4Address(), although it should not be one\");\n            }\n        }\n    }\n\n    @Test\n    public void testReverseInet6Address() {\n        Inet6Address inet6Address = InetAddressUtil.ipv6From(VALID_IPV6[0]);\n        DnsName reversedIpv6Address = InetAddressUtil.reverseIpAddressOf(inet6Address);\n        assertEquals(DnsName.from(\"3.0.a.2.0.0.0.4.2.0.0.0.5.f.2.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0\"), reversedIpv6Address);\n    }\n\n    @Test\n    public void testReverseInet4Address() {\n        Inet4Address inet4Address = InetAddressUtil.ipv4From(VALID_IPV4[0]);\n        DnsName reversedIpv4Address = InetAddressUtil.reverseIpAddressOf(inet4Address);\n        assertEquals(DnsName.from(\"1.0.168.192\"), reversedIpv4Address);\n    }\n}\n"
  },
  {
    "path": "minidns-core/src/test/java/org/minidns/util/NameUtilTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class NameUtilTest {\n\n    @Test\n    public void idnEqualsTest() {\n        assertTrue(NameUtil.idnEquals(null, null));\n        assertTrue(NameUtil.idnEquals(\"domain.example\", \"domain.example\"));\n        assertTrue(NameUtil.idnEquals(\"dömäin.example\", \"xn--dmin-moa0i.example\"));\n        assertTrue(NameUtil.idnEquals(\"موقع.وزارة-الاتصالات.مصر\", \"xn--4gbrim.xn----ymcbaaajlc6dj7bxne2c.xn--wgbh1c\"));\n\n        assertFalse(NameUtil.idnEquals(\"dömäin.example\", null));\n        assertFalse(NameUtil.idnEquals(null, \"domain.example\"));\n        assertFalse(NameUtil.idnEquals(\"dömäin.example\", \"domain.example\"));\n        assertFalse(NameUtil.idnEquals(\"\", \"domain.example\"));\n    }\n\n}\n"
  },
  {
    "path": "minidns-core/src/test/java/org/minidns/util/SrvUtilTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.util;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.junit.jupiter.api.Test;\n\nimport org.minidns.record.SRV;\n\npublic class SrvUtilTest {\n\n    @Test\n    public void sortSRVlowestPrioFirstTest() {\n        List<SRV> sortedRecords = SrvUtil.sortSrvRecords(createSRVRecords());\n        assertTrue(sortedRecords.get(0).target.ace.equals(\"0.20.foo.bar\"));\n    }\n\n    @Test\n    public void sortSRVdistributeOverWeights() {\n        int weight50 = 0, weight20one = 0, weight20two = 0, weight10 = 0;\n        for (int i = 0; i < 1000; i++) {\n            List<SRV> sortedRecords = SrvUtil.sortSrvRecords(createSRVRecords());\n            String host = sortedRecords.get(1).target.ace;\n            if (host.equals(\"5.20.one.foo.bar\")) {\n                weight20one++;\n            } else if (host.equals(\"5.20.two.foo.bar\")) {\n                weight20two++;\n            } else if (host.equals(\"5.10.foo.bar\")) {\n                weight10++;\n            } else if (host.equals(\"5.50.foo.bar\")) {\n                weight50++;\n            } else {\n                fail(\"Wrong host after SRVRecord sorting\");\n            }\n        }\n        assertTrue(weight50 > 400 && weight50 < 600);\n        assertTrue(weight20one > 100 && weight20one < 300);\n        assertTrue(weight20two > 100 && weight20two < 300);\n        assertTrue(weight10 > 0 && weight10 < 200);\n    }\n\n    @Test\n    public void sortSRVdistributeZeroWeights() {\n        int weightZeroOne = 0, weightZeroTwo = 0;\n        for (int i = 0; i < 1000; i++) {\n            List<SRV> sortedRecords = SrvUtil.sortSrvRecords(createSRVRecords());\n            // Remove the first 5 records with a lower priority\n            for (int j = 0; j < 5; j++) {\n                sortedRecords.remove(0);\n            }\n            String host = sortedRecords.remove(0).target.ace;\n            if (host.equals(\"10.0.one.foo.bar\")) {\n                weightZeroOne++;\n            } else if (host.endsWith(\"10.0.two.foo.bar\")) {\n                weightZeroTwo++;\n            } else {\n                fail(\"Wrong host after SRVRecord sorting\");\n            }\n        }\n        assertTrue(weightZeroOne > 400 && weightZeroOne < 600);\n        assertTrue(weightZeroTwo > 400 && weightZeroTwo < 600);\n    }\n\n    private static List<SRV> createSRVRecords() {\n        List<SRV> records = new ArrayList<>();\n\n        // We create one record with priority 0 that should also be tried first\n        // Then 4 records with priority 5 and different weights (50, 20, 20, 10)\n        // Then 2 records with priority 10 and weight 0 which should be treated equal\n        // These records are added in a 'random' way to the list\n        records.add(new SRV(5, 20, 42, \"5.20.one.foo.bar\"));  // Priority 5, Weight 20\n        records.add(new SRV(10, 0, 42, \"10.0.one.foo.bar\"));  // Priority 10, Weight 0\n        records.add(new SRV(5, 10, 42, \"5.10.foo.bar\"));      // Priority 5, Weight 10\n        records.add(new SRV(10, 0, 42, \"10.0.two.foo.bar\"));  // Priority 10, Weight 0\n        records.add(new SRV(5, 20, 42, \"5.20.two.foo.bar\"));  // Priority 5, Weight 20\n        records.add(new SRV(0, 20, 42, \"0.20.foo.bar\"));      // Priority 0, Weight 20\n        records.add(new SRV(5, 50, 42, \"5.50.foo.bar\"));      // Priority 5, Weight 50\n\n        return records;\n    }\n}\n"
  },
  {
    "path": "minidns-dane/build.gradle",
    "content": "plugins {\n\tid 'org.minidns.java-conventions'\n}\n\ndescription = \"Support for DANE using APIs of Java 7 (or higher)\"\n\ndependencies {\n    api project(':minidns-dnssec')\n    testImplementation project(path: \":minidns-client\", configuration: \"testRuntime\")\n    testImplementation project(path: \":minidns-dnssec\", configuration: \"testRuntime\")\n}\n"
  },
  {
    "path": "minidns-dane/src/main/java/org/minidns/dane/java7/DaneExtendedTrustManager.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dane.java7;\n\nimport org.minidns.dane.DaneVerifier;\nimport org.minidns.dane.X509TrustManagerUtil;\nimport org.minidns.dnssec.DnssecClient;\nimport org.minidns.util.InetAddressUtil;\n\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLEngine;\nimport javax.net.ssl.SSLSocket;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.X509ExtendedTrustManager;\nimport javax.net.ssl.X509TrustManager;\n\nimport java.net.Socket;\nimport java.security.KeyManagementException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport java.util.logging.Logger;\n\npublic class DaneExtendedTrustManager extends X509ExtendedTrustManager {\n    private static final Logger LOGGER = Logger.getLogger(DaneExtendedTrustManager.class.getName());\n\n    private final X509TrustManager base;\n    private final DaneVerifier verifier;\n\n    public static void inject() {\n        inject(new DaneExtendedTrustManager());\n    }\n\n    public static void inject(DaneExtendedTrustManager trustManager) {\n        try {\n            SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n            sslContext.init(null, new TrustManager[] {trustManager}, null);\n            SSLContext.setDefault(sslContext);\n        } catch (NoSuchAlgorithmException | KeyManagementException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public DaneExtendedTrustManager() {\n        this(X509TrustManagerUtil.getDefault());\n    }\n\n    public DaneExtendedTrustManager(DnssecClient client) {\n        this(client, X509TrustManagerUtil.getDefault());\n    }\n\n    public DaneExtendedTrustManager(X509TrustManager base) {\n        this(new DaneVerifier(), base);\n    }\n\n    public DaneExtendedTrustManager(DnssecClient client, X509TrustManager base) {\n        this(new DaneVerifier(client), base);\n    }\n\n    public DaneExtendedTrustManager(DaneVerifier verifier, X509TrustManager base) {\n        this.verifier = verifier;\n        this.base = base;\n    }\n\n    @Override\n    public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {\n        if (base == null) {\n            LOGGER.warning(\"DaneExtendedTrustManager invalidly used for client certificate check and no fallback X509TrustManager specified\");\n            return;\n        }\n\n        LOGGER.info(\"DaneExtendedTrustManager invalidly used for client certificate check forwarding request to fallback X509TrustManage\");\n        if (base instanceof X509ExtendedTrustManager) {\n            ((X509ExtendedTrustManager) base).checkClientTrusted(chain, authType, socket);\n        } else {\n            base.checkClientTrusted(chain, authType);\n        }\n    }\n\n    @Override\n    public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException {\n        boolean verificationSuccessful = false;\n\n        if (socket instanceof SSLSocket) {\n            final SSLSocket sslSocket = (SSLSocket) socket;\n            final String hostname = sslSocket.getHandshakeSession().getPeerHost();\n\n            if (hostname == null) {\n                LOGGER.warning(\"Hostname returned by sslSocket.getHandshakeSession().getPeerHost() is null\");\n            } else if (InetAddressUtil.isIpAddress(hostname)) {\n                LOGGER.warning(\n                        \"Hostname returned by sslSocket.getHandshakeSession().getPeerHost() '\" + hostname\n                                + \"' is an IP address\");\n            } else {\n                final int port = socket.getPort();\n                verificationSuccessful = verifier.verifyCertificateChain(chain, hostname, port);\n            }\n        } else {\n            throw new IllegalStateException(\"The provided socket '\" + socket + \"' is not of type SSLSocket\");\n        }\n\n        if (verificationSuccessful) {\n            // Verification successful, no need to delegate to base trust manager.\n            return;\n        }\n\n        if (base instanceof X509ExtendedTrustManager) {\n            ((X509ExtendedTrustManager) base).checkServerTrusted(chain, authType, socket);\n        } else {\n            base.checkServerTrusted(chain, authType);\n        }\n    }\n\n    @Override\n    public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {\n        if (base == null) {\n            LOGGER.warning(\"DaneExtendedTrustManager invalidly used for client certificate check and no fallback X509TrustManager specified\");\n            return;\n        }\n\n        LOGGER.info(\"DaneExtendedTrustManager invalidly used for client certificate check, forwarding request to fallback X509TrustManage\");\n        if (base instanceof X509ExtendedTrustManager) {\n            ((X509ExtendedTrustManager) base).checkClientTrusted(chain, authType, engine);\n        } else {\n            base.checkClientTrusted(chain, authType);\n        }\n    }\n\n    @Override\n    public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException {\n        if (verifier.verifyCertificateChain(chain, engine.getPeerHost(), engine.getPeerPort())) {\n            // Verification successful, no need to delegate to base trust manager.\n            return;\n        }\n\n        if (base instanceof X509ExtendedTrustManager) {\n            ((X509ExtendedTrustManager) base).checkServerTrusted(chain, authType, engine);\n        } else {\n            base.checkServerTrusted(chain, authType);\n        }\n    }\n\n    @Override\n    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n        if (base == null) {\n            LOGGER.warning(\"DaneExtendedTrustManager invalidly used for client certificate check and no fallback X509TrustManager specified\");\n            return;\n        }\n\n        LOGGER.info(\"DaneExtendedTrustManager invalidly used for client certificate check, forwarding request to fallback X509TrustManage\");\n        base.checkClientTrusted(chain, authType);\n    }\n\n    @Override\n    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n        LOGGER.info(\"DaneExtendedTrustManager cannot be used without hostname information, forwarding request to fallback X509TrustManage\");\n        base.checkServerTrusted(chain, authType);\n    }\n\n    @Override\n    public X509Certificate[] getAcceptedIssuers() {\n        return base.getAcceptedIssuers();\n    }\n}\n"
  },
  {
    "path": "minidns-dane/src/test/java/org/minidns/dane/java7/DaneJava7Test.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dane.java7;\n\nimport org.junit.jupiter.api.Test;\n\npublic class DaneJava7Test {\n    /**\n     * Just here to ensure jacoco is not complaining.\n     */\n    @Test\n    public void emptyTest() {\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/build.gradle",
    "content": "plugins {\n\tid 'org.minidns.java-conventions'\n\tid 'org.minidns.android-conventions'\n}\n\ndescription = \"MiniDNS' client with support for DNSSEC\"\n\ndependencies {\n    api project(':minidns-client')\n    api project(':minidns-iterative-resolver')\n    testImplementation project(path: \":minidns-client\", configuration: \"testRuntime\")\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dane/DaneCertificateException.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dane;\n\nimport java.security.cert.CertificateException;\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.minidns.record.TLSA;\n\npublic abstract class DaneCertificateException extends CertificateException {\n\n    /**\n     * \n     */\n    private static final long serialVersionUID = 1L;\n\n    protected DaneCertificateException() {\n    }\n\n    protected DaneCertificateException(String message) {\n        super(message);\n    }\n\n    public static class CertificateMismatch extends DaneCertificateException {\n\n        /**\n         * \n         */\n        private static final long serialVersionUID = 1L;\n\n        public final TLSA tlsa;\n        public final byte[] computed;\n\n        public CertificateMismatch(TLSA tlsa, byte[] computed) {\n            super(\"The TLSA RR does not match the certificate\");\n            this.tlsa = tlsa;\n            this.computed = computed;\n        }\n    }\n\n    public static class MultipleCertificateMismatchExceptions extends DaneCertificateException {\n\n        /**\n         * \n         */\n        private static final long serialVersionUID = 1L;\n\n        public final List<CertificateMismatch> certificateMismatchExceptions;\n\n        public MultipleCertificateMismatchExceptions(List<CertificateMismatch> certificateMismatchExceptions) {\n            super(\"There where multiple CertificateMismatch exceptions because none of the TLSA RR does match the certificate\");\n            assert !certificateMismatchExceptions.isEmpty();\n            this.certificateMismatchExceptions = Collections.unmodifiableList(certificateMismatchExceptions);\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dane/DaneVerifier.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dane;\n\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnssec.DnssecClient;\nimport org.minidns.dnssec.DnssecQueryResult;\nimport org.minidns.dnssec.DnssecUnverifiedReason;\nimport org.minidns.record.Data;\nimport org.minidns.record.Record;\nimport org.minidns.record.TLSA;\n\nimport javax.net.ssl.HttpsURLConnection;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLPeerUnverifiedException;\nimport javax.net.ssl.SSLSession;\nimport javax.net.ssl.SSLSocket;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.X509TrustManager;\n\nimport java.io.IOException;\nimport java.security.KeyManagementException;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.Certificate;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.logging.Logger;\n\n/**\n * A helper class to validate the usage of TLSA records.\n */\npublic class DaneVerifier {\n    private static final Logger LOGGER = Logger.getLogger(DaneVerifier.class.getName());\n\n    private final DnssecClient client;\n\n    public DaneVerifier() {\n        this(new DnssecClient());\n    }\n\n    public DaneVerifier(DnssecClient client) {\n        this.client = client;\n    }\n\n    /**\n     * Verifies the certificate chain in an active {@link SSLSocket}. The socket must be connected.\n     *\n     * @param socket A connected {@link SSLSocket} whose certificate chain shall be verified using DANE.\n     * @return Whether the DANE verification is the only requirement according to the TLSA record.\n     * If this method returns {@code false}, additional PKIX validation is required.\n     * @throws CertificateException if the certificate chain provided differs from the one enforced using DANE.\n     */\n    public boolean verify(SSLSocket socket) throws CertificateException {\n        if (!socket.isConnected()) {\n            throw new IllegalStateException(\"Socket not yet connected.\");\n        }\n        return verify(socket.getSession());\n    }\n\n    /**\n     * Verifies the certificate chain in an active {@link SSLSession}.\n     *\n     * @param session An active {@link SSLSession} whose certificate chain shall be verified using DANE.\n     * @return Whether the DANE verification is the only requirement according to the TLSA record.\n     * If this method returns {@code false}, additional PKIX validation is required.\n     * @throws CertificateException if the certificate chain provided differs from the one enforced using DANE.\n     */\n    public boolean verify(SSLSession session) throws CertificateException {\n        try {\n            return verifyCertificateChain(convert(session.getPeerCertificates()), session.getPeerHost(), session.getPeerPort());\n        } catch (SSLPeerUnverifiedException e) {\n            throw new CertificateException(\"Peer not verified\", e);\n        }\n    }\n\n    /**\n     * Verifies a certificate chain to be valid when used with the given connection details using DANE.\n     *\n     * @param chain A certificate chain that should be verified using DANE.\n     * @param hostName The DNS name of the host this certificate chain belongs to.\n     * @param port The port number that was used to reach the server providing the certificate chain in question.\n     * @return Whether the DANE verification is the only requirement according to the TLSA record.\n     * If this method returns {@code false}, additional PKIX validation is required.\n     * @throws CertificateException if the certificate chain provided differs from the one enforced using DANE.\n     */\n    public boolean verifyCertificateChain(X509Certificate[] chain, String hostName, int port) throws CertificateException {\n        DnsName req = DnsName.from(\"_\" + port + \"._tcp.\" + hostName);\n        DnssecQueryResult result;\n        try {\n            result = client.queryDnssec(req, Record.TYPE.TLSA);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n        DnsMessage res = result.dnsQueryResult.response;\n        // TODO: We previously used the AD bit here. This allowed non-DNSSEC aware clients to be plugged into\n        // DaneVerifier, which, in turn, allows to use a trusted forward as DNSSEC validator. Is this a good idea?\n        if (!result.isAuthenticData()) {\n            String msg = \"Got TLSA response from DNS server, but was not signed properly.\";\n            msg += \" Reasons:\";\n            for (DnssecUnverifiedReason reason : result.getUnverifiedReasons()) {\n                 msg += \" \" + reason;\n            }\n            LOGGER.info(msg);\n            return false;\n        }\n\n        List<DaneCertificateException.CertificateMismatch> certificateMismatchExceptions = new ArrayList<>();\n        boolean verified = false;\n        for (Record<? extends Data> record : res.answerSection) {\n            if (record.type == Record.TYPE.TLSA && record.name.equals(req)) {\n                TLSA tlsa = (TLSA) record.payloadData;\n                try {\n                    verified |= checkCertificateMatches(chain[0], tlsa, hostName);\n                } catch (DaneCertificateException.CertificateMismatch certificateMismatchException) {\n                    // Record the mismatch and only throw an exception if no\n                    // TLSA RR is able to verify the cert. This allows for TLSA\n                    // certificate rollover.\n                    certificateMismatchExceptions.add(certificateMismatchException);\n                }\n                if (verified) break;\n            }\n        }\n\n        if (!verified && !certificateMismatchExceptions.isEmpty()) {\n            throw new DaneCertificateException.MultipleCertificateMismatchExceptions(certificateMismatchExceptions);\n        }\n\n        return verified;\n    }\n\n    private static boolean checkCertificateMatches(X509Certificate cert, TLSA tlsa, String hostName) throws CertificateException {\n        if (tlsa.certUsage == null) {\n            LOGGER.warning(\"TLSA certificate usage byte \" + tlsa.certUsageByte + \" is not supported while verifying \" + hostName);\n            return false;\n        }\n\n        switch (tlsa.certUsage) {\n        case serviceCertificateConstraint: // PKIX-EE\n        case domainIssuedCertificate: // DANE-EE\n            break;\n        case caConstraint: // PKIX-TA\n        case trustAnchorAssertion: // DANE-TA\n        default:\n            LOGGER.warning(\"TLSA certificate usage \" + tlsa.certUsage + \" (\" + tlsa.certUsageByte + \") not supported while verifying \" + hostName);\n            return false;\n        }\n\n        if (tlsa.selector == null) {\n            LOGGER.warning(\"TLSA selector byte \" + tlsa.selectorByte + \" is not supported while verifying \" + hostName);\n            return false;\n        }\n\n        byte[] comp = null;\n        switch (tlsa.selector) {\n            case fullCertificate:\n                comp = cert.getEncoded();\n                break;\n            case subjectPublicKeyInfo:\n                comp = cert.getPublicKey().getEncoded();\n                break;\n            default:\n                LOGGER.warning(\"TLSA selector \" + tlsa.selector + \" (\" + tlsa.selectorByte + \") not supported while verifying \" + hostName);\n                return false;\n        }\n\n        if (tlsa.matchingType == null) {\n            LOGGER.warning(\"TLSA matching type byte \" + tlsa.matchingTypeByte + \" is not supported while verifying \" + hostName);\n            return false;\n        }\n\n        switch (tlsa.matchingType) {\n            case noHash:\n                break;\n            case sha256:\n                try {\n                    comp = MessageDigest.getInstance(\"SHA-256\").digest(comp);\n                } catch (NoSuchAlgorithmException e) {\n                    throw new CertificateException(\"Verification using TLSA failed: could not SHA-256 for matching\", e);\n                }\n                break;\n            case sha512:\n                try {\n                    comp = MessageDigest.getInstance(\"SHA-512\").digest(comp);\n                } catch (NoSuchAlgorithmException e) {\n                    throw new CertificateException(\"Verification using TLSA failed: could not SHA-512 for matching\", e);\n                }\n                break;\n            default:\n                LOGGER.warning(\"TLSA matching type \" + tlsa.matchingType + \" not supported while verifying \" + hostName);\n                return false;\n        }\n\n        boolean matches = tlsa.certificateAssociationEquals(comp);\n        if (!matches) {\n            throw new DaneCertificateException.CertificateMismatch(tlsa, comp);\n        }\n\n        // domain issued certificate does not require further verification,\n        // service certificate constraint does.\n        return tlsa.certUsage == TLSA.CertUsage.domainIssuedCertificate;\n    }\n\n    /**\n     * Invokes {@link HttpsURLConnection#connect()} in a DANE verified fashion.\n     * This method must be called before {@link HttpsURLConnection#connect()} is invoked.\n     *\n     * If a SSLSocketFactory was set on this HttpsURLConnection, it will be ignored. You can use\n     * {@link #verifiedConnect(HttpsURLConnection, X509TrustManager)} to inject a custom {@link TrustManager}.\n     *\n     * @param conn connection to be connected.\n     * @return The {@link HttpsURLConnection} after being connected.\n     * @throws IOException when the connection could not be established.\n     * @throws CertificateException if there was an exception while verifying the certificate.\n     */\n    public HttpsURLConnection verifiedConnect(HttpsURLConnection conn) throws IOException, CertificateException {\n        return verifiedConnect(conn, null);\n    }\n\n    /**\n     * Invokes {@link HttpsURLConnection#connect()} in a DANE verified fashion.\n     * This method must be called before {@link HttpsURLConnection#connect()} is invoked.\n     *\n     * If a SSLSocketFactory was set on this HttpsURLConnection, it will be ignored.\n     *\n     * @param conn         connection to be connected.\n     * @param trustManager A non-default {@link TrustManager} to be used.\n     * @return The {@link HttpsURLConnection} after being connected.\n     * @throws IOException when the connection could not be established.\n     * @throws CertificateException if there was an exception while verifying the certificate.\n     */\n    public HttpsURLConnection verifiedConnect(HttpsURLConnection conn, X509TrustManager trustManager) throws IOException, CertificateException {\n        try {\n            SSLContext context = SSLContext.getInstance(\"TLS\");\n            ExpectingTrustManager expectingTrustManager = new ExpectingTrustManager(trustManager);\n            context.init(null, new TrustManager[] {expectingTrustManager}, null);\n            conn.setSSLSocketFactory(context.getSocketFactory());\n            conn.connect();\n            boolean fullyVerified = verifyCertificateChain(convert(conn.getServerCertificates()), conn.getURL().getHost(),\n                    conn.getURL().getPort() < 0 ? conn.getURL().getDefaultPort() : conn.getURL().getPort());\n            // If fullyVerified is true then it's the DANE verification performed by verifiyCertificateChain() is\n            // sufficient to verify the certificate and we ignore possible pending exceptions of ExpectingTrustManager.\n            if (!fullyVerified && expectingTrustManager.hasException()) {\n                throw new IOException(\"Peer verification failed using PKIX\", expectingTrustManager.getException());\n            }\n            return conn;\n        } catch (NoSuchAlgorithmException | KeyManagementException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private static X509Certificate[] convert(Certificate[] certificates) {\n        List<X509Certificate> certs = new ArrayList<>();\n        for (Certificate certificate : certificates) {\n            if (certificate instanceof X509Certificate) {\n                certs.add((X509Certificate) certificate);\n            }\n        }\n        return certs.toArray(new X509Certificate[certs.size()]);\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dane/ExpectingTrustManager.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dane;\n\nimport javax.net.ssl.X509TrustManager;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\n\npublic class ExpectingTrustManager implements X509TrustManager {\n    private CertificateException exception;\n    private final X509TrustManager trustManager;\n\n    /**\n     * Creates a new instance of ExpectingTrustManager.\n     *\n     * @param trustManager The {@link X509TrustManager} to be used for verification.\n     *                     {@code null} to use the system default.\n     */\n    public ExpectingTrustManager(X509TrustManager trustManager) {\n        this.trustManager = trustManager == null ? X509TrustManagerUtil.getDefault() : trustManager;\n    }\n\n    public boolean hasException() {\n        return exception != null;\n    }\n\n    public CertificateException getException() {\n        CertificateException e = exception;\n        exception = null;\n        return e;\n    }\n\n    @Override\n    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n        try {\n            trustManager.checkClientTrusted(chain, authType);\n        } catch (CertificateException e) {\n            exception = e;\n        }\n    }\n\n    @Override\n    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {\n        try {\n            trustManager.checkServerTrusted(chain, authType);\n        } catch (CertificateException e) {\n            exception = e;\n        }\n    }\n\n    @Override\n    public X509Certificate[] getAcceptedIssuers() {\n        return trustManager.getAcceptedIssuers();\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dane/X509TrustManagerUtil.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dane;\n\nimport java.security.KeyStore;\nimport java.security.KeyStoreException;\nimport java.security.NoSuchAlgorithmException;\n\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.TrustManagerFactory;\nimport javax.net.ssl.X509TrustManager;\n\npublic class X509TrustManagerUtil {\n\n    public static X509TrustManager getDefault() {\n        return getDefault(null);\n    }\n\n    public static X509TrustManager getDefault(KeyStore keyStore) {\n        String defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm();\n        TrustManagerFactory trustManagerFactory;\n        try {\n            trustManagerFactory = TrustManagerFactory.getInstance(defaultAlgorithm);\n            trustManagerFactory.init(keyStore);\n        } catch (NoSuchAlgorithmException | KeyStoreException e) {\n            throw new AssertionError(e);\n        }\n\n        for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {\n            if (trustManager instanceof X509TrustManager) {\n                return (X509TrustManager) trustManager;\n            }\n        }\n        throw new AssertionError(\"No trust manager for the default algorithm \" + defaultAlgorithm + \" found\");\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/DigestCalculator.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec;\n\npublic interface DigestCalculator {\n    byte[] digest(byte[] bytes);\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/DnssecClient.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec;\n\nimport org.minidns.DnsCache;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.dnssec.DnssecUnverifiedReason.NoActiveSignaturesReason;\nimport org.minidns.dnssec.DnssecUnverifiedReason.NoSecureEntryPointReason;\nimport org.minidns.dnssec.DnssecUnverifiedReason.NoSignaturesReason;\nimport org.minidns.dnssec.DnssecUnverifiedReason.NoTrustAnchorReason;\nimport org.minidns.dnssec.DnssecValidationFailedException.AuthorityDoesNotContainSoa;\nimport org.minidns.iterative.ReliableDnsClient;\nimport org.minidns.record.DLV;\nimport org.minidns.record.DNSKEY;\nimport org.minidns.record.DS;\nimport org.minidns.record.Data;\nimport org.minidns.record.DelegatingDnssecRR;\nimport org.minidns.record.NSEC;\nimport org.minidns.record.NSEC3;\nimport org.minidns.record.RRSIG;\nimport org.minidns.record.Record;\nimport org.minidns.record.Record.CLASS;\nimport org.minidns.record.Record.TYPE;\n\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Date;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class DnssecClient extends ReliableDnsClient {\n\n    /**\n     * The root zone's KSK.\n     * The ID of the current key is \"Klajeyz\", and the key tag value is \"20326\".\n     */\n    private static final BigInteger rootEntryKey = new BigInteger(\"1628686155461064465348252249725010996177649738666492500572664444461532807739744536029771810659241049343994038053541290419968870563183856865780916376571550372513476957870843322273120879361960335192976656756972171258658400305760429696147778001233984421619267530978084631948434496468785021389956803104620471232008587410372348519229650742022804219634190734272506220018657920136902014393834092648785514548876370028925405557661759399901378816916683122474038734912535425670533237815676134840739565610963796427401855723026687073600445461090736240030247906095053875491225879656640052743394090544036297390104110989318819106653199917493\");\n\n    private static final DnsName DEFAULT_DLV = DnsName.from(\"dlv.isc.org\");\n\n    /**\n     * Create a new DNSSEC aware DNS client using the global default cache.\n     */\n    public DnssecClient() {\n        this(DEFAULT_CACHE);\n    }\n\n    /**\n     * Create a new DNSSEC aware DNS client with the given DNS cache.\n     *\n     * @param cache The backend DNS cache.\n     */\n    public DnssecClient(DnsCache cache) {\n        super(cache);\n        addSecureEntryPoint(DnsName.ROOT, rootEntryKey.toByteArray());\n    }\n\n    /**\n     * Known secure entry points (SEPs).\n     */\n    private final Map<DnsName, byte[]> knownSeps = new ConcurrentHashMap<>();\n\n    private boolean stripSignatureRecords = true;\n\n    /**\n     * The active DNSSEC Look-aside Validation Registry. May be <code>null</code>.\n     */\n    private DnsName dlv;\n\n    @Override\n    public DnsQueryResult query(Question q) throws IOException {\n        DnssecQueryResult dnssecQueryResult =  queryDnssec(q);\n        if (!dnssecQueryResult.isAuthenticData()) {\n            // TODO: Refine exception.\n            throw new IOException();\n        }\n        return dnssecQueryResult.dnsQueryResult;\n    }\n\n    public DnssecQueryResult queryDnssec(CharSequence name, TYPE type) throws IOException {\n        Question q = new Question(name, type, CLASS.IN);\n        return queryDnssec(q);\n    }\n\n    public DnssecQueryResult queryDnssec(Question q) throws IOException {\n        DnsQueryResult dnsQueryResult = super.query(q);\n        DnssecQueryResult dnssecQueryResult = performVerification(dnsQueryResult);\n        return dnssecQueryResult;\n    }\n\n    private DnssecQueryResult performVerification(DnsQueryResult dnsQueryResult) throws IOException {\n        if (dnsQueryResult == null) return null;\n\n        DnsMessage dnsMessage = dnsQueryResult.response;\n        DnsMessage.Builder messageBuilder = dnsMessage.asBuilder();\n\n        Set<DnssecUnverifiedReason> unverifiedReasons = verify(dnsMessage);\n\n        messageBuilder.setAuthenticData(unverifiedReasons.isEmpty());\n\n        List<Record<? extends Data>> answers = dnsMessage.answerSection;\n        List<Record<? extends Data>> nameserverRecords = dnsMessage.authoritySection;\n        List<Record<? extends Data>> additionalResourceRecords = dnsMessage.additionalSection;\n        Set<Record<RRSIG>> signatures = new HashSet<>();\n        Record.filter(signatures, RRSIG.class, answers);\n        Record.filter(signatures, RRSIG.class, nameserverRecords);\n        Record.filter(signatures, RRSIG.class, additionalResourceRecords);\n\n        if (stripSignatureRecords) {\n            messageBuilder.setAnswers(stripSignatureRecords(answers));\n            messageBuilder.setNameserverRecords(stripSignatureRecords(nameserverRecords));\n            messageBuilder.setAdditionalResourceRecords(stripSignatureRecords(additionalResourceRecords));\n        }\n\n        return new DnssecQueryResult(messageBuilder.build(), dnsQueryResult, signatures, unverifiedReasons);\n    }\n\n    private static List<Record<? extends Data>> stripSignatureRecords(List<Record<? extends Data>> records) {\n        if (records.isEmpty()) return records;\n        List<Record<? extends Data>> recordList = new ArrayList<>(records.size());\n        for (Record<? extends Data> record : records) {\n            if (record.type != TYPE.RRSIG) {\n                recordList.add(record);\n            }\n        }\n        return recordList;\n    }\n\n    private Set<DnssecUnverifiedReason> verify(DnsMessage dnsMessage) throws IOException {\n        if (!dnsMessage.answerSection.isEmpty()) {\n            return verifyAnswer(dnsMessage);\n        } else {\n            return verifyNsec(dnsMessage);\n        }\n    }\n\n    private Set<DnssecUnverifiedReason> verifyAnswer(DnsMessage dnsMessage) throws IOException {\n        Question q = dnsMessage.questions.get(0);\n        List<Record<? extends Data>> answers = dnsMessage.answerSection;\n        List<Record<? extends Data>> toBeVerified = dnsMessage.copyAnswers();\n        VerifySignaturesResult verifiedSignatures = verifySignatures(q, answers, toBeVerified);\n        Set<DnssecUnverifiedReason> result = verifiedSignatures.reasons;\n        if (!result.isEmpty()) {\n            return result;\n        }\n\n        // Keep SEPs separated, we only need one valid SEP.\n        boolean sepSignatureValid = false;\n        Set<DnssecUnverifiedReason> sepReasons = new HashSet<>();\n        for (Iterator<Record<? extends Data>> iterator = toBeVerified.iterator(); iterator.hasNext(); ) {\n            Record<DNSKEY> record = iterator.next().ifPossibleAs(DNSKEY.class);\n            if (record == null) {\n                continue;\n            }\n\n            // Verify all DNSKEYs as if it was a SEP. If we find a single SEP we are safe.\n            Set<DnssecUnverifiedReason> reasons = verifySecureEntryPoint(record);\n            if (reasons.isEmpty()) {\n                sepSignatureValid = true;\n            } else {\n                sepReasons.addAll(reasons);\n            }\n            if (!verifiedSignatures.sepSignaturePresent) {\n                LOGGER.finer(\"SEP key is not self-signed.\");\n            }\n            iterator.remove();\n        }\n\n        if (verifiedSignatures.sepSignaturePresent && !sepSignatureValid) {\n            result.addAll(sepReasons);\n        }\n        if (verifiedSignatures.sepSignatureRequired && !verifiedSignatures.sepSignaturePresent) {\n            result.add(new NoSecureEntryPointReason(q.name));\n        }\n        if (!toBeVerified.isEmpty()) {\n            if (toBeVerified.size() != answers.size()) {\n                throw new DnssecValidationFailedException(q, \"Only some records are signed!\");\n            } else {\n                result.add(new NoSignaturesReason(q));\n            }\n        }\n        return result;\n    }\n\n    private Set<DnssecUnverifiedReason> verifyNsec(DnsMessage dnsMessage) throws IOException {\n        Set<DnssecUnverifiedReason> result = new HashSet<>();\n        Question q = dnsMessage.questions.get(0);\n        boolean validNsec = false;\n        boolean nsecPresent = false;\n\n        // Get the SOA RR that has to be in the authority section. Note that we will verify its signature later, after\n        // we have verified the NSEC3 RR. And although the data form the SOA RR is only required for NSEC3 we check for\n        // its existence here, since it would be invalid if there is none.\n        // TODO: Add a reference to the relevant RFC parts which specify that there has to be a SOA RR in X.\n        DnsName zone = null;\n        List<Record<? extends Data>> authoritySection = dnsMessage.authoritySection;\n        for (Record<? extends Data> authorityRecord : authoritySection) {\n            if (authorityRecord.type == TYPE.SOA) {\n                zone = authorityRecord.name;\n                break;\n            }\n        }\n        if (zone == null)\n            throw new AuthorityDoesNotContainSoa(dnsMessage);\n\n        // TODO Examine if it is better to verify the RRs in the authority section *before* we verify NSEC(3). We\n        // currently do it the other way around.\n\n        // TODO: This whole logic needs to be changed. It currently checks one NSEC(3) record after another, when it\n        // should first determine if we are dealing with NSEC or NSEC3 and the verify the whole response.\n        for (Record<? extends Data> record : authoritySection) {\n            DnssecUnverifiedReason reason;\n\n            switch (record.type) {\n            case NSEC:\n                nsecPresent = true;\n                Record<NSEC> nsecRecord = record.as(NSEC.class);\n                reason = Verifier.verifyNsec(nsecRecord, q);\n                break;\n            case NSEC3:\n                nsecPresent = true;\n                Record<NSEC3> nsec3Record = record.as(NSEC3.class);\n                reason = Verifier.verifyNsec3(zone, nsec3Record, q);\n                break;\n            default:\n                continue;\n            }\n\n            if (reason != null) {\n                result.add(reason);\n            } else {\n                validNsec = true;\n            }\n        }\n\n        // TODO: Shouldn't we also throw if !nsecPresent?\n        if (nsecPresent && !validNsec) {\n            throw new DnssecValidationFailedException(q, \"Invalid NSEC!\");\n        }\n\n        List<Record<? extends Data>> toBeVerified = dnsMessage.copyAuthority();\n        VerifySignaturesResult verifiedSignatures = verifySignatures(q, authoritySection, toBeVerified);\n        if (validNsec && verifiedSignatures.reasons.isEmpty()) {\n            result.clear();\n        } else {\n            result.addAll(verifiedSignatures.reasons);\n        }\n\n        if (!toBeVerified.isEmpty() && toBeVerified.size() != authoritySection.size()) {\n            // TODO Refine this exception and include the missing toBeVerified RRs and the whole DnsMessage into it.\n            throw new DnssecValidationFailedException(q, \"Only some resource records from the authority section are signed!\");\n        }\n\n        return result;\n    }\n\n    private static final class VerifySignaturesResult {\n        boolean sepSignatureRequired = false;\n        boolean sepSignaturePresent = false;\n        Set<DnssecUnverifiedReason> reasons = new HashSet<>();\n    }\n\n    @SuppressWarnings(\"JavaUtilDate\")\n    private VerifySignaturesResult verifySignatures(Question q, Collection<Record<? extends Data>> reference, List<Record<? extends Data>> toBeVerified) throws IOException {\n        final Date now = new Date();\n        final List<RRSIG> outdatedRrSigs = new ArrayList<>();\n        VerifySignaturesResult result = new VerifySignaturesResult();\n        final List<Record<RRSIG>> rrsigs = new ArrayList<>(toBeVerified.size());\n\n        for (Record<? extends Data> recordToBeVerified : toBeVerified) {\n            Record<RRSIG> record = recordToBeVerified.ifPossibleAs(RRSIG.class);\n            if (record == null) continue;\n\n            RRSIG rrsig = record.payloadData;\n            if (rrsig.signatureExpiration.compareTo(now) < 0 || rrsig.signatureInception.compareTo(now) > 0) {\n                // This RRSIG is out of date, but there might be one that is not.\n                outdatedRrSigs.add(rrsig);\n                continue;\n            }\n            rrsigs.add(record);\n        }\n\n        if (rrsigs.isEmpty()) {\n            if (!outdatedRrSigs.isEmpty()) {\n                result.reasons.add(new NoActiveSignaturesReason(q, outdatedRrSigs));\n            } else {\n                // TODO: Check if QNAME results should have signatures and add a different reason if there are RRSIGs\n                // expected compared to when not.\n                result.reasons.add(new NoSignaturesReason(q));\n            }\n            return result;\n        }\n\n        for (Record<RRSIG> sigRecord : rrsigs) {\n            RRSIG rrsig = sigRecord.payloadData;\n\n            List<Record<? extends Data>> records = new ArrayList<>(reference.size());\n            for (Record<? extends Data> record : reference) {\n                if (record.type == rrsig.typeCovered && record.name.equals(sigRecord.name)) {\n                    records.add(record);\n                }\n            }\n\n            Set<DnssecUnverifiedReason> reasons = verifySignedRecords(q, rrsig, records);\n            result.reasons.addAll(reasons);\n\n            if (q.name.equals(rrsig.signerName) && rrsig.typeCovered == TYPE.DNSKEY) {\n                for (Iterator<Record<? extends Data>> iterator = records.iterator(); iterator.hasNext(); ) {\n                    Record<DNSKEY> dnsKeyRecord = iterator.next().ifPossibleAs(DNSKEY.class);\n                    // dnsKeyRecord should never be null here.\n                    DNSKEY dnskey = dnsKeyRecord.payloadData;\n                    // DNSKEYs are verified separately, so don't mark them verified now.\n                    iterator.remove();\n                    if (dnskey.getKeyTag() == rrsig.keyTag) {\n                        result.sepSignaturePresent = true;\n                    }\n                }\n                // DNSKEY's should be signed by a SEP\n                result.sepSignatureRequired = true;\n            }\n\n            if (!isParentOrSelf(sigRecord.name.ace, rrsig.signerName.ace)) {\n                LOGGER.finer(\"Records at \" + sigRecord.name + \" are cross-signed with a key from \" + rrsig.signerName);\n            } else {\n                toBeVerified.removeAll(records);\n            }\n            toBeVerified.remove(sigRecord);\n        }\n        return result;\n    }\n\n    private static boolean isParentOrSelf(String child, String parent) {\n        if (child.equals(parent)) return true;\n        if (parent.isEmpty()) return true;\n        String[] childSplit = child.split(\"\\\\.\");\n        String[] parentSplit = parent.split(\"\\\\.\");\n        if (parentSplit.length > childSplit.length) return false;\n        for (int i = 1; i <= parentSplit.length; i++) {\n            if (!parentSplit[parentSplit.length - i].equals(childSplit[childSplit.length - i])) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    private Set<DnssecUnverifiedReason> verifySignedRecords(Question q, RRSIG rrsig, List<Record<? extends Data>> records) throws IOException {\n        Set<DnssecUnverifiedReason> result = new HashSet<>();\n        DNSKEY dnskey = null;\n\n        if (rrsig.typeCovered == TYPE.DNSKEY) {\n            // Key must be present\n            List<Record<DNSKEY>> dnskeyRrs = Record.filter(DNSKEY.class, records);\n            for (Record<DNSKEY> dnsKeyRecord : dnskeyRrs) {\n                if (dnsKeyRecord.payloadData.getKeyTag() == rrsig.keyTag) {\n                    dnskey = dnsKeyRecord.payloadData;\n                    break;\n                }\n            }\n        } else if (q.type == TYPE.DS && rrsig.signerName.equals(q.name)) {\n            // We should not probe for the self signed DS negative response, as it will be an endless loop.\n            result.add(new NoTrustAnchorReason(q.name));\n            return result;\n        } else {\n            DnssecQueryResult dnskeyRes = queryDnssec(rrsig.signerName, TYPE.DNSKEY);\n            result.addAll(dnskeyRes.getUnverifiedReasons());\n            List<Record<DNSKEY>> dnskeyRrs = dnskeyRes.dnsQueryResult.response.filterAnswerSectionBy(DNSKEY.class);\n            for (Record<DNSKEY> dnsKeyRecord : dnskeyRrs) {\n                if (dnsKeyRecord.payloadData.getKeyTag() == rrsig.keyTag) {\n                    dnskey = dnsKeyRecord.payloadData;\n                    break;\n                }\n            }\n        }\n\n        if (dnskey == null) {\n            throw new DnssecValidationFailedException(q, records.size() + \" \" + rrsig.typeCovered + \" record(s) are signed using an unknown key.\");\n        }\n\n        DnssecUnverifiedReason unverifiedReason = Verifier.verify(records, rrsig, dnskey);\n        if (unverifiedReason != null) {\n            result.add(unverifiedReason);\n        }\n\n        return result;\n    }\n\n    private Set<DnssecUnverifiedReason> verifySecureEntryPoint(final Record<DNSKEY> sepRecord) throws IOException {\n        final DNSKEY dnskey = sepRecord.payloadData;\n\n        Set<DnssecUnverifiedReason> unverifiedReasons = new HashSet<>();\n        Set<DnssecUnverifiedReason> activeReasons = new HashSet<>();\n        if (knownSeps.containsKey(sepRecord.name)) {\n            if (dnskey.keyEquals(knownSeps.get(sepRecord.name))) {\n                return unverifiedReasons;\n            } else {\n                unverifiedReasons.add(new DnssecUnverifiedReason.ConflictsWithSep(sepRecord));\n                return unverifiedReasons;\n            }\n        }\n\n        // If we are looking for the SEP of the root zone at this point, then the client was not\n        // configured with one. Hence we can abort and state the reason why we aborted.\n        if (sepRecord.name.isRootLabel()) {\n           unverifiedReasons.add(new DnssecUnverifiedReason.NoRootSecureEntryPointReason());\n           return unverifiedReasons;\n        }\n\n        DelegatingDnssecRR delegation = null;\n        DnssecQueryResult dsResp = queryDnssec(sepRecord.name, TYPE.DS);\n        unverifiedReasons.addAll(dsResp.getUnverifiedReasons());\n\n        List<Record<DS>> dsRrs = dsResp.dnsQueryResult.response.filterAnswerSectionBy(DS.class);\n        for (Record<DS> dsRecord : dsRrs) {\n            DS ds = dsRecord.payloadData;\n            if (dnskey.getKeyTag() == ds.keyTag) {\n                delegation = ds;\n                activeReasons = dsResp.getUnverifiedReasons();\n                break;\n            }\n        }\n\n        if (delegation == null) {\n            LOGGER.fine(\"There is no DS record for \\'\" + sepRecord.name + \"\\', server gives empty result\");\n        }\n\n        if (delegation == null && dlv != null && !dlv.isChildOf(sepRecord.name)) {\n            DnssecQueryResult dlvResp = queryDnssec(DnsName.from(sepRecord.name, dlv), TYPE.DLV);\n            unverifiedReasons.addAll(dlvResp.getUnverifiedReasons());\n\n            List<Record<DLV>> dlvRrs = dlvResp.dnsQueryResult.response.filterAnswerSectionBy(DLV.class);\n            for (Record<DLV> dlvRecord : dlvRrs) {\n                if (sepRecord.payloadData.getKeyTag() == dlvRecord.payloadData.keyTag) {\n                    LOGGER.fine(\"Found DLV for \" + sepRecord.name + \", awesome.\");\n                    delegation = dlvRecord.payloadData;\n                    activeReasons = dlvResp.getUnverifiedReasons();\n                    break;\n                }\n            }\n        }\n\n        if (delegation != null) {\n            DnssecUnverifiedReason unverifiedReason = Verifier.verify(sepRecord, delegation);\n            if (unverifiedReason != null) {\n                unverifiedReasons.add(unverifiedReason);\n            } else {\n                unverifiedReasons = activeReasons;\n            }\n        } else if (unverifiedReasons.isEmpty()) {\n            unverifiedReasons.add(new NoTrustAnchorReason(sepRecord.name));\n        }\n        return unverifiedReasons;\n    }\n\n    @Override\n    protected DnsMessage.Builder newQuestion(DnsMessage.Builder message) {\n        message.getEdnsBuilder().setUdpPayloadSize(dataSource.getUdpPayloadSize()).setDnssecOk();\n        message.setCheckingDisabled(true);\n        return super.newQuestion(message);\n    }\n\n    @Override\n    protected String isResponseAcceptable(DnsMessage response) {\n        boolean dnssecOk = response.isDnssecOk();\n        if (!dnssecOk) {\n            // This is a deliberate violation of RFC 6840 § 5.6. I doubt that\n            // \"resolvers MUST ignore the DO bit in responses\" does any good. Also we basically ignore the DO bit after\n            // the fall back to iterative mode.\n            return \"DNSSEC OK (DO) flag not set in response\";\n        }\n        boolean checkingDisabled = response.checkingDisabled;\n        if (!checkingDisabled) {\n            return \"CHECKING DISABLED (CD) flag not set in response\";\n        }\n        return super.isResponseAcceptable(response);\n    }\n\n    /**\n     * Add a new secure entry point to the list of known secure entry points.\n     *\n     * A secure entry point acts as a trust anchor. By default, the only secure entry point is the key signing key\n     * provided by the root zone.\n     *\n     * @param name The domain name originating the key. Once the secure entry point for this domain is requested,\n     *             the resolver will use this key without further verification instead of using the DNS system to\n     *             verify the key.\n     * @param key  The secure entry point corresponding to the domain name. This key can be retrieved by requesting\n     *             the DNSKEY record for the domain and using the key with first flags bit set\n     *             (also called key signing key)\n     */\n    public final void addSecureEntryPoint(DnsName name, byte[] key) {\n        knownSeps.put(name, key);\n    }\n\n    /**\n     * Remove the secure entry point stored for a domain name.\n     *\n     * @param name The domain name of which the corresponding secure entry point shall be removed. For the root zone,\n     *             use the empty string here.\n     */\n    public void removeSecureEntryPoint(DnsName name) {\n        knownSeps.remove(name);\n    }\n\n    /**\n     * Clears the list of known secure entry points.\n     *\n     * This will also remove the secure entry point of the root zone and\n     * thus render this instance useless until a new secure entry point is added.\n     */\n    public void clearSecureEntryPoints() {\n        knownSeps.clear();\n    }\n\n    /**\n     * Whether signature records (RRSIG) are stripped from the resulting {@link DnsMessage}.\n     *\n     * Default is {@code true}.\n     *\n     * @return Whether signature records are stripped.\n     */\n    public boolean isStripSignatureRecords() {\n        return stripSignatureRecords;\n    }\n\n    /**\n     * Enable or disable stripping of signature records (RRSIG) from the result {@link DnsMessage}.\n     * @param stripSignatureRecords Whether signature records shall be stripped.\n     */\n    public void setStripSignatureRecords(boolean stripSignatureRecords) {\n        this.stripSignatureRecords = stripSignatureRecords;\n    }\n\n    /**\n     * Enables DNSSEC Lookaside Validation (DLV) using the default DLV service at dlv.isc.org.\n     */\n    public void enableLookasideValidation() {\n        configureLookasideValidation(DEFAULT_DLV);\n    }\n\n    /**\n     * Disables DNSSEC Lookaside Validation (DLV).\n     * DLV is disabled by default, this is only required if {@link #enableLookasideValidation()} was used before.\n     */\n    public void disableLookasideValidation() {\n        configureLookasideValidation(null);\n    }\n\n    /**\n     * Enables DNSSEC Lookaside Validation (DLV) using the given DLV service.\n     *\n     * @param dlv The domain name of the DLV service to be used or {@code null} to disable DLV.\n     */\n    public void configureLookasideValidation(DnsName dlv) {\n        this.dlv = dlv;\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/DnssecQueryResult.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec;\n\nimport java.util.Collections;\nimport java.util.Set;\n\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.record.RRSIG;\nimport org.minidns.record.Record;\n\npublic class DnssecQueryResult {\n\n    public final DnsMessage synthesizedResponse;\n    public final DnsQueryResult dnsQueryResult;\n\n    private final Set<Record<RRSIG>> signatures;\n    private final Set<DnssecUnverifiedReason> dnssecUnverifiedReasons;\n\n    DnssecQueryResult(DnsMessage synthesizedResponse, DnsQueryResult dnsQueryResult, Set<Record<RRSIG>> signatures,\n            Set<DnssecUnverifiedReason> dnssecUnverifiedReasons) {\n        this.synthesizedResponse = synthesizedResponse;\n        this.dnsQueryResult = dnsQueryResult;\n        this.signatures = Collections.unmodifiableSet(signatures);\n        if (dnssecUnverifiedReasons == null) {\n            this.dnssecUnverifiedReasons = Collections.emptySet();\n        } else {\n            this.dnssecUnverifiedReasons = Collections.unmodifiableSet(dnssecUnverifiedReasons);\n        }\n    }\n\n    public boolean isAuthenticData() {\n        return dnssecUnverifiedReasons.isEmpty();\n    }\n\n    public Set<Record<RRSIG>> getSignatures() {\n        return signatures;\n    }\n\n    public Set<DnssecUnverifiedReason> getUnverifiedReasons() {\n        return dnssecUnverifiedReasons;\n    }\n\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/DnssecResultNotAuthenticException.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec;\n\nimport java.util.Collections;\nimport java.util.Set;\n\nimport org.minidns.MiniDnsException;\n\npublic final class DnssecResultNotAuthenticException extends MiniDnsException {\n\n    /**\n     * \n     */\n    private static final long serialVersionUID = 1L;\n\n    private final Set<DnssecUnverifiedReason> unverifiedReasons;\n\n    private DnssecResultNotAuthenticException(String message, Set<DnssecUnverifiedReason> unverifiedReasons) {\n        super(message);\n        if (unverifiedReasons.isEmpty()) {\n            throw new IllegalArgumentException();\n        }\n        this.unverifiedReasons = Collections.unmodifiableSet(unverifiedReasons);\n    }\n\n    public static DnssecResultNotAuthenticException from(Set<DnssecUnverifiedReason> unverifiedReasons) {\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"DNSSEC result not authentic. Reasons: \");\n        for (DnssecUnverifiedReason reason : unverifiedReasons) {\n            sb.append(reason).append('.');\n        }\n\n        return new DnssecResultNotAuthenticException(sb.toString(), unverifiedReasons);\n    }\n\n    public Set<DnssecUnverifiedReason> getUnverifiedReasons() {\n        return unverifiedReasons;\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/DnssecUnverifiedReason.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec;\n\nimport java.util.Collections;\nimport java.util.List;\n\nimport org.minidns.constants.DnssecConstants.DigestAlgorithm;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.record.DNSKEY;\nimport org.minidns.record.Data;\nimport org.minidns.record.RRSIG;\nimport org.minidns.record.Record;\nimport org.minidns.record.Record.TYPE;\n\npublic abstract class DnssecUnverifiedReason {\n    public abstract String getReasonString();\n\n    @Override\n    public String toString() {\n        return getReasonString();\n    }\n\n    @Override\n    public int hashCode() {\n        return getReasonString().hashCode();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        return obj instanceof DnssecUnverifiedReason && ((DnssecUnverifiedReason) obj).getReasonString().equals(getReasonString());\n    }\n\n    public static class AlgorithmNotSupportedReason extends DnssecUnverifiedReason {\n        private final String algorithm;\n        private final TYPE type;\n        private final Record<? extends Data> record;\n\n        public AlgorithmNotSupportedReason(byte algorithm, TYPE type, Record<? extends Data> record) {\n            this.algorithm = Integer.toString(algorithm & 0xff);\n            this.type = type;\n            this.record = record;\n        }\n\n        @Override\n        public String getReasonString() {\n            return type.name() + \" algorithm \" + algorithm + \" required to verify \" + record.name + \" is unknown or not supported by platform\";\n        }\n    }\n\n    public static class AlgorithmExceptionThrownReason extends DnssecUnverifiedReason {\n        private final int algorithmNumber;\n        private final String kind;\n        private final Exception reason;\n        private final Record<? extends Data> record;\n\n        public AlgorithmExceptionThrownReason(DigestAlgorithm algorithm, String kind, Record<? extends Data> record, Exception reason) {\n            this.algorithmNumber = algorithm.value;\n            this.kind = kind;\n            this.record = record;\n            this.reason = reason;\n        }\n\n        @Override\n        public String getReasonString() {\n            return kind + \" algorithm \" + algorithmNumber + \" threw exception while verifying \" + record.name + \": \" + reason;\n        }\n    }\n\n    public static class ConflictsWithSep extends DnssecUnverifiedReason {\n        private final Record<DNSKEY> record;\n\n        public ConflictsWithSep(Record<DNSKEY> record) {\n            this.record = record;\n        }\n\n        @Override\n        public String getReasonString() {\n            return \"Zone \" + record.name.ace + \" is in list of known SEPs, but DNSKEY from response mismatches!\";\n        }\n    }\n\n    public static class NoTrustAnchorReason extends DnssecUnverifiedReason {\n        private final DnsName zone;\n\n        public NoTrustAnchorReason(DnsName zone) {\n            this.zone = zone;\n        }\n\n        @Override\n        public String getReasonString() {\n            return \"No trust anchor was found for zone \" + zone + \". Try enabling DLV\";\n        }\n    }\n\n    public static class NoSecureEntryPointReason extends DnssecUnverifiedReason {\n        private final DnsName zone;\n\n        public NoSecureEntryPointReason(DnsName zone) {\n            this.zone = zone;\n        }\n\n        @Override\n        public String getReasonString() {\n            return \"No secure entry point was found for zone \" + zone;\n        }\n    }\n\n    public static class NoRootSecureEntryPointReason extends DnssecUnverifiedReason {\n        public NoRootSecureEntryPointReason() {\n        }\n\n        @Override\n        public String getReasonString() {\n            return \"No secure entry point was found for the root zone (\\\"Did you forget to configure a root SEP?\\\")\";\n        }\n    }\n\n    public static class NoSignaturesReason extends DnssecUnverifiedReason {\n        private final Question question;\n\n        public NoSignaturesReason(Question question) {\n            this.question = question;\n        }\n\n        @Override\n        public String getReasonString() {\n            return \"No signatures were attached to answer on question for \" + question.type + \" at \" + question.name;\n        }\n    }\n\n    public static class NoActiveSignaturesReason extends DnssecUnverifiedReason {\n        private final Question question;\n        private final List<RRSIG> outdatedRrSigs;\n\n        public NoActiveSignaturesReason(Question question, List<RRSIG> outdatedRrSigs) {\n            this.question = question;\n            assert !outdatedRrSigs.isEmpty();\n            this.outdatedRrSigs = Collections.unmodifiableList(outdatedRrSigs);\n        }\n\n        @Override\n        public String getReasonString() {\n            return \"No currently active signatures were attached to answer on question for \" + question.type + \" at \" + question.name;\n        }\n\n        public List<RRSIG> getOutdatedRrSigs() {\n            return outdatedRrSigs;\n        }\n    }\n\n    public static class NSECDoesNotMatchReason extends DnssecUnverifiedReason {\n        private final Question question;\n        private final Record<? extends Data> record;\n\n        public NSECDoesNotMatchReason(Question question, Record<? extends Data> record) {\n            this.question = question;\n            this.record = record;\n        }\n\n        @Override\n        public String getReasonString() {\n            return \"NSEC \" + record.name + \" does nat match question for \" + question.type + \" at \" + question.name;\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/DnssecValidationFailedException.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec;\n\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.record.Data;\nimport org.minidns.record.DelegatingDnssecRR;\nimport org.minidns.record.Record;\n\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.security.spec.InvalidKeySpecException;\nimport java.util.List;\nimport java.util.Locale;\n\npublic class DnssecValidationFailedException extends IOException {\n    private static final long serialVersionUID = 5413184667629832742L;\n\n    public DnssecValidationFailedException(Question question, String reason) {\n        super(\"Validation of request to \" + question + \" failed: \" + reason);\n    }\n\n    public DnssecValidationFailedException(String message) {\n        super(message);\n    }\n\n    public DnssecValidationFailedException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public DnssecValidationFailedException(Record<? extends Data> record, String reason) {\n        super(\"Validation of record \" + record + \" failed: \" + reason);\n    }\n\n    public DnssecValidationFailedException(List<Record<? extends Data>> records, String reason) {\n        super(\"Validation of \" + records.size() + \" \" + records.get(0).type + \" record\" + (records.size() > 1 ? \"s\" : \"\") + \" failed: \" + reason);\n    }\n\n    public static class DataMalformedException extends DnssecValidationFailedException {\n\n        /**\n         * \n         */\n        private static final long serialVersionUID = 1L;\n\n        private final byte[] data;\n\n        public DataMalformedException(IOException exception, byte[] data) {\n            super(\"Malformed data\", exception);\n            this.data = data;\n        }\n\n        public DataMalformedException(String message, IOException exception, byte[] data) {\n            super(message, exception);\n            this.data = data;\n        }\n\n        public byte[] getData() {\n            return data;\n        }\n    }\n\n    public static class DnssecInvalidKeySpecException extends DnssecValidationFailedException {\n\n        /**\n         * \n         */\n        private static final long serialVersionUID = 1L;\n\n        public DnssecInvalidKeySpecException(InvalidKeySpecException exception) {\n            super(\"Invalid key spec\", exception);\n        }\n\n        public DnssecInvalidKeySpecException(String message, InvalidKeySpecException exception, byte[] data) {\n            super(message, exception);\n        }\n\n    }\n\n    public static class AuthorityDoesNotContainSoa extends DnssecValidationFailedException {\n\n        /**\n         *\n         */\n        private static final long serialVersionUID = 1L;\n\n        private final DnsMessage response;\n\n        public AuthorityDoesNotContainSoa(DnsMessage response) {\n            super(\"Autority does not contain SOA\");\n            this.response = response;\n        }\n\n        public DnsMessage getResponse() {\n            return response;\n        }\n    }\n\n    public static final class DigestComparisonFailedException extends DnssecValidationFailedException {\n\n        /**\n        *\n        */\n       private static final long serialVersionUID = 1L;\n\n       private final Record<? extends Data> record;\n       private final DelegatingDnssecRR ds;\n       private final byte[] digest;\n       private final String digestHex;\n\n       private DigestComparisonFailedException(String message, Record<? extends Data> record, DelegatingDnssecRR ds, byte[] digest, String digestHex) {\n           super(message);\n           this.record = record;\n           this.ds = ds;\n           this.digest = digest;\n           this.digestHex = digestHex;\n       }\n\n       public Record<? extends Data> getRecord() {\n           return record;\n       }\n\n       public DelegatingDnssecRR getDelegaticDnssecRr() {\n           return ds;\n       }\n\n       public byte[] getDigest() {\n           return digest.clone();\n       }\n\n       public String getDigestHex() {\n           return digestHex;\n       }\n\n       public static DigestComparisonFailedException from(Record<? extends Data> record, DelegatingDnssecRR ds, byte[] digest) {\n           BigInteger digestBigInteger = new BigInteger(1, digest);\n           String digestHex = digestBigInteger.toString(16).toUpperCase(Locale.ROOT);\n\n           String message = \"Digest for \" + record + \" does not match. Digest of delegating DNSSEC RR \" + ds + \" is '\"\n                   + ds.getDigestHex() + \"' while we calculated '\" + digestHex + \"'\";\n           return new DigestComparisonFailedException(message, record, ds, digest, digestHex);\n       }\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/DnssecValidatorInitializationException.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec;\n\npublic class DnssecValidatorInitializationException extends RuntimeException {\n    private static final long serialVersionUID = -1464257268053507791L;\n\n    public DnssecValidatorInitializationException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/SignatureVerifier.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec;\n\nimport org.minidns.record.DNSKEY;\nimport org.minidns.record.RRSIG;\n\npublic interface SignatureVerifier {\n    boolean verify(byte[] content, RRSIG rrsig, DNSKEY key) throws DnssecValidationFailedException;\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/Verifier.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec;\n\nimport org.minidns.dnslabel.DnsLabel;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnssec.DnssecUnverifiedReason.AlgorithmExceptionThrownReason;\nimport org.minidns.dnssec.DnssecUnverifiedReason.AlgorithmNotSupportedReason;\nimport org.minidns.dnssec.DnssecUnverifiedReason.NSECDoesNotMatchReason;\nimport org.minidns.dnssec.DnssecValidationFailedException.DigestComparisonFailedException;\nimport org.minidns.dnssec.algorithms.AlgorithmMap;\nimport org.minidns.record.DNSKEY;\nimport org.minidns.record.Data;\nimport org.minidns.record.DelegatingDnssecRR;\nimport org.minidns.record.NSEC;\nimport org.minidns.record.NSEC3;\nimport org.minidns.record.RRSIG;\nimport org.minidns.record.Record;\nimport org.minidns.util.Base32;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\n\nclass Verifier {\n    private static final AlgorithmMap algorithmMap = AlgorithmMap.INSTANCE;\n\n    public static DnssecUnverifiedReason verify(Record<DNSKEY> dnskeyRecord, DelegatingDnssecRR ds) throws DnssecValidationFailedException {\n        DNSKEY dnskey = dnskeyRecord.payloadData;\n        DigestCalculator digestCalculator = algorithmMap.getDsDigestCalculator(ds.digestType);\n        if (digestCalculator == null) {\n            return new AlgorithmNotSupportedReason(ds.digestTypeByte, ds.getType(), dnskeyRecord);\n        }\n\n        byte[] dnskeyData = dnskey.toByteArray();\n        byte[] dnskeyOwner = dnskeyRecord.name.getBytes();\n        byte[] combined = new byte[dnskeyOwner.length + dnskeyData.length];\n        System.arraycopy(dnskeyOwner, 0, combined, 0, dnskeyOwner.length);\n        System.arraycopy(dnskeyData, 0, combined, dnskeyOwner.length, dnskeyData.length);\n        byte[] digest;\n        try {\n            digest = digestCalculator.digest(combined);\n        } catch (Exception e) {\n            return new AlgorithmExceptionThrownReason(ds.digestType, \"DS\", dnskeyRecord, e);\n        }\n\n        if (!ds.digestEquals(digest)) {\n            throw DigestComparisonFailedException.from(dnskeyRecord, ds, digest);\n        }\n        return null;\n    }\n\n    public static DnssecUnverifiedReason verify(List<Record<? extends Data>> records, RRSIG rrsig, DNSKEY key) throws IOException {\n        SignatureVerifier signatureVerifier = algorithmMap.getSignatureVerifier(rrsig.algorithm);\n        if (signatureVerifier == null) {\n            return new AlgorithmNotSupportedReason(rrsig.algorithmByte, rrsig.getType(), records.get(0));\n        }\n\n        byte[] combine = combine(rrsig, records);\n        if (signatureVerifier.verify(combine, rrsig, key)) {\n            return null;\n        } else {\n            throw new DnssecValidationFailedException(records, \"Signature is invalid.\");\n        }\n    }\n\n    public static DnssecUnverifiedReason verifyNsec(Record<NSEC> nsecRecord, Question q) {\n        NSEC nsec = nsecRecord.payloadData;\n        if (nsecRecord.name.equals(q.name) && !nsec.types.contains(q.type)) {\n            // records with same name but different types exist\n            return null;\n        } else if (nsecMatches(q.name, nsecRecord.name, nsec.next)) {\n            return null;\n        }\n        return new NSECDoesNotMatchReason(q, nsecRecord);\n    }\n\n    public static DnssecUnverifiedReason verifyNsec3(DnsName zone, Record<NSEC3> nsec3record, Question q) {\n        NSEC3 nsec3 = nsec3record.payloadData;\n        DigestCalculator digestCalculator = algorithmMap.getNsecDigestCalculator(nsec3.hashAlgorithm);\n        if (digestCalculator == null) {\n            return new AlgorithmNotSupportedReason(nsec3.hashAlgorithmByte, nsec3.getType(), nsec3record);\n        }\n\n        byte[] bytes = nsec3hash(digestCalculator, nsec3, q.name, nsec3.iterations);\n        String s = Base32.encodeToString(bytes);\n        DnsName computedNsec3Record = DnsName.from(s + \".\" + zone);\n        if (nsec3record.name.equals(computedNsec3Record)) {\n            if (nsec3.types.contains(q.type)) {\n                // TODO: Refine exception thrown in this case.\n                return new NSECDoesNotMatchReason(q, nsec3record);\n            }\n            return null;\n        }\n        if (nsecMatches(s, nsec3record.name.getHostpart(), Base32.encodeToString(nsec3.getNextHashed()))) {\n            return null;\n        }\n        return new NSECDoesNotMatchReason(q, nsec3record);\n    }\n\n    static byte[] combine(RRSIG rrsig, List<Record<? extends Data>> records) {\n        ByteArrayOutputStream bos = new ByteArrayOutputStream();\n        DataOutputStream dos = new DataOutputStream(bos);\n\n        // Write RRSIG without signature\n        try {\n            rrsig.writePartialSignature(dos);\n\n            DnsName sigName = records.get(0).name;\n            if (!sigName.isRootLabel()) {\n                if (sigName.getLabelCount() < rrsig.labels) {\n                    // TODO: This is currently not covered by the unit tests.\n                    throw new DnssecValidationFailedException(\"Invalid RRsig record\");\n                }\n\n                if (sigName.getLabelCount() > rrsig.labels) {\n                    // TODO: This is currently not covered by the unit tests.\n                    // Expand wildcards\n                    sigName = DnsName.from(DnsLabel.WILDCARD_LABEL, sigName.stripToLabels(rrsig.labels));\n                }\n            }\n\n            List<byte[]> recordBytes = new ArrayList<>(records.size());\n            for (Record<? extends Data> record : records) {\n                Record<Data> ref = new Record<Data>(sigName, record.type, record.clazzValue, rrsig.originalTtl, record.payloadData);\n                recordBytes.add(ref.toByteArray());\n            }\n\n            // Sort correctly (cause they might be ordered randomly) as per RFC 4034 § 6.3.\n            final int offset = sigName.size() + 10; // Where the RDATA begins\n            Collections.sort(recordBytes, new Comparator<byte[]>() {\n                @Override\n                public int compare(byte[] b1, byte[] b2) {\n                    for (int i = offset; i < b1.length && i < b2.length; i++) {\n                        if (b1[i] != b2[i]) {\n                            return (b1[i] & 0xFF) - (b2[i] & 0xFF);\n                        }\n                    }\n                    return b1.length - b2.length;\n                }\n            });\n\n            for (byte[] recordByte : recordBytes) {\n                dos.write(recordByte);\n            }\n            dos.flush();\n        } catch (IOException e) {\n            // Never happens\n            throw new RuntimeException(e);\n        }\n        return bos.toByteArray();\n    }\n\n    static boolean nsecMatches(String test, String lowerBound, String upperBound) {\n        return nsecMatches(DnsName.from(test), DnsName.from(lowerBound), DnsName.from(upperBound));\n    }\n\n    /**\n     * Tests if a nsec domain name is part of an NSEC record.\n     *\n     * @param test       test domain name\n     * @param lowerBound inclusive lower bound\n     * @param upperBound exclusive upper bound\n     * @return test domain name is covered by NSEC record\n     */\n    static boolean nsecMatches(DnsName test, DnsName lowerBound, DnsName upperBound) {\n        int lowerParts = lowerBound.getLabelCount();\n        int upperParts = upperBound.getLabelCount();\n        int testParts = test.getLabelCount();\n\n        if (testParts > lowerParts && !test.isChildOf(lowerBound) && test.stripToLabels(lowerParts).compareTo(lowerBound) < 0)\n            return false;\n        if (testParts <= lowerParts && test.compareTo(lowerBound.stripToLabels(testParts)) < 0)\n            return false;\n\n        if (testParts > upperParts && !test.isChildOf(upperBound) && test.stripToLabels(upperParts).compareTo(upperBound) > 0)\n            return false;\n        if (testParts <= upperParts && test.compareTo(upperBound.stripToLabels(testParts)) >= 0)\n            return false;\n\n        return true;\n    }\n\n    static byte[] nsec3hash(DigestCalculator digestCalculator, NSEC3 nsec3, DnsName ownerName, int iterations) {\n        return nsec3hash(digestCalculator, nsec3.getSalt(), ownerName.getBytes(), iterations);\n    }\n\n    /**\n     * Derived from RFC 5155 Section 5.\n     *\n     * @param digestCalculator the digest calculator.\n     * @param salt the salt.\n     * @param data the data.\n     * @param iterations the number of iterations.\n     * @return the NSEC3 hash.\n     */\n    static byte[] nsec3hash(DigestCalculator digestCalculator, byte[] salt, byte[] data, int iterations) {\n        while (iterations-- >= 0) {\n            byte[] combined = new byte[data.length + salt.length];\n            System.arraycopy(data, 0, combined, 0, data.length);\n            System.arraycopy(salt, 0, combined, data.length, salt.length);\n            data = digestCalculator.digest(combined);\n        }\n        return data;\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/algorithms/AlgorithmMap.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec.algorithms;\n\nimport org.minidns.constants.DnssecConstants.DigestAlgorithm;\nimport org.minidns.constants.DnssecConstants.SignatureAlgorithm;\nimport org.minidns.dnssec.DnssecValidatorInitializationException;\nimport org.minidns.dnssec.DigestCalculator;\nimport org.minidns.dnssec.SignatureVerifier;\nimport org.minidns.record.NSEC3.HashAlgorithm;\n\nimport java.security.NoSuchAlgorithmException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\npublic final class AlgorithmMap {\n    private Logger LOGGER = Logger.getLogger(AlgorithmMap.class.getName());\n\n    public static final AlgorithmMap INSTANCE = new AlgorithmMap();\n\n    private final Map<DigestAlgorithm, DigestCalculator> dsDigestMap = new HashMap<>();\n    private final Map<SignatureAlgorithm, SignatureVerifier> signatureMap = new HashMap<>();\n    private final Map<HashAlgorithm, DigestCalculator> nsecDigestMap = new HashMap<>();\n\n    @SuppressWarnings(\"deprecation\")\n    private AlgorithmMap() {\n        try {\n            dsDigestMap.put(DigestAlgorithm.SHA1, new JavaSecDigestCalculator(\"SHA-1\"));\n            nsecDigestMap.put(HashAlgorithm.SHA1, new JavaSecDigestCalculator(\"SHA-1\"));\n        } catch (NoSuchAlgorithmException e) {\n            // SHA-1 is MANDATORY\n            throw new DnssecValidatorInitializationException(\"SHA-1 is mandatory\", e);\n        }\n        try {\n            dsDigestMap.put(DigestAlgorithm.SHA256, new JavaSecDigestCalculator(\"SHA-256\"));\n        } catch (NoSuchAlgorithmException e) {\n            // SHA-256 is MANDATORY\n            throw new DnssecValidatorInitializationException(\"SHA-256 is mandatory\", e);\n        }\n\n        try {\n            dsDigestMap.put(DigestAlgorithm.SHA384, new JavaSecDigestCalculator(\"SHA-384\"));\n        } catch (NoSuchAlgorithmException e) {\n            // SHA-384 is OPTIONAL\n            LOGGER.log(Level.FINE, \"Platform does not support SHA-384\", e);\n        }\n\n        try {\n            signatureMap.put(SignatureAlgorithm.RSAMD5, new RsaSignatureVerifier(\"MD5withRSA\"));\n        } catch (NoSuchAlgorithmException e) {\n            // RSA/MD5 is DEPRECATED\n            LOGGER.log(Level.FINER, \"Platform does not support RSA/MD5\", e);\n        }\n        try {\n            DsaSignatureVerifier sha1withDSA = new DsaSignatureVerifier(\"SHA1withDSA\");\n            signatureMap.put(SignatureAlgorithm.DSA, sha1withDSA);\n            signatureMap.put(SignatureAlgorithm.DSA_NSEC3_SHA1, sha1withDSA);\n        } catch (NoSuchAlgorithmException e) {\n            // DSA/SHA-1 is OPTIONAL\n            LOGGER.log(Level.FINE, \"Platform does not support DSA/SHA-1\", e);\n        }\n        try {\n            RsaSignatureVerifier sha1withRSA = new RsaSignatureVerifier(\"SHA1withRSA\");\n            signatureMap.put(SignatureAlgorithm.RSASHA1, sha1withRSA);\n            signatureMap.put(SignatureAlgorithm.RSASHA1_NSEC3_SHA1, sha1withRSA);\n        } catch (NoSuchAlgorithmException e) {\n            throw new DnssecValidatorInitializationException(\"Platform does not support RSA/SHA-1\", e);\n        }\n        try {\n            signatureMap.put(SignatureAlgorithm.RSASHA256, new RsaSignatureVerifier(\"SHA256withRSA\"));\n        } catch (NoSuchAlgorithmException e) {\n            // RSA/SHA-256 is RECOMMENDED\n            LOGGER.log(Level.INFO, \"Platform does not support RSA/SHA-256\", e);\n        }\n        try {\n            signatureMap.put(SignatureAlgorithm.RSASHA512, new RsaSignatureVerifier(\"SHA512withRSA\"));\n        } catch (NoSuchAlgorithmException e) {\n            // RSA/SHA-512 is RECOMMENDED\n            LOGGER.log(Level.INFO, \"Platform does not support RSA/SHA-512\", e);\n        }\n        try {\n            signatureMap.put(SignatureAlgorithm.ECC_GOST, new EcgostSignatureVerifier());\n        } catch (NoSuchAlgorithmException e) {\n            // GOST R 34.10-2001 is OPTIONAL\n            LOGGER.log(Level.FINE, \"Platform does not support GOST R 34.10-2001\", e);\n        }\n        try {\n            signatureMap.put(SignatureAlgorithm.ECDSAP256SHA256, new EcdsaSignatureVerifier.P256SHA256());\n        } catch (NoSuchAlgorithmException e) {\n            // ECDSA/SHA-256 is RECOMMENDED\n            LOGGER.log(Level.INFO, \"Platform does not support ECDSA/SHA-256\", e);\n        }\n        try {\n            signatureMap.put(SignatureAlgorithm.ECDSAP384SHA384, new EcdsaSignatureVerifier.P384SHA284());\n        } catch (NoSuchAlgorithmException e) {\n            // ECDSA/SHA-384 is RECOMMENDED\n            LOGGER.log(Level.INFO, \"Platform does not support ECDSA/SHA-384\", e);\n        }\n    }\n\n    public DigestCalculator getDsDigestCalculator(DigestAlgorithm algorithm) {\n        return dsDigestMap.get(algorithm);\n    }\n\n    public SignatureVerifier getSignatureVerifier(SignatureAlgorithm algorithm) {\n        return signatureMap.get(algorithm);\n    }\n\n    public DigestCalculator getNsecDigestCalculator(HashAlgorithm algorithm) {\n        return nsecDigestMap.get(algorithm);\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/algorithms/DsaSignatureVerifier.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec.algorithms;\n\nimport org.minidns.dnssec.DnssecValidationFailedException.DnssecInvalidKeySpecException;\nimport org.minidns.record.DNSKEY;\nimport org.minidns.record.RRSIG;\nimport org.minidns.dnssec.DnssecValidationFailedException.DataMalformedException;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.DataInput;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.PublicKey;\nimport java.security.spec.DSAPublicKeySpec;\nimport java.security.spec.InvalidKeySpecException;\n\nclass DsaSignatureVerifier extends JavaSecSignatureVerifier {\n    private static final int LENGTH = 20;\n\n    DsaSignatureVerifier(String algorithm) throws NoSuchAlgorithmException {\n        super(\"DSA\", algorithm);\n    }\n\n    @Override\n    protected byte[] getSignature(RRSIG rrsig) throws DataMalformedException {\n        DataInput dis = rrsig.getSignatureAsDataInputStream();\n\n        ByteArrayOutputStream bos;\n        try {\n            // Convert RFC 2536 to ASN.1\n            @SuppressWarnings(\"unused\")\n            byte t = dis.readByte();\n\n            byte[] r = new byte[LENGTH];\n            dis.readFully(r);\n            int roff = 0;\n            final int rlen;\n            if (r[0] == 0) {\n                while (roff < LENGTH && r[roff] == 0) {\n                    roff++;\n                }\n                rlen = r.length - roff;\n            } else if (r[0] < 0) {\n                rlen = r.length + 1;\n            } else {\n                rlen = r.length;\n            }\n\n            byte[] s = new byte[LENGTH];\n            dis.readFully(s);\n            int soff = 0;\n            final int slen;\n            if (s[0] == 0) {\n                while (soff < LENGTH && s[soff] == 0) {\n                    soff++;\n                }\n                slen = s.length - soff;\n            } else if (s[0] < 0) {\n                slen = s.length + 1;\n            } else {\n                slen = s.length;\n            }\n\n            bos = new ByteArrayOutputStream();\n            DataOutputStream dos = new DataOutputStream(bos);\n\n            dos.writeByte(0x30);\n            dos.writeByte(rlen + slen + 4);\n\n            dos.writeByte(0x2);\n            dos.writeByte(rlen);\n            if (rlen > LENGTH)\n                dos.writeByte(0);\n            dos.write(r, roff, LENGTH - roff);\n\n            dos.writeByte(0x2);\n            dos.writeByte(slen);\n            if (slen > LENGTH)\n                dos.writeByte(0);\n            dos.write(s, soff, LENGTH - soff);\n        } catch (IOException e) {\n            throw new DataMalformedException(e, rrsig.getSignature());\n        }\n\n        return bos.toByteArray();\n    }\n\n    @Override\n    protected PublicKey getPublicKey(DNSKEY key) throws DataMalformedException, DnssecInvalidKeySpecException {\n        DataInput dis = key.getKeyAsDataInputStream();\n        BigInteger subPrime, prime, base, pubKey;\n\n        try {\n            int t = dis.readUnsignedByte();\n\n            byte[] subPrimeBytes = new byte[LENGTH];\n            dis.readFully(subPrimeBytes);\n            subPrime = new BigInteger(1, subPrimeBytes);\n\n            byte[] primeBytes = new byte[64 + t * 8];\n            dis.readFully(primeBytes);\n            prime = new BigInteger(1, primeBytes);\n\n            byte[] baseBytes = new byte[64 + t * 8];\n            dis.readFully(baseBytes);\n            base = new BigInteger(1, baseBytes);\n\n            byte[] pubKeyBytes = new byte[64 + t * 8];\n            dis.readFully(pubKeyBytes);\n            pubKey = new BigInteger(1, pubKeyBytes);\n        } catch (IOException e) {\n            throw new DataMalformedException(e, key.getKey());\n        }\n\n        try {\n            return getKeyFactory().generatePublic(new DSAPublicKeySpec(pubKey, prime, subPrime, base));\n        } catch (InvalidKeySpecException e) {\n            throw new DnssecInvalidKeySpecException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/algorithms/EcdsaSignatureVerifier.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec.algorithms;\n\nimport org.minidns.dnssec.DnssecValidationFailedException.DataMalformedException;\nimport org.minidns.dnssec.DnssecValidationFailedException.DnssecInvalidKeySpecException;\nimport org.minidns.record.DNSKEY;\nimport org.minidns.record.RRSIG;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.DataInput;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.PublicKey;\nimport java.security.spec.ECFieldFp;\nimport java.security.spec.ECParameterSpec;\nimport java.security.spec.ECPoint;\nimport java.security.spec.ECPublicKeySpec;\nimport java.security.spec.EllipticCurve;\nimport java.security.spec.InvalidKeySpecException;\n\nabstract class EcdsaSignatureVerifier extends JavaSecSignatureVerifier {\n    private final ECParameterSpec spec;\n    private final int length;\n\n    EcdsaSignatureVerifier(BigInteger[] spec, int length, String algorithm) throws NoSuchAlgorithmException {\n        this(new ECParameterSpec(new EllipticCurve(new ECFieldFp(spec[0]), spec[1], spec[2]), new ECPoint(spec[3], spec[4]), spec[5], 1), length, algorithm);\n    }\n\n    EcdsaSignatureVerifier(ECParameterSpec spec, int length, String algorithm) throws NoSuchAlgorithmException {\n        super(\"EC\", algorithm);\n        this.length = length;\n        this.spec = spec;\n    }\n\n    @Override\n    protected byte[] getSignature(RRSIG rrsig) throws DataMalformedException {\n        DataInput dis = rrsig.getSignatureAsDataInputStream();\n        ByteArrayOutputStream bos = new ByteArrayOutputStream();\n        DataOutputStream dos = new DataOutputStream(bos);\n\n        try {\n            byte[] r = new byte[length];\n            dis.readFully(r);\n            int rlen = (r[0] < 0) ? length + 1 : length;\n\n            byte[] s = new byte[length];\n            dis.readFully(s);\n            int slen = (s[0] < 0) ? length + 1 : length;\n\n            dos.writeByte(0x30);\n            dos.writeByte(rlen + slen + 4);\n\n            dos.writeByte(0x2);\n            dos.writeByte(rlen);\n            if (rlen > length) dos.writeByte(0);\n            dos.write(r);\n\n            dos.writeByte(0x2);\n            dos.writeByte(slen);\n            if (slen > length) dos.writeByte(0);\n            dos.write(s);\n        } catch (IOException e) {\n            throw new DataMalformedException(e, rrsig.getSignature());\n        }\n\n        return bos.toByteArray();\n    }\n\n    @Override\n    protected PublicKey getPublicKey(DNSKEY key) throws DataMalformedException, DnssecInvalidKeySpecException {\n        DataInput dis = key.getKeyAsDataInputStream();\n        BigInteger x, y;\n\n        try {\n            byte[] xBytes = new byte[length];\n            dis.readFully(xBytes);\n            x = new BigInteger(1, xBytes);\n\n            byte[] yBytes = new byte[length];\n            dis.readFully(yBytes);\n            y = new BigInteger(1, yBytes);\n        } catch (IOException e) {\n            throw new DataMalformedException(e, key.getKey());\n        }\n\n        try {\n            return getKeyFactory().generatePublic(new ECPublicKeySpec(new ECPoint(x, y), spec));\n        } catch (InvalidKeySpecException e) {\n            throw new DnssecInvalidKeySpecException(e);\n        }\n    }\n\n    public static class P256SHA256 extends EcdsaSignatureVerifier {\n        private static BigInteger[] SPEC = {\n                new BigInteger(\"FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF\", 16),\n                new BigInteger(\"FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC\", 16),\n                new BigInteger(\"5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B\", 16),\n                new BigInteger(\"6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296\", 16),\n                new BigInteger(\"4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5\", 16),\n                new BigInteger(\"FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551\", 16)\n        };\n\n        P256SHA256() throws NoSuchAlgorithmException {\n            super(SPEC, 32, \"SHA256withECDSA\");\n        }\n    }\n\n    public static class P384SHA284 extends EcdsaSignatureVerifier {\n        private static BigInteger[] SPEC = {\n                new BigInteger(\"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF\", 16),\n                new BigInteger(\"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC\", 16),\n                new BigInteger(\"B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF\", 16),\n                new BigInteger(\"AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7\", 16),\n                new BigInteger(\"3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F\", 16),\n                new BigInteger(\"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973\", 16)\n        };\n\n        P384SHA284() throws NoSuchAlgorithmException {\n            super(SPEC, 48, \"SHA384withECDSA\");\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/algorithms/EcgostSignatureVerifier.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec.algorithms;\n\nimport org.minidns.dnssec.DnssecValidationFailedException.DnssecInvalidKeySpecException;\nimport org.minidns.record.DNSKEY;\nimport org.minidns.record.RRSIG;\nimport org.minidns.dnssec.DnssecValidationFailedException.DataMalformedException;\n\nimport java.io.DataInput;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.PublicKey;\nimport java.security.spec.ECFieldFp;\nimport java.security.spec.ECParameterSpec;\nimport java.security.spec.ECPoint;\nimport java.security.spec.ECPublicKeySpec;\nimport java.security.spec.EllipticCurve;\nimport java.security.spec.InvalidKeySpecException;\n\nclass EcgostSignatureVerifier extends JavaSecSignatureVerifier {\n    private static final int LENGTH = 32;\n    private static final ECParameterSpec SPEC = new ECParameterSpec(\n            new EllipticCurve(\n                    new ECFieldFp(new BigInteger(\"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD97\", 16)),\n                    new BigInteger(\"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD94\", 16),\n                    new BigInteger(\"A6\", 16)\n            ),\n            new ECPoint(BigInteger.ONE, new BigInteger(\"8D91E471E0989CDA27DF505A453F2B7635294F2DDF23E3B122ACC99C9E9F1E14\", 16)),\n            new BigInteger(\"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C611070995AD10045841B09B761B893\", 16),\n            1\n    );\n\n    EcgostSignatureVerifier() throws NoSuchAlgorithmException {\n        super(\"ECGOST3410\", \"GOST3411withECGOST3410\");\n    }\n\n    @Override\n    protected byte[] getSignature(RRSIG rrsig) {\n        return rrsig.getSignature();\n    }\n\n    @Override\n    protected PublicKey getPublicKey(DNSKEY key) throws DataMalformedException, DnssecInvalidKeySpecException {\n        DataInput dis = key.getKeyAsDataInputStream();\n        BigInteger x, y;\n\n        try {\n            byte[] xBytes = new byte[LENGTH];\n            dis.readFully(xBytes);\n            reverse(xBytes);\n            x = new BigInteger(1, xBytes);\n\n            byte[] yBytes = new byte[LENGTH];\n            dis.readFully(yBytes);\n            reverse(yBytes);\n            y = new BigInteger(1, yBytes);\n        } catch (IOException e) {\n            throw new DataMalformedException(e, key.getKey());\n        }\n\n        try {\n            return getKeyFactory().generatePublic(new ECPublicKeySpec(new ECPoint(x, y), SPEC));\n        } catch (InvalidKeySpecException e) {\n            throw new DnssecInvalidKeySpecException(e);\n        }\n    }\n\n    private static void reverse(byte[] array) {\n        for (int i = 0; i < array.length / 2; i++) {\n            int j = array.length - i - 1;\n            byte tmp = array[i];\n            array[i] = array[j];\n            array[j] = tmp;\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/algorithms/JavaSecDigestCalculator.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec.algorithms;\n\nimport org.minidns.dnssec.DigestCalculator;\n\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\n\npublic class JavaSecDigestCalculator implements DigestCalculator {\n    private MessageDigest md;\n\n    public JavaSecDigestCalculator(String algorithm) throws NoSuchAlgorithmException {\n        md = MessageDigest.getInstance(algorithm);\n    }\n\n    @Override\n    public byte[] digest(byte[] bytes) {\n        return md.digest(bytes);\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/algorithms/JavaSecSignatureVerifier.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec.algorithms;\n\nimport org.minidns.dnssec.DnssecValidationFailedException;\nimport org.minidns.dnssec.DnssecValidationFailedException.DataMalformedException;\nimport org.minidns.dnssec.DnssecValidationFailedException.DnssecInvalidKeySpecException;\nimport org.minidns.dnssec.SignatureVerifier;\nimport org.minidns.record.DNSKEY;\nimport org.minidns.record.RRSIG;\n\nimport java.security.InvalidKeyException;\nimport java.security.KeyFactory;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.PublicKey;\nimport java.security.Signature;\nimport java.security.SignatureException;\n\npublic abstract class JavaSecSignatureVerifier implements SignatureVerifier {\n    private final KeyFactory keyFactory;\n    private final String signatureAlgorithm;\n\n    public JavaSecSignatureVerifier(String keyAlgorithm, String signatureAlgorithm) throws NoSuchAlgorithmException {\n        keyFactory = KeyFactory.getInstance(keyAlgorithm);\n        this.signatureAlgorithm = signatureAlgorithm;\n\n        // Verify signature algorithm to be valid\n        Signature.getInstance(signatureAlgorithm);\n    }\n\n    public KeyFactory getKeyFactory() {\n        return keyFactory;\n    }\n\n    @Override\n    public boolean verify(byte[] content, RRSIG rrsig, DNSKEY key) throws DnssecValidationFailedException {\n        try {\n            PublicKey publicKey = getPublicKey(key);\n            Signature signature = Signature.getInstance(signatureAlgorithm);\n            signature.initVerify(publicKey);\n            signature.update(content);\n            return signature.verify(getSignature(rrsig));\n        } catch (NoSuchAlgorithmException e) {\n            // We checked against this before, it should never happen!\n            throw new AssertionError(e);\n        } catch (InvalidKeyException | SignatureException | ArithmeticException e) {\n            throw new DnssecValidationFailedException(\"Validating signature failed\", e);\n        }\n    }\n\n    protected abstract byte[] getSignature(RRSIG rrsig) throws DataMalformedException;\n\n    protected abstract PublicKey getPublicKey(DNSKEY key) throws DataMalformedException, DnssecInvalidKeySpecException;\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/java/org/minidns/dnssec/algorithms/RsaSignatureVerifier.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec.algorithms;\n\nimport org.minidns.dnssec.DnssecValidationFailedException.DnssecInvalidKeySpecException;\nimport org.minidns.record.DNSKEY;\nimport org.minidns.record.RRSIG;\nimport org.minidns.dnssec.DnssecValidationFailedException.DataMalformedException;\n\nimport java.io.DataInput;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.PublicKey;\nimport java.security.spec.InvalidKeySpecException;\nimport java.security.spec.RSAPublicKeySpec;\n\nclass RsaSignatureVerifier extends JavaSecSignatureVerifier {\n    RsaSignatureVerifier(String algorithm) throws NoSuchAlgorithmException {\n        super(\"RSA\", algorithm);\n    }\n\n    @Override\n    protected PublicKey getPublicKey(DNSKEY key) throws DataMalformedException, DnssecInvalidKeySpecException {\n        DataInput dis = key.getKeyAsDataInputStream();\n        BigInteger exponent, modulus;\n\n        try {\n            int exponentLength = dis.readUnsignedByte();\n            int bytesRead = 1;\n            if (exponentLength == 0) {\n                bytesRead += 2;\n                exponentLength = dis.readUnsignedShort();\n            }\n\n            byte[] exponentBytes = new byte[exponentLength];\n            dis.readFully(exponentBytes);\n            bytesRead += exponentLength;\n            exponent = new BigInteger(1, exponentBytes);\n\n            byte[] modulusBytes = new byte[key.getKeyLength() - bytesRead];\n            dis.readFully(modulusBytes);\n            modulus = new BigInteger(1, modulusBytes);\n        } catch (IOException e) {\n            throw new DataMalformedException(e, key.getKey());\n        }\n\n        try {\n            return getKeyFactory().generatePublic(new RSAPublicKeySpec(modulus, exponent));\n        } catch (InvalidKeySpecException e) {\n            throw new DnssecInvalidKeySpecException(e);\n        }\n    }\n\n    @Override\n    protected byte[] getSignature(RRSIG rrsig) {\n        return rrsig.getSignature();\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/main/resources/.keep-minidns-dnssec-main-resources",
    "content": ""
  },
  {
    "path": "minidns-dnssec/src/test/java/org/minidns/dnssec/DnssecClientTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec;\n\nimport org.minidns.DnsWorld;\nimport org.minidns.cache.LruCache;\nimport org.minidns.constants.DnssecConstants.DigestAlgorithm;\nimport org.minidns.constants.DnssecConstants.SignatureAlgorithm;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnssec.DnssecValidationFailedException.AuthorityDoesNotContainSoa;\nimport org.minidns.dnssec.DnssecWorld.DnssecData;\nimport org.minidns.iterative.ReliableDnsClient.Mode;\nimport org.minidns.record.A;\nimport org.minidns.record.DNSKEY;\nimport org.minidns.record.Data;\nimport org.minidns.record.RRSIG;\nimport org.minidns.record.Record;\nimport org.minidns.record.Record.TYPE;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.lang.reflect.Field;\nimport java.net.InetAddress;\nimport java.security.PrivateKey;\nimport java.util.Date;\nimport java.util.List;\n\nimport static org.minidns.DnsWorld.a;\nimport static org.minidns.DnsWorld.applyZones;\nimport static org.minidns.DnsWorld.dnskey;\nimport static org.minidns.DnsWorld.ns;\nimport static org.minidns.DnsWorld.nsec;\nimport static org.minidns.DnsWorld.record;\nimport static org.minidns.DnsWorld.rootZone;\nimport static org.minidns.DnsWorld.rrsig;\nimport static org.minidns.DnsWorld.soa;\nimport static org.minidns.DnsWorld.zone;\nimport static org.minidns.dnssec.DnssecWorld.addNsec;\nimport static org.minidns.dnssec.DnssecWorld.dlv;\nimport static org.minidns.dnssec.DnssecWorld.ds;\nimport static org.minidns.dnssec.DnssecWorld.publicKey;\nimport static org.minidns.dnssec.DnssecWorld.rrsigRecord;\nimport static org.minidns.dnssec.DnssecWorld.selfSignDnskeyRrSet;\nimport static org.minidns.dnssec.DnssecWorld.sign;\nimport static org.minidns.dnssec.DnssecWorld.signedRootZone;\nimport static org.minidns.dnssec.DnssecWorld.signedZone;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\n// TODO: Make selfSignDnskeyRrset() part of signedZone() and remove it from all tests\n\npublic class DnssecClientTest {\n    private static SignatureAlgorithm algorithm = SignatureAlgorithm.RSASHA256;\n    private static DigestAlgorithm digestType = DigestAlgorithm.SHA1;\n    private static PrivateKey rootPrivateKSK;\n    private static DNSKEY rootKSK;\n    private static PrivateKey rootPrivateZSK;\n    private static DNSKEY rootZSK;\n    private static DNSKEY comKSK;\n    private static DNSKEY comZSK;\n    private static PrivateKey comPrivateZSK;\n    private static PrivateKey comPrivateKSK;\n\n    static {\n        DnssecData rootDnssecData = DnssecWorld.getDnssecDataFor(\"\");\n        rootPrivateKSK = rootDnssecData.privateKsk;\n        rootKSK = rootDnssecData.ksk;\n        rootPrivateZSK = rootDnssecData.privateZsk;\n        rootZSK = rootDnssecData.zsk;\n\n        DnssecData comDnssecData = DnssecWorld.getDnssecDataFor(\"com\");\n        comPrivateKSK = comDnssecData.privateKsk;\n        comKSK = comDnssecData.ksk;\n        comPrivateZSK = comDnssecData.privateZsk;\n        comZSK = comDnssecData.zsk;\n    }\n\n    public static DnssecClient constructDnssecClient() {\n        DnssecClient client = new DnssecClient(new LruCache(0));\n        client.addSecureEntryPoint(DnsName.ROOT, rootKSK.getKey());\n        client.setMode(Mode.iterativeOnly);\n        return client;\n    }\n\n    void checkCorrectExampleMessage(DnsMessage message) {\n        List<Record<? extends Data>> answers = message.answerSection;\n        assertEquals(1, answers.size());\n        assertEquals(Record.TYPE.A, answers.get(0).type);\n        assertArrayEquals(new byte[] {1, 1, 1, 2}, ((A) answers.get(0).payloadData).getIp());\n    }\n\n    @Test\n    public void testBasicValid() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ds(\"com\", digestType, comKSK))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), signedZone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        sign(comKSK, \"com\", comPrivateKSK, algorithm,\n                                record(\"com\", comKSK),\n                                record(\"com\", comZSK)),\n                        sign(comZSK, \"com\", comPrivateZSK, algorithm,\n                                record(\"example.com\", a(\"1.1.1.2\")))\n                )\n        );\n        DnssecQueryResult result = client.queryDnssec(\"example.com\", Record.TYPE.A);\n        assertTrue(result.isAuthenticData());\n        DnsMessage message = result.synthesizedResponse;\n        checkCorrectExampleMessage(message);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testNoSEPAtKSK() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        DNSKEY comKSK = dnskey(DNSKEY.FLAG_ZONE, algorithm, publicKey(algorithm, comPrivateKSK));\n        applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ds(\"com\", digestType, comKSK))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), signedZone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        sign(comKSK, \"com\", comPrivateKSK, algorithm,\n                                record(\"com\", comKSK),\n                                record(\"com\", comZSK)),\n                        sign(comZSK, \"com\", comPrivateZSK, algorithm,\n                                record(\"example.com\", a(\"1.1.1.2\")))\n                )\n        );\n        DnssecQueryResult result = client.queryDnssec(\"example.com\", Record.TYPE.A);\n        assertTrue(result.isAuthenticData());\n        DnsMessage message = result.synthesizedResponse;\n        checkCorrectExampleMessage(message);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testSingleZSK() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ds(\"com\", digestType, comKSK))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), signedZone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        sign(comKSK, \"com\", comPrivateKSK, algorithm,\n                                record(\"com\", comKSK)),\n                        sign(comKSK, \"com\", comPrivateKSK, algorithm,\n                                record(\"example.com\", a(\"1.1.1.2\")))\n                )\n        );\n        DnssecQueryResult result = client.queryDnssec(\"example.com\", Record.TYPE.A);\n        assertTrue(result.isAuthenticData());\n        DnsMessage message = result.synthesizedResponse;\n        checkCorrectExampleMessage(message);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testMissingDelegation() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), signedZone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        sign(comKSK, \"com\", comPrivateKSK, algorithm,\n                                record(\"com\", comKSK),\n                                record(\"com\", comZSK)),\n                        sign(comZSK, \"com\", comPrivateZSK, algorithm,\n                                record(\"example.com\", a(\"1.1.1.2\")))\n                )\n        );\n\n        assertThrows(AuthorityDoesNotContainSoa.class, () ->\n            client.queryDnssec(\"example.com\", Record.TYPE.A)\n        );\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testUnsignedRoot() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        applyZones(client,\n                rootZone(\n                        record(\"com\", ds(\"com\", digestType, comKSK)),\n                        record(\"com\", ns(\"ns.com\")),\n                        record(\"ns.com\", a(\"1.1.1.1\"))\n                ), signedZone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        sign(comKSK, \"com\", comPrivateKSK, algorithm,\n                                record(\"com\", comKSK),\n                                record(\"com\", comZSK)),\n                        sign(comZSK, \"com\", comPrivateZSK, algorithm,\n                                record(\"example.com\", a(\"1.1.1.2\")))\n                )\n        );\n        DnssecQueryResult result = client.queryDnssec(\"example.com\", Record.TYPE.A);\n        assertFalse(result.isAuthenticData());\n        DnsMessage message = result.synthesizedResponse;\n        checkCorrectExampleMessage(message);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testNoRootSecureEntryPoint() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        client.clearSecureEntryPoints();\n        applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ds(\"com\", digestType, comKSK))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), signedZone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        sign(comKSK, \"com\", comPrivateKSK, algorithm,\n                                record(\"com\", comKSK),\n                                record(\"com\", comZSK)),\n                        sign(comZSK, \"com\", comPrivateZSK, algorithm,\n                                record(\"example.com\", a(\"1.1.1.2\")))\n                )\n        );\n        DnssecQueryResult result = client.queryDnssec(\"example.com\", Record.TYPE.A);\n        assertFalse(result.isAuthenticData());\n        DnsMessage message = result.synthesizedResponse;\n        checkCorrectExampleMessage(message);\n        assertEquals(1, result.getUnverifiedReasons().size());\n        assertTrue(result.getUnverifiedReasons().iterator().next() instanceof DnssecUnverifiedReason.NoRootSecureEntryPointReason);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testUnsignedZone() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ds(\"com\", digestType, comKSK))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), zone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        record(\"example.com\", a(\"1.1.1.2\"))\n                )\n        );\n        DnssecQueryResult result = client.queryDnssec(\"example.com\", Record.TYPE.A);\n        assertFalse(result.isAuthenticData());\n        DnsMessage message = result.dnsQueryResult.response;\n        checkCorrectExampleMessage(message);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testInvalidDNSKEY() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ds(\"com\", digestType, comKSK))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), signedZone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        sign(comKSK, \"com\", comPrivateKSK, algorithm,\n                                record(\"com\", comKSK)),\n                        sign(comZSK, \"com\", comPrivateZSK, algorithm,\n                                record(\"example.com\", a(\"1.1.1.2\")))\n                )\n        );\n\n        assertThrows(DnssecValidationFailedException.class, () ->\n            client.query(\"example.com\", Record.TYPE.A)\n        );\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testNoDNSKEY() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ds(\"com\", digestType, comKSK))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), signedZone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        sign(comZSK, \"com\", comPrivateZSK, algorithm,\n                                record(\"example.com\", a(\"1.1.1.2\")))\n                )\n        );\n\n        assertThrows(DnssecValidationFailedException.class, () ->\n            client.query(\"example.com\", Record.TYPE.A)\n        );\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testInvalidRRSIG() throws IOException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {\n        DnssecClient client = constructDnssecClient();\n        Record<RRSIG> invalidRrSig = rrsigRecord(comZSK, \"com\", comPrivateZSK, algorithm, record(\"example.com\", a(\"1.1.1.2\")));\n        RRSIG soonToBeInvalidRrSig = invalidRrSig.payloadData;\n        Field signature = soonToBeInvalidRrSig.getClass().getDeclaredField(\"signature\");\n        signature.setAccessible(true);\n        byte[] signatureMod = (byte[]) signature.get(soonToBeInvalidRrSig);\n\n        // Change the signature a little bit so that it becomes invalid.\n        signatureMod[signatureMod.length / 2]++;\n\n        applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ds(\"com\", digestType, comKSK))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), zone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        record(\"com\", comKSK),\n                        record(\"com\", comZSK),\n                        record(\"example.com\", a(\"1.1.1.2\")),\n                        invalidRrSig\n                )\n        );\n\n        assertThrows(DnssecValidationFailedException.class, () ->\n            client.query(\"example.com\", Record.TYPE.A)\n        );\n    }\n\n    @SuppressWarnings({\"unchecked\", \"JavaUtilDate\"})\n    @Test\n    public void testUnknownAlgorithm() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        Date signatureExpiration = new Date(System.currentTimeMillis() + 14 * 24 * 60 * 60 * 1000);\n        Date signatureInception = new Date(System.currentTimeMillis() - 14 * 24 * 60 * 60 * 1000);\n        RRSIG unknownRrsig = rrsig(Record.TYPE.A, 213, 2, 3600, signatureExpiration, signatureInception, comZSK.getKeyTag(), \"com\", new byte[0]);\n        applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ds(\"com\", digestType, comKSK))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), zone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        record(\"com\", comKSK),\n                        record(\"com\", comZSK),\n                        record(\"example.com\", a(\"1.1.1.2\")),\n                        record(\"example.com\", unknownRrsig)\n                )\n        );\n        DnssecQueryResult result = client.queryDnssec(\"example.com\", Record.TYPE.A);\n        assertFalse(result.isAuthenticData());\n        DnsMessage message = result.synthesizedResponse;\n        checkCorrectExampleMessage(message);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testInvalidDelegation() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ds(comKSK.getKeyTag(), algorithm, digestType, new byte[0]))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), signedZone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        sign(comKSK, \"com\", comPrivateKSK, algorithm,\n                                record(\"com\", comKSK),\n                                record(\"com\", comZSK)),\n                        sign(comZSK, \"com\", comPrivateZSK, algorithm,\n                                record(\"example.com\", a(\"1.1.1.2\")))\n                )\n        );\n\n        assertThrows(DnssecValidationFailedException.class, () ->\n            client.query(\"example.com\", Record.TYPE.A)\n        );\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testUnknownDelegationDigestType() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ds(comKSK.getKeyTag(), algorithm, (byte) 213, new byte[0]))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), signedZone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        sign(comKSK, \"com\", comPrivateKSK, algorithm,\n                                record(\"com\", comKSK),\n                                record(\"com\", comZSK)),\n                        sign(comZSK, \"com\", comPrivateZSK, algorithm,\n                                record(\"example.com\", a(\"1.1.1.2\")))\n                )\n        );\n        DnssecQueryResult result = client.queryDnssec(\"example.com\", Record.TYPE.A);\n        assertFalse(result.isAuthenticData());\n        DnsMessage message = result.synthesizedResponse;\n        checkCorrectExampleMessage(message);\n    }\n\n    @SuppressWarnings({\"unchecked\", \"JavaUtilDate\"})\n    @Test\n    public void testSignatureOutOfDate() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        Date signatureExpiration = new Date(System.currentTimeMillis() - 14 * 24 * 60 * 60 * 1000);\n        Date signatureInception = new Date(System.currentTimeMillis() - 28L * 24L * 60L * 60L * 1000L);\n        RRSIG outOfDateSig = rrsig(Record.TYPE.A, algorithm, 2, 3600, signatureExpiration, signatureInception, comZSK.getKeyTag(), \"com\", new byte[0]);\n        applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ds(\"com\", digestType, comKSK))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), signedZone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        sign(comKSK, \"com\", comPrivateKSK, algorithm,\n                                record(\"com\", comKSK),\n                                record(\"com\", comZSK)),\n                        sign(comPrivateZSK, outOfDateSig,\n                                record(\"example.com\", a(\"1.1.1.2\")))\n                )\n        );\n        DnssecQueryResult result = client.queryDnssec(\"example.com\", Record.TYPE.A);\n        assertFalse(result.isAuthenticData());\n        DnsMessage message = result.synthesizedResponse;\n        checkCorrectExampleMessage(message);\n    }\n\n    @SuppressWarnings({\"unchecked\", \"JavaUtilDate\"})\n    @Test\n    public void testSignatureInFuture() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        Date signatureExpiration = new Date(System.currentTimeMillis() + 28L * 24L * 60L * 60L * 1000L);\n        Date signatureInception = new Date(System.currentTimeMillis() + 14 * 24 * 60 * 60 * 1000);\n        RRSIG outOfDateSig = rrsig(Record.TYPE.A, algorithm, 2, 3600, signatureExpiration, signatureInception, comZSK.getKeyTag(), \"com\", new byte[0]);\n        applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ds(\"com\", digestType, comKSK))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), signedZone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        sign(comKSK, \"com\", comPrivateKSK, algorithm,\n                                record(\"com\", comKSK),\n                                record(\"com\", comZSK)),\n                        sign(comPrivateZSK, outOfDateSig,\n                                record(\"example.com\", a(\"1.1.1.2\")))\n                )\n        );\n        DnssecQueryResult result = client.queryDnssec(\"example.com\", Record.TYPE.A);\n        assertFalse(result.isAuthenticData());\n        DnsMessage message = result.synthesizedResponse;\n        checkCorrectExampleMessage(message);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    @Test\n    public void testValidNSEC() throws Exception {\n        DnssecClient client = constructDnssecClient();\n        DnsWorld world = applyZones(client,\n                signedRootZone(\n                        sign(rootKSK, \"\", rootPrivateKSK, algorithm,\n                                record(\"\", rootKSK),\n                                record(\"\", rootZSK)),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ds(\"com\", digestType, comKSK))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(rootZSK, \"\", rootPrivateZSK, algorithm,\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), signedZone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        sign(comKSK, \"com\", comPrivateKSK, algorithm,\n                                record(\"com\", comKSK),\n                                record(\"com\", comZSK)),\n                        sign(comZSK, \"com\", comPrivateZSK, algorithm,\n                                record(\"example.com\", a(\"1.1.1.2\")))\n                )\n        );\n        DnsMessage.Builder nsecMessage = DnsMessage.builder();\n        List<Record<? extends Data>> records = DnssecWorld.merge(\n                                sign(comZSK, \"com\", comPrivateZSK, algorithm,\n                                        record(\"example.com\", nsec(\"www.example.com\", Record.TYPE.A))),\n                                sign(comZSK, \"com\", comPrivateZSK, algorithm,\n                                        record(\"example.com\", soa(\"sns.dns.icann.org\", \"noc.dns.icann.org\", 2015081265, 7200, 3600, 1209600, 3600))));\n        nsecMessage.setNameserverRecords(records);\n        nsecMessage.setAuthoritativeAnswer(true);\n        world.addPreparedResponse(new DnssecWorld.AddressedNsecResponse(InetAddress.getByAddress(\"ns.com\", new byte[] {1, 1, 1, 1}), nsecMessage.build()));\n        DnssecQueryResult result = client.queryDnssec(\"nsec.example.com\", Record.TYPE.A);\n        // TODO: The setSripSignatureRecords() call could probably be removed. It does not appear to server any purpose here.\n        client.setStripSignatureRecords(false);\n        DnsMessage message = result.synthesizedResponse;\n        assertEquals(0, message.answerSection.size());\n        assertTrue(message.authenticData);\n    }\n\n    /**\n     * Zone 'com.' has no DS in the root zone. Hence, in order to verify the results of RRs under 'com.' a DLV has to\n     * been used.\n     *\n     * @throws IOException in case of an I/O error.\n     */\n    @Test\n    public void testValidDLV() throws IOException {\n        DnssecClient client = constructDnssecClient();\n        DnsWorld dnsWorld = applyZones(client,\n                signedRootZone(\n                        selfSignDnskeyRrSet(\"\"),\n                        sign(\"\",\n                                ds(\"dlv\")),\n                        sign(\"\",\n                                record(\"dlv\", ns(\"ns.com\"))),\n                        sign(\"\",\n                                record(\"com\", ns(\"ns.com\"))),\n                        sign(\"\",\n                                record(\"ns.com\", a(\"1.1.1.1\")))\n                ), signedZone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        selfSignDnskeyRrSet(\"com\"),\n                        sign(\"com\",\n                                record(\"example.com\", a(\"1.1.1.2\")))\n                ), signedZone(\"dlv\", \"ns.com\", \"1.1.1.1\",\n                        selfSignDnskeyRrSet(\"dlv\"),\n                        sign(\"dlv\",\n                                record(\"com.dlv\", dlv(\"com\", digestType, comKSK)))\n                )\n        );\n        // Add NSEC which proves that there is no DS record for 'com.'. Note that the prove comes from the parental zone\n        // nameserver in case of DS RRs.\n        addNsec(dnsWorld, \"\", \"a.root-servers.net\", \"com\", \"dlv\", TYPE.NS);\n\n        client.configureLookasideValidation(DnsName.from(\"dlv\"));\n\n        DnssecQueryResult result = client.queryDnssec(\"example.com\", Record.TYPE.A);\n        assertTrue(result.isAuthenticData());\n        DnsMessage message = result.synthesizedResponse;\n        checkCorrectExampleMessage(message);\n\n        client.disableLookasideValidation();\n        result = client.queryDnssec(\"example.com\", Record.TYPE.A);\n        assertFalse(result.isAuthenticData());\n        message = result.synthesizedResponse;\n        checkCorrectExampleMessage(message);\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/test/java/org/minidns/dnssec/DnssecWorld.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec;\n\nimport org.minidns.DnsWorld;\nimport org.minidns.constants.DnssecConstants.DigestAlgorithm;\nimport org.minidns.constants.DnssecConstants.SignatureAlgorithm;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnssec.algorithms.AlgorithmMap;\nimport org.minidns.record.DLV;\nimport org.minidns.record.DNSKEY;\nimport org.minidns.record.DS;\nimport org.minidns.record.Data;\nimport org.minidns.record.NSEC;\nimport org.minidns.record.RRSIG;\nimport org.minidns.record.Record;\nimport org.minidns.record.Record.TYPE;\nimport org.minidns.util.InetAddressUtil;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.math.BigInteger;\nimport java.net.Inet4Address;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.security.InvalidKeyException;\nimport java.security.KeyPair;\nimport java.security.KeyPairGenerator;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.PrivateKey;\nimport java.security.Signature;\nimport java.security.SignatureException;\nimport java.security.interfaces.DSAParams;\nimport java.security.interfaces.DSAPrivateKey;\nimport java.security.interfaces.RSAPrivateCrtKey;\nimport java.security.spec.RSAKeyGenParameterSpec;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class DnssecWorld extends DnsWorld {\n\n    public static final SignatureAlgorithm DEFAULT_DNSSEC_ALGORITHM = SignatureAlgorithm.RSASHA256;\n    public static final DigestAlgorithm DEFAULT_DIGEST_ALGORITHM = DigestAlgorithm.SHA1;\n\n    private static final Map<DnsName, DnssecData> DNSSEC_DATA = new HashMap<>();\n\n    public static final class DnssecData {\n        public final DnsName zone;\n        public final DNSKEY ksk;\n        public final PrivateKey privateKsk;\n        public final DNSKEY zsk;\n        public final PrivateKey privateZsk;\n        public final SignatureAlgorithm signatureAlgorithm;\n\n        private DnssecData(DnsName zone, DNSKEY ksk, PrivateKey privateKsk, DNSKEY zsk, PrivateKey privateZsk,\n                SignatureAlgorithm signatureAlgorithm) {\n            this.zone = zone;\n            this.ksk = ksk;\n            this.privateKsk = privateKsk;\n            this.zsk = zsk;\n            this.privateZsk = privateZsk;\n            this.signatureAlgorithm = signatureAlgorithm;\n        }\n    }\n\n    public static DnssecData getDnssecDataFor(CharSequence zone) {\n        return getDnssecDataFor(DnsName.from(zone));\n    }\n\n    public static DnssecData getDnssecDataFor(DnsName zone) {\n        DnssecData dnssecData = DNSSEC_DATA.get(zone);\n        if (dnssecData != null) {\n            return dnssecData;\n        }\n\n        SignatureAlgorithm algorithm = DEFAULT_DNSSEC_ALGORITHM;\n        PrivateKey privateKsk = generatePrivateKey(algorithm, 2048);\n        DNSKEY ksk = dnskey(DNSKEY.FLAG_ZONE | DNSKEY.FLAG_SECURE_ENTRY_POINT, algorithm, publicKey(algorithm, privateKsk));\n        PrivateKey privateZsk = generatePrivateKey(algorithm, 1024);\n        DNSKEY zsk = dnskey(DNSKEY.FLAG_ZONE, algorithm, publicKey(algorithm, privateZsk));\n        dnssecData = new DnssecData(zone, ksk, privateKsk, zsk, privateZsk, algorithm);\n\n        DNSSEC_DATA.put(zone, dnssecData);\n\n        return dnssecData;\n    }\n\n    public static Zone signedRootZone(SignedRRSet... rrSets) {\n        return new Zone(\"\", null, merge(rrSets));\n    }\n\n    public static Zone signedZone(String zoneName, String nsName, String nsIp, SignedRRSet... records) {\n        Inet4Address inet4Address = InetAddressUtil.ipv4From(nsIp);\n        try {\n            return signedZone(zoneName, InetAddress.getByAddress(nsName, inet4Address.getAddress()), records);\n        } catch (UnknownHostException e) {\n            // This will never happen, as we already ensured the validity of the IP address by using parseIpV4()\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static Zone signedZone(String zoneName, InetAddress address, SignedRRSet... rrSets) {\n        return new Zone(zoneName, address, merge(rrSets));\n    }\n\n    public static List<Record<? extends Data>> merge(SignedRRSet... rrSets) {\n        List<Record<? extends Data>> recordList = new ArrayList<>();\n        for (SignedRRSet rrSet : rrSets) {\n            recordList.add(rrSet.signature);\n            recordList.addAll(Arrays.asList(rrSet.records));\n        }\n        return recordList;\n    }\n\n    @SuppressWarnings(\"varargs\")\n    @SafeVarargs\n    public static SignedRRSet sign(DNSKEY key, String signerName, PrivateKey privateKey, SignatureAlgorithm algorithm, Record<? extends Data>... records) {\n        return new SignedRRSet(records, rrsigRecord(key, signerName, privateKey, algorithm, records));\n    }\n\n    @SuppressWarnings(\"varargs\")\n    @SafeVarargs\n    public static SignedRRSet sign(DNSKEY key, DnsName signerName, PrivateKey privateKey, SignatureAlgorithm algorithm, Record<? extends Data>... records) {\n        return new SignedRRSet(records, rrsigRecord(key, signerName, privateKey, algorithm, records));\n    }\n\n    @SuppressWarnings(\"varargs\")\n    @SafeVarargs\n    public static SignedRRSet sign(PrivateKey privateKey, RRSIG rrsig, Record<? extends Data>... records) {\n        return new SignedRRSet(records, rrsigRecord(privateKey, rrsig, records));\n    }\n\n    @SafeVarargs\n    public static SignedRRSet sign(CharSequence signerName, Record<? extends Data>... records) {\n        return sign(DnsName.from(signerName), records);\n    }\n\n    @SuppressWarnings(\"varargs\")\n    @SafeVarargs\n    public static SignedRRSet sign(DnsName signerName, Record<? extends Data>... records) {\n        DnssecData dnssecData = getDnssecDataFor(signerName);\n\n        DNSKEY dnskey;\n        PrivateKey privateKey;\n        final TYPE typeToSign = records[0].type;\n        switch (typeToSign) {\n        case DNSKEY:\n            dnskey = dnssecData.ksk;\n            privateKey = dnssecData.privateKsk;\n            break;\n        default:\n            dnskey = dnssecData.zsk;\n            privateKey = dnssecData.privateZsk;\n            break;\n        }\n\n        // TODO: Check if all records are of type 'typeToSign'.\n\n        return new SignedRRSet(records, rrsigRecord(dnskey, signerName, privateKey, dnssecData.signatureAlgorithm, records));\n    }\n\n    public static SignedRRSet selfSignDnskeyRrSet(CharSequence zone) {\n        return selfSignDnskeyRrSet(DnsName.from(zone));\n    }\n\n    public static SignedRRSet selfSignDnskeyRrSet(DnsName zone) {\n        DnssecData dnssecData = getDnssecDataFor(zone);\n        return sign(zone,\n                record(zone, dnssecData.ksk),\n                record(zone, dnssecData.zsk));\n    }\n\n    public static class SignedRRSet {\n        Record<? extends Data>[] records;\n        Record<RRSIG> signature;\n\n        public SignedRRSet(Record<? extends Data>[] records, Record<RRSIG> signature) {\n            this.records = records;\n            this.signature = signature;\n        }\n    }\n\n\n    @SafeVarargs\n    public static Record<RRSIG> rrsigRecord(DNSKEY key, String signerName, PrivateKey privateKey, SignatureAlgorithm algorithm, Record<? extends Data>... records) {\n        return rrsigRecord(key, DnsName.from(signerName), privateKey, algorithm, records);\n    }\n\n    @SuppressWarnings({\"unchecked\", \"JavaUtilDate\"})\n    public static Record<RRSIG> rrsigRecord(DNSKEY key, DnsName signerName, PrivateKey privateKey, SignatureAlgorithm algorithm, Record<? extends Data>... records) {\n        Record.TYPE typeCovered = records[0].type;\n        int labels = records[0].name.getLabelCount();\n        long originalTtl = records[0].ttl;\n        Date signatureExpiration = new Date(System.currentTimeMillis() + 14 * 24 * 60 * 60 * 1000);\n        Date signatureInception = new Date(System.currentTimeMillis() - 14 * 24 * 60 * 60 * 1000);\n        RRSIG rrsig = rrsig(typeCovered, algorithm, labels, originalTtl, signatureExpiration, signatureInception,\n                key.getKeyTag(), signerName, new byte[0]);\n        return rrsigRecord(privateKey, rrsig, records);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static Record<RRSIG> rrsigRecord(PrivateKey privateKey, RRSIG rrsig, Record<? extends Data>... records) {\n        byte[] bytes = Verifier.combine(rrsig, Arrays.asList(records));\n        return record(records[0].name, rrsig.originalTtl, rrsig(rrsig.typeCovered, rrsig.algorithm, rrsig.labels, rrsig.originalTtl,\n                rrsig.signatureExpiration, rrsig.signatureInception, rrsig.keyTag, rrsig.signerName,\n                sign(privateKey, rrsig.algorithm, bytes))).as(RRSIG.class);\n    }\n\n    public static Record<DS> ds(CharSequence zone) {\n        return ds(DnsName.from(zone));\n    }\n\n    public static Record<DS> ds(DnsName zone) {\n        DnssecData dnssecData = getDnssecDataFor(zone);\n        return record(zone, ds(zone, DEFAULT_DIGEST_ALGORITHM, dnssecData.ksk));\n    }\n\n    public static DS ds(String name, DigestAlgorithm digestType, DNSKEY dnskey) {\n        return ds(DnsName.from(name), digestType, dnskey);\n    }\n\n    public static DS ds(DnsName name, DigestAlgorithm digestType, DNSKEY dnskey) {\n        return ds(dnskey.getKeyTag(), dnskey.algorithm, digestType, calculateDsDigest(name, digestType, dnskey));\n    }\n\n    public static DLV dlv(String name, DigestAlgorithm digestType, DNSKEY dnskey) {\n        return dlv(DnsName.from(name), digestType, dnskey);\n    }\n\n    public static DLV dlv(DnsName name, DigestAlgorithm digestType, DNSKEY dnskey) {\n        return dlv(dnskey.getKeyTag(), dnskey.algorithm, digestType, calculateDsDigest(name, digestType, dnskey));\n    }\n\n    public static byte[] calculateDsDigest(DnsName name, DigestAlgorithm digestType, DNSKEY dnskey) {\n        DigestCalculator digestCalculator = AlgorithmMap.INSTANCE.getDsDigestCalculator(digestType);\n\n        byte[] dnskeyData = dnskey.toByteArray();\n        byte[] dnskeyOwner = name.getBytes();\n        byte[] combined = new byte[dnskeyOwner.length + dnskeyData.length];\n        System.arraycopy(dnskeyOwner, 0, combined, 0, dnskeyOwner.length);\n        System.arraycopy(dnskeyData, 0, combined, dnskeyOwner.length, dnskeyData.length);\n        return digestCalculator.digest(combined);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    public static byte[] sign(PrivateKey privateKey, SignatureAlgorithm algorithm, byte[] content) {\n        try {\n            Signature signature;\n            switch (algorithm) {\n                case RSAMD5:\n                    signature = Signature.getInstance(\"MD5withRSA\");\n                    break;\n                case RSASHA1:\n                case RSASHA1_NSEC3_SHA1:\n                    signature = Signature.getInstance(\"SHA1withRSA\");\n                    break;\n                case RSASHA256:\n                    signature = Signature.getInstance(\"SHA256withRSA\");\n                    break;\n                case RSASHA512:\n                    signature = Signature.getInstance(\"SHA512withRSA\");\n                    break;\n                case DSA:\n                case DSA_NSEC3_SHA1:\n                    signature = Signature.getInstance(\"SHA1withDSA\");\n                    break;\n                default:\n                    throw new RuntimeException(algorithm + \" algorithm not yet supported by DNSSECWorld\");\n            }\n            signature.initSign(privateKey);\n            signature.update(content);\n            byte[] bytes = signature.sign();\n            switch (algorithm) {\n                case DSA:\n                case DSA_NSEC3_SHA1:\n                    return convertAsn1ToRFC((DSAPrivateKey) privateKey, bytes);\n\n                case RSAMD5:\n                case RSASHA1:\n                case RSASHA1_NSEC3_SHA1:\n                case RSASHA256:\n                case RSASHA512:\n                default:\n                    return bytes;\n            }\n        } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException | IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * Convert ASN.1 to RFC 2536.\n     *\n     * @param privateKey the private key.\n     * @param bytes the bytes.\n     * @return the RFC 2536 bytes.\n     * @throws IOException if an IO error occurs.\n     */\n    public static byte[] convertAsn1ToRFC(DSAPrivateKey privateKey, byte[] bytes) throws IOException {\n        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));\n        ByteArrayOutputStream bos = new ByteArrayOutputStream();\n        DataOutputStream dos = new DataOutputStream(bos);\n        dos.writeByte(privateKey.getParams().getP().bitLength() / 64 - 8);\n        dis.skipBytes(2);\n        streamAsn1Int(dis, dos, 20);\n        streamAsn1Int(dis, dos, 20);\n        return bos.toByteArray();\n    }\n\n    public static void streamAsn1Int(DataInputStream dis, DataOutputStream dos, int targetLength) throws IOException {\n        dis.skipBytes(1);\n\n        byte s_pad = (byte) (dis.readByte() - targetLength);\n        if (s_pad >= 0) {\n            dis.skipBytes(s_pad);\n            s_pad = 0;\n        } else {\n            for (int i = 0; i < (1 - s_pad); i++) {\n                dos.writeByte(0);\n            }\n        }\n\n        byte[] buf = new byte[targetLength + s_pad];\n\n        int bytesRead = dis.read(buf);\n        if (bytesRead != buf.length) throw new IOException();\n\n        dos.write(buf);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    public static PrivateKey generatePrivateKey(SignatureAlgorithm algorithm, int length) {\n        switch (algorithm) {\n            case RSAMD5:\n            case RSASHA1:\n            case RSASHA1_NSEC3_SHA1:\n            case RSASHA256:\n            case RSASHA512:\n                return generateRSAPrivateKey(length, RSAKeyGenParameterSpec.F4);\n            case DSA:\n            case DSA_NSEC3_SHA1:\n                return generateDSAPrivateKey(length);\n            default:\n                throw new RuntimeException(algorithm + \" algorithm not yet supported by DNSSECWorld\");\n        }\n    }\n\n    public static PrivateKey generateRSAPrivateKey(int length, BigInteger publicExponent) {\n        try {\n            KeyPairGenerator rsa = KeyPairGenerator.getInstance(\"RSA\");\n            rsa.initialize(new RSAKeyGenParameterSpec(length, publicExponent));\n            KeyPair keyPair = rsa.generateKeyPair();\n            return keyPair.getPrivate();\n        } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static PrivateKey generateDSAPrivateKey(int length) {\n        try {\n            KeyPairGenerator dsa = KeyPairGenerator.getInstance(\"DSA\");\n            dsa.initialize(length);\n            KeyPair keyPair = dsa.generateKeyPair();\n            return keyPair.getPrivate();\n        } catch (NoSuchAlgorithmException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    public static byte[] publicKey(SignatureAlgorithm algorithm, PrivateKey privateKey) {\n        switch (algorithm) {\n            case RSAMD5:\n            case RSASHA1:\n            case RSASHA1_NSEC3_SHA1:\n            case RSASHA256:\n            case RSASHA512:\n                return getRSAPublicKey((RSAPrivateCrtKey) privateKey);\n            case DSA:\n            case DSA_NSEC3_SHA1:\n                return getDSAPublicKey((DSAPrivateKey) privateKey);\n            default:\n                throw new RuntimeException(algorithm + \" algorithm not yet supported by DNSSECWorld\");\n        }\n    }\n\n    private static byte[] getDSAPublicKey(DSAPrivateKey privateKey) {\n        final DSAParams params = privateKey.getParams();\n        final BigInteger g = params.getG();\n        final BigInteger p = params.getP();\n        final BigInteger q = params.getQ();\n        final BigInteger x = privateKey.getX();\n        final BigInteger y = g.modPow(x, p);\n        final int t = p.bitLength() / 64 - 8;\n\n        final ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        final DataOutputStream dos = new DataOutputStream(baos);\n        try {\n            dos.writeByte(t);\n            dos.write(toUnsignedByteArray(q, 20));\n            dos.write(toUnsignedByteArray(p, t * 8 + 64));\n            dos.write(toUnsignedByteArray(g, t * 8 + 64));\n            dos.write(toUnsignedByteArray(y, t * 8 + 64));\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n\n        return baos.toByteArray();\n    }\n\n    public static byte[] getRSAPublicKey(RSAPrivateCrtKey privateKey) {\n        try {\n            ByteArrayOutputStream baos = new ByteArrayOutputStream();\n            DataOutputStream dos = new DataOutputStream(baos);\n            byte[] exponent = toUnsignedByteArray(privateKey.getPublicExponent());\n            if (exponent.length > 255) {\n                dos.writeByte(0);\n                dos.writeShort(exponent.length);\n            } else {\n                dos.writeByte(exponent.length);\n            }\n            dos.write(exponent);\n            dos.write(toUnsignedByteArray(privateKey.getModulus()));\n            return baos.toByteArray();\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private static byte[] toUnsignedByteArray(BigInteger bigInteger) {\n        byte[] array = bigInteger.toByteArray();\n        if (array[0] == 0) {\n            byte[] tmp = new byte[array.length - 1];\n            System.arraycopy(array, 1, tmp, 0, tmp.length);\n            array = tmp;\n        }\n        return array;\n    }\n\n    private static byte[] toUnsignedByteArray(BigInteger bigInteger, int length) {\n        byte[] array = bigInteger.toByteArray();\n        if (array.length != length) {\n            if (array.length == length + 1 && array[0] == 0) {\n                byte[] tmp = new byte[array.length - 1];\n                System.arraycopy(array, 1, tmp, 0, tmp.length);\n                array = tmp;\n            } else if (array.length < length) {\n                byte[] tmp = new byte[length];\n                System.arraycopy(array, 0, tmp, length - array.length, array.length);\n                array = tmp;\n            }\n        }\n        return array;\n    }\n\n    public static class AddressedNsecResponse implements PreparedResponse {\n        final InetAddress address;\n        final DnsMessage nsecMessage;\n        final boolean isRootNameserver;\n\n        // We currently do not use the whole list of NSEC records, but in the future we eventually will be.\n        final List<Record<NSEC>> nsecRecords;\n\n        public AddressedNsecResponse(InetAddress address, DnsMessage nsecMessage) {\n            this.address = address;\n            this.nsecMessage = nsecMessage;\n            this.isRootNameserver = address.getHostName().endsWith(\".root-servers.net\");\n            this.nsecRecords = nsecMessage.filterAuthoritySectionBy(NSEC.class);\n        }\n\n        @Override\n        public boolean isResponse(DnsMessage request, InetAddress address) {\n            boolean nameserverMatches;\n            if (isRootNameserver) {\n                nameserverMatches = address.getHostName().endsWith(\".root-servers.net\");\n            } else {\n                nameserverMatches = address.equals(this.address);\n            }\n\n            Record<NSEC> nsecRecord = nsecRecords.get(0);\n            return nameserverMatches && Verifier.nsecMatches(request.getQuestion().name, nsecRecord.name, nsecRecord.payloadData.next);\n        }\n\n        @Override\n        public DnsMessage getResponse() {\n            return nsecMessage;\n        }\n\n        @Override\n        public String toString() {\n            return getClass().getSimpleName() + \": \" + address + '\\n'\n                    + nsecMessage;\n        }\n    }\n\n    public static void addNsec(DnsWorld dnsWorld, CharSequence zone, CharSequence zoneSoaNameserver,\n            CharSequence owner, String nextSecure, Record.TYPE... typesCovered) {\n        addNsec(dnsWorld, DnsName.from(zone), DnsName.from(zoneSoaNameserver), DnsName.from(owner),\n                DnsName.from(nextSecure), typesCovered);\n    }\n\n    public static void addNsec(DnsWorld dnsWorld, DnsName zone, DnsName zoneSoaNameserver, DnsName owner, DnsName nextSecure,\n            Record.TYPE... typesCovered) {\n        DnssecData dnssecData = getDnssecDataFor(zone);\n        PrivateKey privateKey = dnssecData.privateZsk;\n        DNSKEY key = dnssecData.zsk;\n        SignatureAlgorithm signatureAlgorithm = dnssecData.signatureAlgorithm;\n\n        DnsMessage.Builder nsecAnswerBuilder = DnsMessage.builder();\n        List<Record<? extends Data>> records = DnssecWorld.merge(\n                                sign(key, zone, privateKey, signatureAlgorithm,\n                                        record(owner, nsec(nextSecure, typesCovered))),\n                                sign(key, zone, privateKey, signatureAlgorithm,\n                                        record(owner, soa(zoneSoaNameserver,\n                                                          DnsName.from(\"mailbox.of.responsible.person\"),\n                                                          2015081265,\n                                                          7200,\n                                                          3600,\n                                                          1209600,\n                                                          3600))));\n        nsecAnswerBuilder.setNameserverRecords(records);\n        nsecAnswerBuilder.setAuthoritativeAnswer(true);\n\n        DnsMessage nsecAnswer = nsecAnswerBuilder.build();\n\n        // Get the authoritative nameserver IP address from dns world.\n        InetAddress authoritativeNameserver = dnsWorld.lookupSingleAuthoritativeNameserverForZone(zone);\n\n        PreparedResponse preparedNsecResponse = new DnssecWorld.AddressedNsecResponse(authoritativeNameserver, nsecAnswer); \n        dnsWorld.addPreparedResponse(preparedNsecResponse);\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/test/java/org/minidns/dnssec/VerifierTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec;\n\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnssec.algorithms.JavaSecDigestCalculator;\nimport org.minidns.record.NSEC;\nimport org.minidns.record.NSEC3;\nimport org.minidns.record.Record;\nimport org.minidns.record.Record.TYPE;\nimport org.junit.jupiter.api.Test;\n\nimport java.math.BigInteger;\n\nimport static org.minidns.DnsWorld.nsec;\nimport static org.minidns.DnsWorld.nsec3;\nimport static org.minidns.DnsWorld.record;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class VerifierTest {\n\n    @Test\n    public void testNsecMatches() {\n        assertTrue(Verifier.nsecMatches(\"example.com\", \"com\", \"com\"));\n        assertTrue(Verifier.nsecMatches(\"example.com\", \"e.com\", \"f.com\"));\n        assertTrue(Verifier.nsecMatches(\"example.com\", \"be\", \"de\"));\n        assertTrue(Verifier.nsecMatches(\"nsec.example.com\", \"example.com\", \"www.example.com\"));\n        assertFalse(Verifier.nsecMatches(\"example.com\", \"a.com\", \"example.com\"));\n        assertFalse(Verifier.nsecMatches(\"example.com\", \"example1.com\", \"example2.com\"));\n        assertFalse(Verifier.nsecMatches(\"example.com\", \"test.com\", \"xxx.com\"));\n        assertFalse(Verifier.nsecMatches(\"example.com\", \"xxx.com\", \"test.com\"));\n        assertFalse(Verifier.nsecMatches(\"example.com\", \"aaa.com\", \"bbb.com\"));\n        assertFalse(Verifier.nsecMatches(\"www.example.com\", \"example2.com\", \"example3.com\"));\n        assertFalse(Verifier.nsecMatches(\"test.nsec.example.com\", \"nsec.example.com\", \"a.nsec.example.com\"));\n        assertFalse(Verifier.nsecMatches(\"test.nsec.example.com\", \"test.nsec.example.com\", \"a.example.com\"));\n        assertFalse(Verifier.nsecMatches(\"www.example.com\", \"example.com\", \"nsec.example.com\"));\n        assertFalse(Verifier.nsecMatches(\"example.com\", \"nsec.example.com\", \"www.example.com\"));\n    }\n\n    @Test\n    public void testVerifyNsec() {\n        Record<NSEC> nsecRecord = record(\"example.com\", nsec(\"www.example.com\", TYPE.A, TYPE.NS, TYPE.SOA, TYPE.TXT, TYPE.AAAA, TYPE.RRSIG, TYPE.NSEC, TYPE.DNSKEY)).as(NSEC.class);\n        assertNull(Verifier.verifyNsec(nsecRecord, new Question(\"nsec.example.com\", TYPE.A)));\n        assertNull(Verifier.verifyNsec(nsecRecord, new Question(\"example.com\", TYPE.PTR)));\n        assertNotNull(Verifier.verifyNsec(nsecRecord, new Question(\"www.example.com\", TYPE.A)));\n        assertNotNull(Verifier.verifyNsec(nsecRecord, new Question(\"example.com\", TYPE.NS)));\n    }\n\n    @Test\n    public void testVerifyNsec3() {\n        byte[] bytes = new byte[] {0x3f, (byte) 0xb1, (byte) 0xd0, (byte) 0xaa, 0x27, (byte) 0xe2, 0x5f, (byte) 0xda, 0x40, 0x75, (byte) 0x92, (byte) 0x95, 0x5a, 0x1c, 0x7f, (byte) 0x98, (byte) 0xdb, 0x5b, 0x79, (byte) 0x91};\n        Record<NSEC3> nsec3Record = record(\"7UO4LIHALHHLNGLJAFT7TBIQ6H1SL1CN.net\", nsec3((byte) 1, (byte) 1, 0, new byte[0], bytes, TYPE.NS, TYPE.SOA, TYPE.RRSIG, TYPE.DNSKEY, TYPE.NSEC3PARAM)).as(NSEC3.class);\n        DnsName zone = DnsName.from(\"net\");\n        assertNull(Verifier.verifyNsec3(zone, nsec3Record, new Question(\"x.net\", TYPE.A)));\n        assertNotNull(Verifier.verifyNsec3(zone, nsec3Record, new Question(\"example.net\", TYPE.A)));\n    }\n\n    @Test\n    public void testNsec3hash() throws Exception {\n        JavaSecDigestCalculator digestCalculator = new JavaSecDigestCalculator(\"SHA-1\");\n        assertEquals(\"6e8777855bcd60d7b45fc51893776dde75bf6cd4\", new BigInteger(1, Verifier.nsec3hash(digestCalculator, new byte[] {42}, new byte[] {88}, 5)).toString(16));\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/test/java/org/minidns/dnssec/algorithms/AlgorithmTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec.algorithms;\n\npublic class AlgorithmTest {\n    protected static AlgorithmMap algorithmMap;\n\n    static {\n        algorithmMap = AlgorithmMap.INSTANCE;\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/test/java/org/minidns/dnssec/algorithms/DigestTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec.algorithms;\n\nimport org.minidns.constants.DnssecConstants.DigestAlgorithm;\nimport org.minidns.dnssec.DigestCalculator;\nimport org.minidns.record.NSEC3.HashAlgorithm;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.math.BigInteger;\nimport java.nio.charset.StandardCharsets;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\npublic class DigestTest extends AlgorithmTest {\n\n    @Test\n    public void testSha1DsDigest() {\n        DigestCalculator dsDigestCalculator = algorithmMap.getDsDigestCalculator(DigestAlgorithm.SHA1);\n        assertEquals(\"da39a3ee5e6b4b0d3255bfef95601890afd80709\", digestHexString(dsDigestCalculator, \"\"));\n        assertEquals(\"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3\", digestHexString(dsDigestCalculator, \"test\"));\n        assertEquals(\"640ab2bae07bedc4c163f679a746f7ab7fb5d1fa\", digestHexString(dsDigestCalculator, \"Test\"));\n    }\n\n    @Test\n    public void testSha256DsDigest() {\n        DigestCalculator dsDigestCalculator = algorithmMap.getDsDigestCalculator(DigestAlgorithm.SHA256);\n        assertEquals(\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\", digestHexString(dsDigestCalculator, \"\"));\n        assertEquals(\"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\", digestHexString(dsDigestCalculator, \"test\"));\n        assertEquals(\"532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25\", digestHexString(dsDigestCalculator, \"Test\"));\n    }\n\n    @Test\n    public void testSha1nsec3Digest() {\n        DigestCalculator nsecDigestCalculator = algorithmMap.getNsecDigestCalculator(HashAlgorithm.SHA1);\n        assertEquals(\"da39a3ee5e6b4b0d3255bfef95601890afd80709\", digestHexString(nsecDigestCalculator, \"\"));\n        assertEquals(\"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3\", digestHexString(nsecDigestCalculator, \"test\"));\n        assertEquals(\"640ab2bae07bedc4c163f679a746f7ab7fb5d1fa\", digestHexString(nsecDigestCalculator, \"Test\"));\n    }\n\n    private static String digestHexString(DigestCalculator digestCalculator, String in) {\n        return new BigInteger(1, digestCalculator.digest(in.getBytes(StandardCharsets.UTF_8))).toString(16);\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/test/java/org/minidns/dnssec/algorithms/DsaSingatureVerifierTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec.algorithms;\n\nimport org.minidns.constants.DnssecConstants.SignatureAlgorithm;\nimport org.minidns.dnssec.DnssecValidationFailedException;\nimport org.minidns.dnssec.DnssecValidationFailedException.DataMalformedException;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.minidns.dnssec.DnssecWorld.generatePrivateKey;\nimport static org.minidns.dnssec.DnssecWorld.publicKey;\nimport static org.minidns.dnssec.DnssecWorld.sign;\n\npublic class DsaSingatureVerifierTest extends SignatureVerifierTest {\n    private static final SignatureAlgorithm ALGORITHM = SignatureAlgorithm.DSA;\n\n    @Test\n    public void testDSA1024Valid() throws DnssecValidationFailedException {\n        verifierTest(1024, ALGORITHM);\n    }\n\n    @Test\n    public void testDSA512Valid() throws DnssecValidationFailedException {\n        verifierTest(512, ALGORITHM);\n    }\n\n    @Test\n    public void testDSAIllegalSignature() {\n        byte[] sample = new byte[] { 0x0 };\n        assertThrows(DataMalformedException.class, () ->\n            assertSignatureValid(publicKey(ALGORITHM, generatePrivateKey(ALGORITHM, 1024)), ALGORITHM, sample, sample)\n        );\n    }\n\n    @Test\n    public void testDSAIllegalPublicKey() {\n        byte[] sample = getRandomBytes();\n\n        assertThrows(DataMalformedException.class, () ->\n            assertSignatureValid(new byte[] {0x0}, ALGORITHM, sign(generatePrivateKey(ALGORITHM, 1024), ALGORITHM, sample), sample)\n        );\n    }\n\n    @Test\n    public void testDSAWrongSignature() throws DnssecValidationFailedException {\n        byte[] sample = getRandomBytes();\n        assertSignatureInvalid(publicKey(ALGORITHM, generatePrivateKey(ALGORITHM, 1024)), ALGORITHM,\n                sign(generatePrivateKey(ALGORITHM, 1024), ALGORITHM, sample), sample);\n    }\n\n}\n"
  },
  {
    "path": "minidns-dnssec/src/test/java/org/minidns/dnssec/algorithms/RsaSignatureVerifierTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec.algorithms;\n\nimport org.minidns.constants.DnssecConstants.SignatureAlgorithm;\nimport org.minidns.dnssec.DnssecValidationFailedException;\nimport org.minidns.dnssec.DnssecValidationFailedException.DataMalformedException;\nimport org.junit.jupiter.api.Test;\n\nimport java.math.BigInteger;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.minidns.dnssec.DnssecWorld.generatePrivateKey;\nimport static org.minidns.dnssec.DnssecWorld.generateRSAPrivateKey;\nimport static org.minidns.dnssec.DnssecWorld.publicKey;\nimport static org.minidns.dnssec.DnssecWorld.sign;\n\npublic class RsaSignatureVerifierTest extends SignatureVerifierTest {\n    @Test\n    public void testShortExponentSHA1RSAValid() throws DnssecValidationFailedException {\n        verifierTest(generateRSAPrivateKey(1024, BigInteger.valueOf(17)), SignatureAlgorithm.RSASHA1);\n    }\n\n    @Test\n    public void testLongExponentSHA1RSAValid() throws DnssecValidationFailedException {\n        verifierTest(generateRSAPrivateKey(3072, BigInteger.valueOf(256).pow(256).add(BigInteger.ONE)), SignatureAlgorithm.RSASHA1);\n    }\n\n    @Test\n    public void testSHA1RSAIllegalSignature() throws DnssecValidationFailedException {\n        byte[] sample = new byte[] { 0x0 };\n        assertThrows(DnssecValidationFailedException.class, () ->\n            assertSignatureValid(\n                publicKey(SignatureAlgorithm.RSASHA1, generatePrivateKey(SignatureAlgorithm.RSASHA1, 1024)),\n                SignatureAlgorithm.RSASHA1, sample, sample) \n        );\n    }\n\n    @Test\n    public void testSHA1RSAIllegalPublicKey() throws DnssecValidationFailedException {\n        byte[] sample = getRandomBytes();\n\n        assertThrows(DataMalformedException.class, () ->\n            assertSignatureValid(new byte[] { 0x0 }, SignatureAlgorithm.RSASHA1,\n                sign(generatePrivateKey(SignatureAlgorithm.RSASHA1, 1024), SignatureAlgorithm.RSASHA1, sample), sample)\n        );\n    }\n\n    @Test\n    public void testSHA1RSAWrongSignature() throws DnssecValidationFailedException {\n        byte[] sample = getRandomBytes();\n\n        assertSignatureInvalid(\n                publicKey(SignatureAlgorithm.RSASHA1, generatePrivateKey(SignatureAlgorithm.RSASHA1, 1024)),\n                SignatureAlgorithm.RSASHA1,\n                sign(generatePrivateKey(SignatureAlgorithm.RSASHA1, 1024), SignatureAlgorithm.RSASHA1, sample),\n                sample);\n    }\n\n    @SuppressWarnings(\"deprecation\")\n    @Test\n    public void testMD5RSAValid() throws DnssecValidationFailedException {\n        verifierTest(1024, SignatureAlgorithm.RSAMD5);\n    }\n\n    @Test\n    public void testSHA256RSAValid() throws DnssecValidationFailedException {\n        verifierTest(1024, SignatureAlgorithm.RSASHA256);\n    }\n\n    @Test\n    public void testSHA512RSAValid() throws DnssecValidationFailedException {\n        verifierTest(1024, SignatureAlgorithm.RSASHA512);\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/test/java/org/minidns/dnssec/algorithms/SignatureVerifierTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.dnssec.algorithms;\n\nimport org.minidns.constants.DnssecConstants.SignatureAlgorithm;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnssec.DnssecValidationFailedException;\nimport org.minidns.record.DNSKEY;\nimport org.minidns.record.RRSIG;\n\nimport java.security.PrivateKey;\nimport java.util.concurrent.ThreadLocalRandom;\n\nimport static org.minidns.dnssec.DnssecWorld.generatePrivateKey;\nimport static org.minidns.dnssec.DnssecWorld.publicKey;\nimport static org.minidns.dnssec.DnssecWorld.sign;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class SignatureVerifierTest extends AlgorithmTest {\n\n    protected void verifierTest(int length, SignatureAlgorithm algorithm) throws DnssecValidationFailedException {\n        verifierTest(generatePrivateKey(algorithm, length), algorithm);\n    }\n\n    protected void verifierTest(PrivateKey privateKey, SignatureAlgorithm algorithm) throws DnssecValidationFailedException {\n        byte[] sample = getRandomBytes();\n        assertSignatureValid(publicKey(algorithm, privateKey), algorithm, sign(privateKey, algorithm, sample), sample);\n    }\n\n    protected static void assertSignatureValid(byte[] publicKey, SignatureAlgorithm algorithm, byte[] signature,\n            byte[] signedBytes) throws DnssecValidationFailedException {\n        assertTrue(verify(publicKey, algorithm, signature, signedBytes));\n    }\n\n    protected static void assertSignatureInvalid(byte[] publicKey, SignatureAlgorithm algorithm, byte[] signature,\n            byte[] signedBytes) throws DnssecValidationFailedException {\n        assertFalse(verify(publicKey, algorithm, signature, signedBytes));\n    }\n\n    private static boolean verify(byte[] publicKey, SignatureAlgorithm algorithm, byte[] signature, byte[] signedBytes)\n            throws DnssecValidationFailedException {\n        DNSKEY key = new DNSKEY((short) 0, (byte) 0, algorithm, publicKey);\n        RRSIG rrsig = new RRSIG(null, algorithm, (byte) 0, (long) 0, null, null, 0, DnsName.ROOT, signature);\n\n        boolean res = algorithmMap.getSignatureVerifier(algorithm).verify(signedBytes, rrsig, key);\n        return res;\n    }\n\n    protected static byte[] getRandomBytes() {\n        byte[] randomBytes = new byte[1024];\n        ThreadLocalRandom.current().nextBytes(randomBytes);\n        return randomBytes;\n    }\n}\n"
  },
  {
    "path": "minidns-dnssec/src/test/resources/.keep-minidns-dnssec-test-resources",
    "content": ""
  },
  {
    "path": "minidns-hla/build.gradle",
    "content": "plugins {\n\tid 'org.minidns.java-conventions'\n\tid 'org.minidns.android-conventions'\n}\n\ndescription = \"An easy to use high-level API (HLA) of MiniDNS' client\"\n\ndependencies {\n    api project(':minidns-dnssec')\n    testImplementation project(path: \":minidns-client\", configuration: \"testRuntime\")\n    testImplementation project(path: \":minidns-dnssec\", configuration: \"testRuntime\")\n}\n"
  },
  {
    "path": "minidns-hla/src/main/java/org/minidns/hla/DnssecResolverApi.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.hla;\n\nimport java.io.IOException;\nimport java.util.Set;\n\nimport org.minidns.DnsCache;\nimport org.minidns.MiniDnsException.NullResultException;\nimport org.minidns.cache.LruCache;\nimport org.minidns.cache.MiniDnsCacheFactory;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnssec.DnssecClient;\nimport org.minidns.dnssec.DnssecQueryResult;\nimport org.minidns.dnssec.DnssecUnverifiedReason;\nimport org.minidns.iterative.ReliableDnsClient.Mode;\nimport org.minidns.record.Data;\nimport org.minidns.record.Record.TYPE;\n\npublic class DnssecResolverApi extends ResolverApi {\n\n    public static final DnssecResolverApi INSTANCE = new DnssecResolverApi();\n\n    private final DnssecClient dnssecClient;\n    private final DnssecClient iterativeOnlyDnssecClient;\n    private final DnssecClient recursiveOnlyDnssecClient;\n\n    public DnssecResolverApi() {\n        this(new MiniDnsCacheFactory() {\n            @Override\n            public DnsCache newCache() {\n                return new LruCache();\n            }\n        });\n    }\n\n    public DnssecResolverApi(MiniDnsCacheFactory cacheFactory) {\n        this(new DnssecClient(cacheFactory.newCache()), cacheFactory);\n    }\n\n    private DnssecResolverApi(DnssecClient dnssecClient, MiniDnsCacheFactory cacheFactory) {\n        super(dnssecClient);\n        this.dnssecClient = dnssecClient;\n\n        // Set the *_ONLY_DNSSEC ResolverApi. It is important that the two do *not* share the same cache, since we\n        // probably fall back to iterativeOnly and in that case do not want the cached results of the recursive result.\n        iterativeOnlyDnssecClient = new DnssecClient(cacheFactory.newCache());\n        iterativeOnlyDnssecClient.setMode(Mode.iterativeOnly);\n\n        recursiveOnlyDnssecClient = new DnssecClient(cacheFactory.newCache());\n        recursiveOnlyDnssecClient.setMode(Mode.recursiveOnly);\n    }\n\n    @Override\n    public <D extends Data> ResolverResult<D> resolve(Question question) throws IOException {\n        DnssecQueryResult dnssecMessage = dnssecClient.queryDnssec(question);\n        return toResolverResult(question, dnssecMessage);\n    }\n\n    /**\n     * Resolve the given name and type which is expected to yield DNSSEC authenticated results.\n     *\n     * @param name the DNS name to resolve.\n     * @param type the class of the RR type to resolve.\n     * @param <D> the RR type to resolve.\n     * @return the resolver result.\n     * @throws IOException in case an exception happens while resolving.\n     * @see #resolveDnssecReliable(Question)\n     */\n    public <D extends Data> ResolverResult<D> resolveDnssecReliable(String name, Class<D> type) throws IOException {\n        return resolveDnssecReliable(DnsName.from(name), type);\n    }\n\n    /**\n     * Resolve the given name and type which is expected to yield DNSSEC authenticated results.\n     *\n     * @param name the DNS name to resolve.\n     * @param type the class of the RR type to resolve.\n     * @param <D> the RR type to resolve.\n     * @return the resolver result.\n     * @throws IOException in case an exception happens while resolving.\n     * @see #resolveDnssecReliable(Question)\n     */\n    public <D extends Data> ResolverResult<D> resolveDnssecReliable(DnsName name, Class<D> type) throws IOException {\n        TYPE t = TYPE.getType(type);\n        Question q = new Question(name, t);\n        return resolveDnssecReliable(q);\n    }\n\n    /**\n     * Resolve the given question which is expected to yield DNSSEC authenticated results.\n     *\n     * @param question the question to resolve.\n     * @param <D> the RR type to resolve.\n     * @return the resolver result.\n     * @throws IOException in case an exception happens while resolving.\n     */\n    public <D extends Data> ResolverResult<D> resolveDnssecReliable(Question question) throws IOException {\n        DnssecQueryResult dnssecMessage = recursiveOnlyDnssecClient.queryDnssec(question);\n        if (dnssecMessage == null || !dnssecMessage.isAuthenticData()) {\n            dnssecMessage = iterativeOnlyDnssecClient.queryDnssec(question);\n        }\n        return toResolverResult(question, dnssecMessage);\n    }\n\n    public DnssecClient getDnssecClient() {\n        return dnssecClient;\n    }\n\n    private static <D extends Data> ResolverResult<D> toResolverResult(Question question, DnssecQueryResult dnssecMessage) throws NullResultException {\n        Set<DnssecUnverifiedReason> unverifiedReasons = dnssecMessage.getUnverifiedReasons();\n\n        return new ResolverResult<D>(question, dnssecMessage.dnsQueryResult, unverifiedReasons);\n    }\n}\n"
  },
  {
    "path": "minidns-hla/src/main/java/org/minidns/hla/ResolutionUnsuccessfulException.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.hla;\n\nimport org.minidns.MiniDnsException;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;\n\npublic class ResolutionUnsuccessfulException extends MiniDnsException {\n\n    /**\n     * \n     */\n    private static final long serialVersionUID = 1L;\n\n    public final Question question;\n    public final RESPONSE_CODE responseCode;\n\n    public ResolutionUnsuccessfulException(Question question, RESPONSE_CODE responseCode) {\n        super(\"Asking for \" + question + \" yielded an error response \" + responseCode);\n        this.question = question;\n        this.responseCode = responseCode;\n    }\n}\n"
  },
  {
    "path": "minidns-hla/src/main/java/org/minidns/hla/ResolverApi.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.hla;\n\nimport java.io.IOException;\nimport java.net.Inet4Address;\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\n\nimport org.minidns.AbstractDnsClient;\nimport org.minidns.DnsClient;\nimport org.minidns.dnslabel.DnsLabel;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.hla.srv.SrvProto;\nimport org.minidns.hla.srv.SrvService;\nimport org.minidns.hla.srv.SrvServiceProto;\nimport org.minidns.hla.srv.SrvType;\nimport org.minidns.iterative.ReliableDnsClient;\nimport org.minidns.record.Data;\nimport org.minidns.record.PTR;\nimport org.minidns.record.SRV;\nimport org.minidns.record.Record.TYPE;\n\n/**\n * The high-level MiniDNS resolving API. It is designed to be easy to use.\n * <p>\n * A simple exammple how to resolve the IPv4 address of a given domain:\n * </p>\n * <pre>\n * {@code\n * ResolverResult<A> result = DnssecResolverApi.INSTANCE.resolve(\"verteiltesysteme.net\", A.class);\n * if (!result.wasSuccessful()) {\n *   RESPONSE_CODE responseCode = result.getResponseCode();\n *   // Perform error handling.\n *   …\n *   return;\n * }\n * if (!result.isAuthenticData()) {\n *   // Response was not secured with DNSSEC.\n *   …\n *   return;\n * }\n * Set<A> answers = result.getAnswers();\n * for (A a : answers) {\n *   InetAddress inetAddress = a.getInetAddress();\n *   // Do someting with the InetAddress, e.g. connect to.\n *   …\n * }\n * }\n * </pre>\n * <p>\n * MiniDNS also supports SRV resource records as first class citizens:\n * </p>\n * <pre>\n * {@code\n * SrvResolverResult result = DnssecResolverApi.INSTANCE.resolveSrv(SrvType.xmpp_client, \"example.org\")\n * if (!result.wasSuccessful()) {\n *   RESPONSE_CODE responseCode = result.getResponseCode();\n *   // Perform error handling.\n *   …\n *   return;\n * }\n * if (!result.isAuthenticData()) {\n *   // Response was not secured with DNSSEC.\n *   …\n *   return;\n * }\n * List<ResolvedSrvRecord> srvRecords = result.getSortedSrvResolvedAddresses();\n * // Loop over the domain names pointed by the SRV RR. MiniDNS will return the list\n * // correctly sorted by the priority and weight of the related SRV RR.\n * for (ResolvedSrvRecord srvRecord : srvRecord) {\n *   // Loop over the Internet Address RRs resolved for the SRV RR. The order of\n *   // the list depends on the prefered IP version setting of MiniDNS.\n *   for (InternetAddressRR inetAddressRR : srvRecord.addresses) {\n *     InetAddress inetAddress = inetAddressRR.getInetAddress();\n *     int port = srvAddresses.port;\n *     // Try to connect to inetAddress at port.\n *     …\n *   }\n * }\n * }\n * </pre>\n *\n * @author Florian Schmaus\n *\n */\npublic class ResolverApi {\n\n    public static final ResolverApi INSTANCE = new ResolverApi(new ReliableDnsClient());\n\n    private final AbstractDnsClient dnsClient;\n\n    public ResolverApi(AbstractDnsClient dnsClient) {\n        this.dnsClient = dnsClient;\n    }\n\n    public final <D extends Data> ResolverResult<D> resolve(String name, Class<D> type) throws IOException {\n        return resolve(DnsName.from(name), type);\n    }\n\n    public final <D extends Data> ResolverResult<D> resolve(DnsName name, Class<D> type) throws IOException {\n        TYPE t = TYPE.getType(type);\n        Question q = new Question(name, t);\n        return resolve(q);\n    }\n\n    public <D extends Data> ResolverResult<D> resolve(Question question) throws IOException {\n        DnsQueryResult dnsQueryResult = dnsClient.query(question);\n\n        return new ResolverResult<D>(question, dnsQueryResult, null);\n    }\n\n    public SrvResolverResult resolveSrv(SrvType type, String serviceName) throws IOException {\n        return resolveSrv(type.service, type.proto, DnsName.from(serviceName));\n    }\n\n    public SrvResolverResult resolveSrv(SrvType type, DnsName serviceName) throws IOException {\n        return resolveSrv(type.service, type.proto, serviceName);\n    }\n\n    public SrvResolverResult resolveSrv(SrvService service, SrvProto proto, String name) throws IOException {\n        return resolveSrv(service.dnsLabel, proto.dnsLabel, DnsName.from(name));\n    }\n\n    public SrvResolverResult resolveSrv(SrvService service, SrvProto proto, DnsName name) throws IOException {\n        return resolveSrv(service.dnsLabel, proto.dnsLabel, name);\n    }\n\n    public SrvResolverResult resolveSrv(DnsLabel service, DnsLabel proto, DnsName name) throws IOException {\n        SrvServiceProto srvServiceProto = new SrvServiceProto(service, proto);\n        return resolveSrv(name, srvServiceProto);\n    }\n\n    public SrvResolverResult resolveSrv(String name) throws IOException {\n        return resolveSrv(DnsName.from(name));\n    }\n\n    public ResolverResult<PTR> reverseLookup(CharSequence inetAddressCs) throws IOException {\n        InetAddress inetAddress = InetAddress.getByName(inetAddressCs.toString());\n        return reverseLookup(inetAddress);\n    }\n\n    public ResolverResult<PTR> reverseLookup(InetAddress inetAddress) throws IOException {\n        if (inetAddress instanceof Inet4Address) {\n            return reverseLookup((Inet4Address) inetAddress);\n        } else if (inetAddress instanceof Inet6Address) {\n            return reverseLookup((Inet6Address) inetAddress);\n        } else {\n            throw new IllegalArgumentException(\"The given InetAddress '\" + inetAddress + \"' is neither of type Inet4Address or Inet6Address\");\n        }\n    }\n\n    public ResolverResult<PTR> reverseLookup(Inet4Address inet4Address) throws IOException {\n        Question question = DnsClient.getReverseIpLookupQuestionFor(inet4Address);\n        return resolve(question);\n    }\n\n    public ResolverResult<PTR> reverseLookup(Inet6Address inet6Address) throws IOException {\n        Question question = DnsClient.getReverseIpLookupQuestionFor(inet6Address);\n        return resolve(question);\n    }\n\n    /**\n     * Resolve the {@link SRV} resource record for the given name. After ensuring that the resolution was successful\n     * with {@link SrvResolverResult#wasSuccessful()} , and, if DNSSEC was used, that the results could be verified with\n     * {@link SrvResolverResult#isAuthenticData()}, simply use {@link SrvResolverResult#getSortedSrvResolvedAddresses()} to\n     * retrieve the resolved IP addresses.\n     * <p>\n     * The name of SRV records is \"_[service]._[protocol].[serviceDomain]\", for example \"_xmpp-client._tcp.example.org\".\n     * </p>\n     *\n     * @param srvDnsName the name to resolve.\n     * @return a <code>SrvResolverResult</code> instance which can be used to retrieve the IP addresses.\n     * @throws IOException if an IO exception occurs.\n     */\n    public SrvResolverResult resolveSrv(DnsName srvDnsName) throws IOException {\n        final int labelCount = srvDnsName.getLabelCount();\n        if (labelCount < 3) {\n            throw new IllegalArgumentException();\n        }\n\n        DnsLabel service = srvDnsName.getLabel(labelCount - 1);\n        DnsLabel proto = srvDnsName.getLabel(labelCount - 2);\n        DnsName name = srvDnsName.stripToLabels(labelCount - 2);\n\n        SrvServiceProto srvServiceProto = new SrvServiceProto(service, proto);\n\n        return resolveSrv(name, srvServiceProto);\n    }\n\n    /**\n     * Resolve the {@link SRV} resource record for the given service name, service and protcol. After ensuring that the\n     * resolution was successful with {@link SrvResolverResult#wasSuccessful()} , and, if DNSSEC was used, that the\n     * results could be verified with {@link SrvResolverResult#isAuthenticData()}, simply use\n     * {@link SrvResolverResult#getSortedSrvResolvedAddresses()} to retrieve the resolved IP addresses.\n     *\n     * @param name the DNS name of the service.\n     * @param srvServiceProto the service and protocol to lookup.\n     * @return a <code>SrvResolverResult</code> instance which can be used to retrieve the IP addresses.\n     * @throws IOException if an I/O error occurs.\n     */\n    public SrvResolverResult resolveSrv(DnsName name, SrvServiceProto srvServiceProto) throws IOException {\n        DnsName srvDnsName = DnsName.from(srvServiceProto.service, srvServiceProto.proto, name);\n        ResolverResult<SRV> result = resolve(srvDnsName, SRV.class);\n\n        return new SrvResolverResult(result, srvServiceProto, this);\n    }\n\n    public final AbstractDnsClient getClient() {\n        return dnsClient;\n    }\n}\n"
  },
  {
    "path": "minidns-hla/src/main/java/org/minidns/hla/ResolverResult.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.hla;\n\nimport java.util.Collections;\nimport java.util.Set;\n\nimport org.minidns.MiniDnsException;\nimport org.minidns.MiniDnsException.NullResultException;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;\nimport org.minidns.dnssec.DnssecResultNotAuthenticException;\nimport org.minidns.dnssec.DnssecUnverifiedReason;\nimport org.minidns.record.Data;\n\npublic class ResolverResult<D extends Data> {\n\n    protected final Question question;\n    private final RESPONSE_CODE responseCode;\n    private final Set<D> data;\n    private final boolean isAuthenticData;\n    protected final Set<DnssecUnverifiedReason> unverifiedReasons;\n    protected final DnsMessage answer;\n    protected final DnsQueryResult result;\n\n    ResolverResult(Question question, DnsQueryResult result, Set<DnssecUnverifiedReason> unverifiedReasons) throws NullResultException {\n        // TODO: Is this null check still needed?\n        if (result == null) {\n            throw new MiniDnsException.NullResultException(question.asMessageBuilder().build());\n        }\n\n        this.result = result;\n\n        DnsMessage answer = result.response;\n        this.question = question;\n        this.responseCode = answer.responseCode;\n        this.answer = answer;\n\n        Set<D> r = answer.getAnswersFor(question);\n        if (r == null) {\n            this.data = Collections.emptySet();\n        } else {\n            this.data = Collections.unmodifiableSet(r);\n        }\n\n        if (unverifiedReasons == null) {\n            this.unverifiedReasons = null;\n            isAuthenticData = false;\n        } else {\n            this.unverifiedReasons = Collections.unmodifiableSet(unverifiedReasons);\n            isAuthenticData = this.unverifiedReasons.isEmpty();\n        }\n    }\n\n    public boolean wasSuccessful() {\n        return responseCode == RESPONSE_CODE.NO_ERROR;\n    }\n\n    public Set<D> getAnswers() {\n        throwIseIfErrorResponse();\n        return data;\n    }\n\n    public Set<D> getAnswersOrEmptySet() {\n        return data;\n    }\n\n    public RESPONSE_CODE getResponseCode() {\n        return responseCode;\n    }\n\n    public boolean isAuthenticData() {\n        throwIseIfErrorResponse();\n        return isAuthenticData;\n    }\n\n    /**\n     * Get the reasons the result could not be verified if any exists.\n     *\n     * @return The reasons the result could not be verified or <code>null</code>.\n     */\n    public Set<DnssecUnverifiedReason> getUnverifiedReasons() {\n        throwIseIfErrorResponse();\n        return unverifiedReasons;\n    }\n\n    public Question getQuestion() {\n        return question;\n    }\n\n    public void throwIfErrorResponse() throws ResolutionUnsuccessfulException {\n        ResolutionUnsuccessfulException resolutionUnsuccessfulException = getResolutionUnsuccessfulException();\n        if (resolutionUnsuccessfulException != null) throw resolutionUnsuccessfulException;\n    }\n\n    private ResolutionUnsuccessfulException resolutionUnsuccessfulException;\n\n    public ResolutionUnsuccessfulException getResolutionUnsuccessfulException() {\n        if (wasSuccessful()) return null;\n\n        if (resolutionUnsuccessfulException == null) {\n            resolutionUnsuccessfulException = new ResolutionUnsuccessfulException(question, responseCode);\n        }\n\n        return resolutionUnsuccessfulException;\n    }\n\n    private DnssecResultNotAuthenticException dnssecResultNotAuthenticException;\n\n    public DnssecResultNotAuthenticException getDnssecResultNotAuthenticException() {\n        if (!wasSuccessful())\n            return null;\n        if (isAuthenticData)\n            return null;\n\n        if (dnssecResultNotAuthenticException == null) {\n            dnssecResultNotAuthenticException = DnssecResultNotAuthenticException.from(getUnverifiedReasons());\n        }\n\n        return dnssecResultNotAuthenticException;\n    }\n\n    /**\n     * Get the raw answer DNS message we received. <b>This is likely not what you want</b>, try {@link #getAnswers()} instead.\n     *\n     * @return the raw answer DNS Message.\n     * @see #getAnswers()\n     */\n    public DnsMessage getRawAnswer() {\n        return answer;\n    }\n\n    public DnsQueryResult getDnsQueryResult() {\n        return result;\n    }\n\n    @Override\n    public final String toString() {\n        StringBuilder sb = new StringBuilder();\n\n        sb.append(getClass().getName()).append('\\n')\n               .append(\"Question: \").append(question).append('\\n')\n               .append(\"Response Code: \").append(responseCode).append('\\n');\n\n        if (responseCode == RESPONSE_CODE.NO_ERROR) {\n            if (isAuthenticData) {\n                sb.append(\"Results verified via DNSSEC\\n\");\n            }\n            if (hasUnverifiedReasons()) {\n                sb.append(unverifiedReasons).append('\\n');\n            }\n            sb.append(answer.answerSection);\n        }\n\n        return sb.toString();\n    }\n\n    boolean hasUnverifiedReasons() {\n        return unverifiedReasons != null && !unverifiedReasons.isEmpty();\n    }\n\n    protected void throwIseIfErrorResponse() {\n        ResolutionUnsuccessfulException resolutionUnsuccessfulException = getResolutionUnsuccessfulException();\n        if (resolutionUnsuccessfulException != null)\n            throw new IllegalStateException(\"Can not perform operation because the DNS resolution was unsuccessful\",\n                    resolutionUnsuccessfulException);\n    }\n}\n"
  },
  {
    "path": "minidns-hla/src/main/java/org/minidns/hla/SrvResolverResult.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.hla;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Set;\n\nimport org.minidns.AbstractDnsClient.IpVersionSetting;\nimport org.minidns.MiniDnsException.NullResultException;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.hla.srv.SrvServiceProto;\nimport org.minidns.record.A;\nimport org.minidns.record.AAAA;\nimport org.minidns.record.InternetAddressRR;\nimport org.minidns.record.SRV;\nimport org.minidns.util.SrvUtil;\n\npublic class SrvResolverResult extends ResolverResult<SRV> {\n\n    private final ResolverApi resolver;\n    private final IpVersionSetting ipVersion;\n    private final SrvServiceProto srvServiceProto;\n\n    private List<ResolvedSrvRecord> sortedSrvResolvedAddresses;\n\n    SrvResolverResult(ResolverResult<SRV> srvResult, SrvServiceProto srvServiceProto, ResolverApi resolver) throws NullResultException {\n        super(srvResult.question, srvResult.result, srvResult.unverifiedReasons);\n        this.resolver = resolver;\n        this.ipVersion = resolver.getClient().getPreferedIpVersion();\n        this.srvServiceProto = srvServiceProto;\n    }\n\n    /**\n     * Get a list ordered by priority and weight of the resolved SRV records. This method will throw if there was an\n     * error response or if subsequent {@link A} or {@link AAAA} resource record lookups fail. It will return\n     * {@code null} in case the service is decidedly not available at this domain.\n     *\n     * @return a list ordered by priority and weight of the related SRV records.\n     * @throws IOException in case an I/O error occurs.\n     */\n    public List<ResolvedSrvRecord> getSortedSrvResolvedAddresses() throws IOException {\n        if (sortedSrvResolvedAddresses != null) {\n            return sortedSrvResolvedAddresses;\n        }\n\n        throwIseIfErrorResponse();\n\n        if (isServiceDecidedlyNotAvailableAtThisDomain()) {\n            return null;\n        }\n\n        List<SRV> srvRecords = SrvUtil.sortSrvRecords(getAnswers());\n\n        List<ResolvedSrvRecord> res = new ArrayList<>(srvRecords.size());\n        for (SRV srvRecord : srvRecords) {\n            ResolverResult<A> aRecordsResult = null;\n            ResolverResult<AAAA> aaaaRecordsResult = null;\n            Set<A> aRecords = Collections.emptySet();\n            if (ipVersion.v4) {\n                aRecordsResult = resolver.resolve(srvRecord.target, A.class);\n                if (aRecordsResult.wasSuccessful() && !aRecordsResult.hasUnverifiedReasons()) {\n                    aRecords = aRecordsResult.getAnswers();\n                }\n            }\n\n            Set<AAAA> aaaaRecords = Collections.emptySet();\n            if (ipVersion.v6) {\n                aaaaRecordsResult = resolver.resolve(srvRecord.target, AAAA.class);\n                if (aaaaRecordsResult.wasSuccessful() && !aaaaRecordsResult.hasUnverifiedReasons()) {\n                    aaaaRecords = aaaaRecordsResult.getAnswers();\n                }\n            }\n\n            if (aRecords.isEmpty() && aaaaRecords.isEmpty()) {\n                // TODO Possibly check for (C|D)NAME usage and throw a meaningful exception that it is not allowed for\n                // the target of an SRV to be an alias as per RFC 2782.\n                /*\n                ResolverResult<CNAME> cnameRecordResult = resolve(srvRecord.name, CNAME.class);\n                if (cnameRecordResult.wasSuccessful()) {\n                }\n                */\n                continue;\n            }\n\n            List<InternetAddressRR<? extends InetAddress>> srvAddresses = new ArrayList<>(aRecords.size() + aaaaRecords.size());\n            switch (ipVersion) {\n            case v4only:\n                srvAddresses.addAll(aRecords);\n                break;\n            case v6only:\n                srvAddresses.addAll(aaaaRecords);\n                break;\n            case v4v6:\n                srvAddresses.addAll(aRecords);\n                srvAddresses.addAll(aaaaRecords);\n                break;\n            case v6v4:\n                srvAddresses.addAll(aaaaRecords);\n                srvAddresses.addAll(aRecords);\n                break;\n            }\n\n            ResolvedSrvRecord resolvedSrvAddresses = new ResolvedSrvRecord(question.name, srvServiceProto, srvRecord, srvAddresses,\n                    aRecordsResult, aaaaRecordsResult);\n            res.add(resolvedSrvAddresses);\n        }\n\n        sortedSrvResolvedAddresses = res;\n\n        return res;\n    }\n\n    public boolean isServiceDecidedlyNotAvailableAtThisDomain() {\n        Set<SRV> answers = getAnswers();\n        if (answers.size() != 1) {\n            return false;\n        }\n\n        SRV singleAnswer = answers.iterator().next();\n        return !singleAnswer.isServiceAvailable();\n    }\n\n    public static final class ResolvedSrvRecord {\n        public final DnsName name;\n        public final SrvServiceProto srvServiceProto;\n        public final SRV srv;\n        public final List<InternetAddressRR<? extends InetAddress>> addresses;\n        public final ResolverResult<A> aRecordsResult;\n        public final ResolverResult<AAAA> aaaaRecordsResult;\n\n        /**\n         * The port announced by the SRV RR. This is simply a shortcut for <code>srv.port</code>.\n         */\n        public final int port;\n\n        private ResolvedSrvRecord(DnsName name, SrvServiceProto srvServiceProto, SRV srv,\n                List<InternetAddressRR<? extends InetAddress>> addresses, ResolverResult<A> aRecordsResult,\n                ResolverResult<AAAA> aaaaRecordsResult) {\n            this.name = name;\n            this.srvServiceProto = srvServiceProto;\n            this.srv = srv;\n            this.addresses = Collections.unmodifiableList(addresses);\n            this.port = srv.port;\n            this.aRecordsResult = aRecordsResult;\n            this.aaaaRecordsResult = aaaaRecordsResult;\n        }\n    }\n\n    /**\n     * Convenience method to sort multiple resolved SRV RRs. This is for example required by XEP-0368, where\n     * {@link org.minidns.hla.srv.SrvService#xmpp_client} and {@link org.minidns.hla.srv.SrvService#xmpps_client} may be\n     * sorted together.\n     *\n     * @param resolvedSrvRecordCollections a collection of resolved SRV records.\n     * @return a list ordered by priority and weight of the related SRV records.\n     */\n    @SafeVarargs\n    public static List<ResolvedSrvRecord> sortMultiple(Collection<ResolvedSrvRecord>... resolvedSrvRecordCollections) {\n        int srvRecordsCount = 0;\n        for (Collection<ResolvedSrvRecord> resolvedSrvRecords : resolvedSrvRecordCollections) {\n            if (resolvedSrvRecords == null) {\n                continue;\n            }\n            srvRecordsCount += resolvedSrvRecords.size();\n        }\n\n        List<SRV> srvToSort = new ArrayList<>(srvRecordsCount);\n        IdentityHashMap<SRV, ResolvedSrvRecord> identityMap = new IdentityHashMap<>(srvRecordsCount);\n        for (Collection<ResolvedSrvRecord> resolvedSrvRecords : resolvedSrvRecordCollections) {\n            if (resolvedSrvRecords == null) {\n                continue;\n            }\n            for (ResolvedSrvRecord resolvedSrvRecord : resolvedSrvRecords) {\n                srvToSort.add(resolvedSrvRecord.srv);\n                identityMap.put(resolvedSrvRecord.srv, resolvedSrvRecord);\n            }\n        }\n\n        List<SRV> sortedSrvs = SrvUtil.sortSrvRecords(srvToSort);\n        assert sortedSrvs.size() == srvRecordsCount;\n\n        List<ResolvedSrvRecord> res = new ArrayList<>(srvRecordsCount);\n        for (SRV sortedSrv : sortedSrvs) {\n            ResolvedSrvRecord resolvedSrvRecord = identityMap.get(sortedSrv);\n            res.add(resolvedSrvRecord);\n        }\n\n        return res;\n    }\n}\n"
  },
  {
    "path": "minidns-hla/src/main/java/org/minidns/hla/srv/SrvProto.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.hla.srv;\n\nimport org.minidns.dnslabel.DnsLabel;\n\npublic enum SrvProto {\n\n    // @formatter:off\n    tcp,\n    udp,\n    ;\n    // @formatter:on\n\n    @SuppressWarnings(\"ImmutableEnumChecker\")\n    public final DnsLabel dnsLabel;\n\n    SrvProto() {\n        dnsLabel = DnsLabel.from('_' + name());\n    }\n}\n"
  },
  {
    "path": "minidns-hla/src/main/java/org/minidns/hla/srv/SrvService.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.hla.srv;\n\nimport org.minidns.dnslabel.DnsLabel;\n\npublic enum SrvService {\n\n    // @formatter:off\n    xmpp_client,\n    xmpp_server,\n\n    /**\n     * XMPP client-to-server (c2s) connections using implicit TLS (also known as \"Direct TLS\").\n     *\n     * @see <a href=\"https://xmpp.org/extensions/xep-0368.html\">XEP-0368: SRV records for XMPP over TLS</a>\n     */\n    xmpps_client,\n\n    /**\n     * XMPP server-to-server (s2s) connections using implicit TLS (also known as \"Direct TLS\").\n     *\n     * @see <a href=\"https://xmpp.org/extensions/xep-0368.html\">XEP-0368: SRV records for XMPP over TLS</a>\n     */\n    xmpps_server,\n    ;\n    // @formatter:on\n\n    @SuppressWarnings(\"ImmutableEnumChecker\")\n    public final DnsLabel dnsLabel;\n\n    SrvService() {\n        String enumName = name().replaceAll(\"_\", \"-\");\n        dnsLabel = DnsLabel.from('_' + enumName);\n    }\n}\n"
  },
  {
    "path": "minidns-hla/src/main/java/org/minidns/hla/srv/SrvServiceProto.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.hla.srv;\n\nimport org.minidns.dnslabel.DnsLabel;\n\n/**\n *  The Serivce and Protocol part of a SRV owner name. The format of a SRV owner name is \"_Service._Proto.Name\".\n */\npublic class SrvServiceProto {\n\n    public final DnsLabel service;\n    public final DnsLabel proto;\n\n    public SrvServiceProto(DnsLabel service, DnsLabel proto) {\n        this.service = service;\n        this.proto = proto;\n    }\n}\n"
  },
  {
    "path": "minidns-hla/src/main/java/org/minidns/hla/srv/SrvType.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.hla.srv;\n\npublic enum SrvType {\n\n    // @formatter:off\n    xmpp_client(SrvService.xmpp_client, SrvProto.tcp),\n    xmpp_server(SrvService.xmpp_server, SrvProto.tcp),\n    ;\n    // @formatter:on\n\n    public final SrvService service;\n    public final SrvProto proto;\n\n    SrvType(SrvService service, SrvProto proto) {\n        this.service = service;\n        this.proto = proto;\n    }\n}\n"
  },
  {
    "path": "minidns-hla/src/test/java/org/minidns/hla/MiniDnsHlaTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.hla;\n\nimport org.junit.jupiter.api.Test;\n\npublic class MiniDnsHlaTest {\n\n    /**\n     * Dummy test to make jacocoRootReport happy.\n     */\n    @Test\n    public void nopTest() {\n    }\n\n}\n"
  },
  {
    "path": "minidns-integration-test/build.gradle",
    "content": "/*\n * Copyright 2015 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\nplugins {\n\tid 'org.minidns.java-conventions'\n\tid 'org.minidns.application-conventions'\n}\n\ndescription = \"MiniDNS' integration test suite\"\n\nmainClassName = 'org.minidns.integrationtest.IntegrationTestHelper'\napplicationDefaultJvmArgs = [\"-enableassertions\"]\n\ndependencies {\n    api project(':minidns-client')\n    api project(':minidns-async')\n\tapi project(':minidns-iterative-resolver')\n    api project(':minidns-dnssec')\n    api project(':minidns-hla')\n    implementation \"org.junit.vintage:junit-vintage-engine:$junitVersion\"\n\timplementation \"org.junit.jupiter:junit-jupiter-api:$junitVersion\"\n    testImplementation project(path: \":minidns-client\", configuration: \"testRuntime\")\n}\n\nrun {\n   // Pass all system properties down to the \"application\" run.\n   // Used e.g. for integration test configuration via properties.\n   systemProperties System.getProperties()\n}\n"
  },
  {
    "path": "minidns-integration-test/src/main/java/org/minidns/integrationtest/AsyncApiTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.integrationtest;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\nimport java.io.IOException;\n\nimport org.minidns.DnsClient;\nimport org.minidns.record.Record;\nimport org.minidns.MiniDnsFuture;\nimport org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.source.AbstractDnsDataSource;\nimport org.minidns.source.AbstractDnsDataSource.QueryMode;\nimport org.minidns.source.async.AsyncNetworkDataSource;\n\npublic class AsyncApiTest {\n\n    public static void main(String[] args) throws IOException {\n        tcpAsyncApiTest();\n    }\n\n    public static void simpleAsyncApiTest() throws IOException {\n        DnsClient client = new DnsClient();\n        client.setDataSource(new AsyncNetworkDataSource());\n        client.getDataSource().setTimeout(60 * 60 * 1000);\n\n        MiniDnsFuture<DnsQueryResult, IOException> future = client.queryAsync(\"example.com\", Record.TYPE.NS);\n        DnsQueryResult result = future.getOrThrow();\n        assertEquals(RESPONSE_CODE.NO_ERROR, result.response.responseCode);\n    }\n\n    public static void tcpAsyncApiTest() throws IOException {\n        AbstractDnsDataSource dataSource = new AsyncNetworkDataSource();\n        dataSource.setTimeout(60 * 60 * 1000);\n        dataSource.setUdpPayloadSize(256);\n        dataSource.setQueryMode(QueryMode.tcp);\n\n        DnsClient client = new DnsClient();\n        client.setDataSource(dataSource);\n        client.setAskForDnssec(true);\n\n        MiniDnsFuture<DnsQueryResult, IOException> future = client.queryAsync(\"google.com\", Record.TYPE.AAAA);\n        DnsQueryResult result = future.getOrThrow();\n        assertEquals(RESPONSE_CODE.NO_ERROR, result.response.responseCode);\n    }\n}\n"
  },
  {
    "path": "minidns-integration-test/src/main/java/org/minidns/integrationtest/CoreTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.integrationtest;\n\nimport org.minidns.DnsClient;\nimport org.minidns.cache.LruCache;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.record.Data;\nimport org.minidns.record.Record;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\npublic class CoreTest {\n    @IntegrationTest\n    public static void testExampleCom() throws IOException {\n        DnsClient client = new DnsClient(new LruCache(1024));\n        String exampleIp4 = \"93.184.216.34\"; // stable?\n        String exampleIp6 = \"2606:2800:220:1:248:1893:25c8:1946\"; // stable?\n        assertEquals(client.query(\"example.com\", Record.TYPE.A).response.answerSection.get(0).payloadData.toString(), exampleIp4);\n        assertEquals(client.query(\"www.example.com\", Record.TYPE.A).response.answerSection.get(0).payloadData.toString(), exampleIp4);\n        assertEquals(client.query(\"example.com\", Record.TYPE.AAAA).response.answerSection.get(0).payloadData.toString(), exampleIp6);\n        assertEquals(client.query(\"www.example.com\", Record.TYPE.AAAA).response.answerSection.get(0).payloadData.toString(), exampleIp6);\n\n        DnsQueryResult nsResult = client.query(\"example.com\", Record.TYPE.NS);\n        List<String> values = new ArrayList<>();\n        for (Record<? extends Data> record : nsResult.response.answerSection) {\n            values.add(record.payloadData.toString());\n        }\n        Collections.sort(values);\n        assertEquals(values.get(0), \"a.iana-servers.net.\");\n        assertEquals(values.get(1), \"b.iana-servers.net.\");\n    }\n\n    @IntegrationTest\n    public static void testTcpAnswer() throws IOException {\n        DnsClient client = new DnsClient(new LruCache(1024));\n        client.setAskForDnssec(true);\n        client.setDisableResultFilter(true);\n        DnsQueryResult result = client.query(\"www-nsec.example.com\", Record.TYPE.A);\n        assertNotNull(result);\n        assertTrue(result.response.toArray().length > 512);\n    }\n}\n"
  },
  {
    "path": "minidns-integration-test/src/main/java/org/minidns/integrationtest/DaneTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.integrationtest;\n\nimport org.minidns.dane.DaneVerifier;\n\nimport javax.net.ssl.HttpsURLConnection;\n\nimport org.junit.Ignore;\n\nimport java.io.IOException;\nimport java.net.URL;\nimport java.security.cert.CertificateException;\n\npublic class DaneTest {\n\n    @Ignore\n    @IntegrationTest\n    public static void testOarcDaneGood() throws IOException, CertificateException {\n        DaneVerifier daneVerifier = new DaneVerifier();\n        daneVerifier.verifiedConnect((HttpsURLConnection) new URL(\"https://good.dane.dns-oarc.net/\").openConnection());\n    }\n\n    @Ignore\n    @IntegrationTest()\n    public static void testOarcDaneBadHash() throws IOException, CertificateException {\n        DaneVerifier daneVerifier = new DaneVerifier();\n        daneVerifier.verifiedConnect((HttpsURLConnection) new URL(\"https://bad-hash.dane.dns-oarc.net/\").openConnection());\n    }\n\n    @Ignore\n    @IntegrationTest\n    public static void testOarcDaneBadParams() throws IOException, CertificateException {\n        DaneVerifier daneVerifier = new DaneVerifier();\n        daneVerifier.verifiedConnect((HttpsURLConnection) new URL(\"https://bad-params.dane.dns-oarc.net/\").openConnection());\n    }\n}\n"
  },
  {
    "path": "minidns-integration-test/src/main/java/org/minidns/integrationtest/DnssecTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.integrationtest;\n\nimport java.io.IOException;\nimport java.util.Iterator;\n\nimport org.junit.Ignore;\nimport org.minidns.cache.LruCache;\nimport org.minidns.dnssec.DnssecClient;\nimport org.minidns.dnssec.DnssecQueryResult;\nimport org.minidns.dnssec.DnssecUnverifiedReason;\nimport org.minidns.dnssec.DnssecValidationFailedException;\nimport org.minidns.record.Record;\n\nimport static org.junit.jupiter.api.Assertions.assertFalse;\n\npublic class DnssecTest {\n\n    @Ignore\n    @IntegrationTest\n    public static void testOarcDaneBadSig() throws Exception {\n        DnssecClient client = new DnssecClient(new LruCache(1024));\n        assertFalse(client.queryDnssec(\"_443._tcp.bad-sig.dane.dns-oarc.net\", Record.TYPE.TLSA).isAuthenticData());\n    }\n\n    @IntegrationTest\n    public static void testUniDueSigOk() throws IOException {\n        DnssecClient client = new DnssecClient(new LruCache(1024));\n        assertAuthentic(client.queryDnssec(\"sigok.verteiltesysteme.net\", Record.TYPE.A));\n    }\n\n    @IntegrationTest(expected = DnssecValidationFailedException.class)\n    public static void testUniDueSigFail() throws IOException {\n        DnssecClient client = new DnssecClient(new LruCache(1024));\n        client.query(\"sigfail.verteiltesysteme.net\", Record.TYPE.A);\n    }\n\n    @IntegrationTest\n    public static void testCloudFlare() throws IOException {\n        DnssecClient client = new DnssecClient(new LruCache(1024));\n        assertAuthentic(client.queryDnssec(\"www.cloudflare-dnssec-auth.com\", Record.TYPE.A));\n    }\n\n    private static void assertAuthentic(DnssecQueryResult dnssecMessage) {\n        if (dnssecMessage.isAuthenticData()) return;\n\n        StringBuilder sb = new StringBuilder();\n        sb.append(\"Answer should contain authentic data while it does not. Reasons:\\n\");\n        for (Iterator<DnssecUnverifiedReason> it = dnssecMessage.getUnverifiedReasons().iterator(); it.hasNext(); ) {\n            DnssecUnverifiedReason unverifiedReason = it.next();\n            sb.append(unverifiedReason);\n            if (it.hasNext()) sb.append('\\n');\n        }\n        throw new AssertionError(sb.toString());\n    }\n}\n"
  },
  {
    "path": "minidns-integration-test/src/main/java/org/minidns/integrationtest/HlaTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.integrationtest;\n\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertFalse;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.IOException;\nimport java.util.Set;\n\nimport org.minidns.hla.ResolverApi;\nimport org.minidns.hla.ResolverResult;\nimport org.minidns.hla.SrvResolverResult;\nimport org.minidns.record.A;\nimport org.minidns.record.SRV;\n\npublic class HlaTest {\n\n    @IntegrationTest\n    public static void resolverTest() throws IOException {\n        ResolverResult<A> res = ResolverApi.INSTANCE.resolve(\"geekplace.eu\", A.class);\n        assertEquals(true, res.wasSuccessful());\n        Set<A> answers = res.getAnswers();\n        assertEquals(1, answers.size());\n        assertArrayEquals(new A(5, 45, 100, 158).toByteArray(), answers.iterator().next().toByteArray());\n    }\n\n    @IntegrationTest\n    public static void idnSrvTest() throws IOException {\n        ResolverResult<SRV> res = ResolverApi.INSTANCE.resolve(\"_xmpp-client._tcp.im.plä.net\", SRV.class);\n        Set<SRV> answers = res.getAnswers();\n        assertEquals(1, answers.size());\n\n        SRV srv = answers.iterator().next();\n\n        ResolverResult<A> aRes = ResolverApi.INSTANCE.resolve(srv.target, A.class);\n\n        assertTrue(aRes.wasSuccessful());\n    }\n\n    @IntegrationTest\n    public static void resolveSrvTest() throws IOException {\n        SrvResolverResult resolverResult = ResolverApi.INSTANCE.resolveSrv(\"_xmpp-client._tcp.jabber.org\");\n        Set<SRV> answers = resolverResult.getAnswers();\n        assertFalse(answers.isEmpty());\n    }\n}\n"
  },
  {
    "path": "minidns-integration-test/src/main/java/org/minidns/integrationtest/IntegrationTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.integrationtest;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.METHOD)\npublic @interface IntegrationTest {\n    Class<?> expected() default Class.class;\n}\n"
  },
  {
    "path": "minidns-integration-test/src/main/java/org/minidns/integrationtest/IntegrationTestHelper.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.integrationtest;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.logging.Level;\nimport java.util.logging.Logger;\n\nimport org.junit.Ignore;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.jul.MiniDnsJul;\nimport org.minidns.record.Record.TYPE;\n\npublic class IntegrationTestHelper {\n\n    public static final DnsName DNSSEC_DOMAIN = DnsName.from(\"verteiltesysteme.net\");\n    public static final TYPE RR_TYPE = TYPE.A;\n\n    private static Set<Class<?>> testClasses = new HashSet<>();\n    private static Logger LOGGER = Logger.getLogger(IntegrationTestHelper.class.getName());\n\n    enum TestResult {\n        Success,\n        Failure,\n    }\n\n    static {\n        testClasses.add(CoreTest.class);\n        testClasses.add(DnssecTest.class);\n        testClasses.add(DaneTest.class);\n        testClasses.add(HlaTest.class);\n        testClasses.add(NsidTest.class);\n        testClasses.add(IterativeDnssecTest.class);\n    }\n\n    private static final String MINTTEST = \"minttest.\";\n\n    public static void main(String[] args) {\n        Properties systemProperties = System.getProperties();\n        String debugString = systemProperties.getProperty(MINTTEST + \"debug\", Boolean.toString(false));\n        boolean debug = Boolean.parseBoolean(debugString);\n        if (debug) {\n            LOGGER.info(\"Enabling debug and trace output\");\n            MiniDnsJul.enableMiniDnsTrace();\n        }\n\n        int testsRun = 0;\n        List<Method> successfulTests = new ArrayList<>();\n        List<Method> failedTests = new ArrayList<>();\n        List<Method> ignoredTests = new ArrayList<>();\n        for (final Class<?> aClass : testClasses) {\n            for (final Method method : aClass.getDeclaredMethods()) {\n                if (!method.isAnnotationPresent(IntegrationTest.class)) {\n                    continue;\n                }\n                if (method.isAnnotationPresent(Ignore.class)) {\n                    ignoredTests.add(method);\n                    continue;\n                }\n                TestResult result = invokeTest(method, aClass);\n                testsRun++;\n                switch (result) {\n                case Success:\n                    successfulTests.add(method);\n                    break;\n                case Failure:\n                    failedTests.add(method);\n                    break;\n                }\n            }\n        }\n        StringBuilder resultMessage = new StringBuilder();\n        resultMessage.append(\"MiniDNS Integration Test Result: [\").append(successfulTests.size()).append('/').append(testsRun).append(\"] \");\n        if (!ignoredTests.isEmpty()) {\n            resultMessage.append(\"(Ignored: \").append(ignoredTests.size()).append(\") \");\n        }\n        int exitStatus = 0;\n        if (failedTests.isEmpty()) {\n            resultMessage.append(\"SUCCESS \\\\o/\");\n        } else {\n            resultMessage.append(\"FAILURE :(\");\n            exitStatus = 2;\n        }\n        LOGGER.info(resultMessage.toString());\n        System.exit(exitStatus);\n    }\n\n    public static TestResult invokeTest(Method method, Class<?> aClass) {\n        Class<?> expected = method.getAnnotation(IntegrationTest.class).expected();\n        if (!Exception.class.isAssignableFrom(expected)) expected = null;\n\n        String testClassName = method.getDeclaringClass().getSimpleName();\n        String testMethodName = method.getName();\n\n        LOGGER.logp(Level.INFO, testClassName, testMethodName, \"Test start.\");\n        try {\n            method.invoke(null);\n\n            if (expected != null) {\n                LOGGER.logp(Level.WARNING, testClassName, testMethodName, \"Test failed: expected exception \" + expected + \" was not thrown!\");\n                return TestResult.Failure;\n            } else {\n                LOGGER.logp(Level.INFO, testClassName, testMethodName, \"Test suceeded.\");\n                return TestResult.Success;\n            }\n        } catch (InvocationTargetException e) {\n            if (expected != null && expected.isAssignableFrom(e.getTargetException().getClass())) {\n                LOGGER.logp(Level.INFO, testClassName, testMethodName, \"Test suceeded.\");\n                return TestResult.Success;\n            } else {\n                LOGGER.logp(Level.WARNING, testClassName, testMethodName, \"Test failed: unexpected exception was thrown: \", e.getTargetException());\n                return TestResult.Failure;\n            }\n        } catch (IllegalAccessException | NullPointerException e) {\n            LOGGER.logp(Level.SEVERE, testClassName, testMethodName, \"Test failed: could not invoke test, is it public static?\");\n            return TestResult.Failure;\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-integration-test/src/main/java/org/minidns/integrationtest/IntegrationTestTools.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.integrationtest;\n\nimport org.minidns.DnsCache;\nimport org.minidns.cache.ExtendedLruCache;\nimport org.minidns.cache.FullLruCache;\nimport org.minidns.cache.LruCache;\nimport org.minidns.dnssec.DnssecClient;\nimport org.minidns.source.NetworkDataSourceWithAccounting;\n\npublic class IntegrationTestTools {\n\n    public enum CacheConfig {\n        without,\n        normal,\n        extended,\n        full,\n    }\n\n    public static DnssecClient getClient(CacheConfig cacheConfig) {\n        DnsCache cache;\n        switch (cacheConfig) {\n        case without:\n            cache = null;\n            break;\n        case normal:\n            cache = new LruCache();\n            break;\n        case extended:\n            cache = new ExtendedLruCache();\n            break;\n        case full:\n            cache = new FullLruCache();\n            break;\n        default:\n            throw new IllegalStateException();\n        }\n\n        DnssecClient client = new DnssecClient(cache);\n        client.setDataSource(new NetworkDataSourceWithAccounting());\n        return client;\n    }\n\n}\n"
  },
  {
    "path": "minidns-integration-test/src/main/java/org/minidns/integrationtest/IterativeDnssecTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.integrationtest;\n\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport java.io.IOException;\n\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnssec.DnssecClient;\nimport org.minidns.dnssec.DnssecQueryResult;\nimport org.minidns.integrationtest.IntegrationTestTools.CacheConfig;\nimport org.minidns.iterative.ReliableDnsClient.Mode;\nimport org.minidns.record.Record.TYPE;\nimport org.minidns.source.NetworkDataSourceWithAccounting;\n\npublic class IterativeDnssecTest {\n\n    private static final DnsName DNSSEC_DOMAIN = IntegrationTestHelper.DNSSEC_DOMAIN;\n    private static final TYPE RR_TYPE = IntegrationTestHelper.RR_TYPE;\n\n    @IntegrationTest\n    public static void shouldRequireLessQueries() throws IOException {\n        DnssecClient normalCacheClient = getClient(CacheConfig.normal);\n        DnssecQueryResult normalCacheResult = normalCacheClient.queryDnssec(DNSSEC_DOMAIN, RR_TYPE);\n        assertTrue(normalCacheResult.isAuthenticData());\n        NetworkDataSourceWithAccounting normalCacheNdswa = NetworkDataSourceWithAccounting.from(normalCacheClient);\n\n        DnssecClient extendedCacheClient = getClient(CacheConfig.extended);\n        DnssecQueryResult extendedCacheResult = extendedCacheClient.queryDnssec(DNSSEC_DOMAIN, RR_TYPE);\n        assertTrue(extendedCacheResult.isAuthenticData());\n        NetworkDataSourceWithAccounting extendedCacheNdswa = NetworkDataSourceWithAccounting.from(extendedCacheClient);\n\n        final int normalCacheSuccessfulQueries = normalCacheNdswa.getStats().successfulQueries;\n        final int extendedCacheSuccessfulQueries = extendedCacheNdswa.getStats().successfulQueries;\n        assertTrue(\n                normalCacheSuccessfulQueries > extendedCacheSuccessfulQueries,\n                \"Extend cache successful query count \" + extendedCacheSuccessfulQueries\n                + \" is not less than normal cache successful query count \" + normalCacheSuccessfulQueries);\n    }\n\n    private static DnssecClient getClient(CacheConfig cacheConfig) {\n        DnssecClient client = IntegrationTestTools.getClient(cacheConfig);\n        client.setMode(Mode.iterativeOnly);\n        return client;\n    }\n}\n"
  },
  {
    "path": "minidns-integration-test/src/main/java/org/minidns/integrationtest/NsidTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.integrationtest;\n\nimport java.io.IOException;\nimport java.net.InetAddress;\n\nimport org.minidns.DnsClient;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.edns.Nsid;\nimport org.minidns.edns.Edns.OptionCode;\nimport org.minidns.iterative.IterativeDnsClient;\nimport org.minidns.record.Record.TYPE;\n\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\npublic class NsidTest {\n\n    @IntegrationTest\n    public static Nsid testNsidLRoot() {\n        DnsClient client = new DnsClient(null) {\n            @Override\n            protected DnsMessage.Builder newQuestion(DnsMessage.Builder message) {\n                message.getEdnsBuilder().addEdnsOption(Nsid.REQUEST);\n                return super.newQuestion(message);\n            }\n        };\n        DnsQueryResult result = null;\n        Question q = new Question(\"de\", TYPE.NS);\n        for (InetAddress lRoot : IterativeDnsClient.getRootServer('l')) {\n            try {\n                result = client.query(q, lRoot);\n            } catch (IOException e) {\n                continue;\n            }\n            break;\n        }\n        Nsid nsid = result.response.getEdns().getEdnsOption(OptionCode.NSID);\n        assertNotNull(nsid);\n        return nsid;\n    }\n}\n"
  },
  {
    "path": "minidns-integration-test/src/main/java/org/minidns/jul/MiniDnsJul.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.jul;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Instant;\nimport java.time.format.DateTimeFormatter;\nimport java.util.logging.ConsoleHandler;\nimport java.util.logging.Formatter;\nimport java.util.logging.Handler;\nimport java.util.logging.Level;\nimport java.util.logging.LogManager;\nimport java.util.logging.LogRecord;\nimport java.util.logging.Logger;\n\n@SuppressWarnings(\"DateFormatConstant\")\npublic class MiniDnsJul {\n\n    private static final Logger LOGGER = Logger.getLogger(MiniDnsJul.class.getName());\n\n    private static final InputStream LOG_MANAGER_CONFIG = new ByteArrayInputStream((\n// @formatter:off\n\"org.minidns.level=FINEST\" + '\\n'\n).getBytes(StandardCharsets.UTF_8)\n);\n// @formatter:on\n\n    private static final DateTimeFormatter LONG_LOG_TIME_FORMAT = DateTimeFormatter.ofPattern(\"hh:mm:ss.SSS\");\n\n    private static final DateTimeFormatter SHORT_LOG_TIME_FORMAT = DateTimeFormatter.ofPattern(\"mm:ss.SSS\");\n\n    private static final Handler CONSOLE_HANDLER = new ConsoleHandler();\n\n    private static boolean shortLog = true;\n\n    static {\n        try {\n            LogManager.getLogManager().readConfiguration(LOG_MANAGER_CONFIG);\n        } catch (SecurityException | IOException e) {\n            LOGGER.log(Level.SEVERE, \"Could not apply MiniDNS JUL configuration\", e);\n        }\n\n        CONSOLE_HANDLER.setLevel(Level.OFF);\n        CONSOLE_HANDLER.setFormatter(new Formatter() {\n            @Override\n            public String format(LogRecord logRecord) {\n                StringBuilder sb = new StringBuilder(256);\n\n                Instant date = Instant.ofEpochMilli(logRecord.getMillis());\n                String dateString;\n                if (shortLog) {\n\t\t\t\t\tdateString = SHORT_LOG_TIME_FORMAT.format(date);\n                } else {\n\t\t\t\t\tdateString = LONG_LOG_TIME_FORMAT.format(date);\n                }\n                sb.append(dateString).append(' ');\n\n                String level = logRecord.getLevel().toString();\n                if (shortLog) {\n                    level = level.substring(0, 1);\n                }\n                sb.append(level).append(' ');\n\n                String loggerName = logRecord.getLoggerName();\n                if (shortLog) {\n                    String[] parts = loggerName.split(\"\\\\.\");\n                    loggerName = parts[parts.length > 1 ? parts.length - 1 : 0];\n                }\n                sb.append(loggerName);\n                sb.append(' ').append(logRecord.getSourceMethodName());\n\n                if (shortLog) {\n                    sb.append(' ');\n                } else {\n                    sb.append('\\n');\n                }\n\n                sb.append(formatMessage(logRecord));\n                if (logRecord.getThrown() != null) {\n                    StringWriter sw = new StringWriter();\n                    PrintWriter pw = new PrintWriter(sw);\n                    // CHECKSTYLE:OFF\n                    pw.println();\n                    logRecord.getThrown().printStackTrace(pw);\n                    // CHECKSTYLE:ON\n                    pw.close();\n                    sb.append(sw);\n                }\n                sb.append('\\n');\n                return sb.toString();\n            }\n        });\n        Logger.getLogger(\"org.minidns\").addHandler(CONSOLE_HANDLER);\n    }\n\n    public static void enableMiniDnsTrace() {\n        enableMiniDnsTrace(true);\n    }\n\n    public static void enableMiniDnsTrace(boolean shortLog) {\n        MiniDnsJul.shortLog = shortLog;\n        CONSOLE_HANDLER.setLevel(Level.FINEST);\n    }\n\n    public static void disableMiniDnsTrace() {\n        CONSOLE_HANDLER.setLevel(Level.OFF);\n    }\n}\n"
  },
  {
    "path": "minidns-integration-test/src/test/java/org/minidns/integrationtest/IntegrationTestTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.integrationtest;\n\nimport org.junit.jupiter.api.Test;\n\npublic class IntegrationTestTest {\n    /**\n     * Just here to ensure jacoco is not complaining.\n     */\n    @Test\n    public void emptyTest() {\n    }\n}\n"
  },
  {
    "path": "minidns-iterative-resolver/build.gradle",
    "content": "plugins {\n\tid 'org.minidns.java-conventions'\n\tid 'org.minidns.android-conventions'\n}\n\ndescription = \"A DNS client that can iteratively resolve resource records\"\n\ndependencies {\n    api project(':minidns-client')\n    testImplementation project(path: \":minidns-client\", configuration: \"testRuntime\")\n}\n"
  },
  {
    "path": "minidns-iterative-resolver/src/main/java/org/minidns/iterative/IterativeClientException.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.iterative;\n\nimport java.net.InetAddress;\n\nimport org.minidns.MiniDnsException;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\n\npublic abstract class IterativeClientException extends MiniDnsException {\n\n    /**\n     * \n     */\n    private static final long serialVersionUID = 1L;\n\n    protected IterativeClientException(String message) {\n        super(message);\n    }\n\n    public static class LoopDetected extends IterativeClientException {\n\n        /**\n         * \n         */\n        private static final long serialVersionUID = 1L;\n\n        public final InetAddress address;\n        public final Question question;\n\n        public LoopDetected(InetAddress address, Question question) {\n            super(\"Resolution loop detected: We already asked \" + address + \" about \" + question);\n            this.address = address;\n            this.question = question;\n        }\n\n    }\n\n    public static class MaxIterativeStepsReached extends IterativeClientException {\n\n        /**\n         * \n         */\n        private static final long serialVersionUID = 1L;\n\n        public MaxIterativeStepsReached() {\n            super(\"Maxmimum steps reached\");\n        }\n\n    }\n\n    public static class NotAuthoritativeNorGlueRrFound extends IterativeClientException {\n\n        /**\n         * \n         */\n        private static final long serialVersionUID = 1L;\n\n        private final DnsMessage request;\n        private final DnsQueryResult result;\n        private final DnsName authoritativeZone;\n\n        public NotAuthoritativeNorGlueRrFound(DnsMessage request, DnsQueryResult result, DnsName authoritativeZone) {\n            super(\"Did not receive an authoritative answer, nor did the result contain any glue records\");\n            this.request = request;\n            this.result = result;\n            this.authoritativeZone = authoritativeZone;\n        }\n\n        public DnsMessage getRequest() {\n            return request;\n        }\n\n        public DnsQueryResult getResult() {\n            return result;\n        }\n\n        public DnsName getAuthoritativeZone() {\n            return authoritativeZone;\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-iterative-resolver/src/main/java/org/minidns/iterative/IterativeDnsClient.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.iterative;\n\nimport static org.minidns.constants.DnsRootServer.getIpv4RootServerById;\nimport static org.minidns.constants.DnsRootServer.getIpv6RootServerById;\nimport static org.minidns.constants.DnsRootServer.getRandomIpv4RootServer;\nimport static org.minidns.constants.DnsRootServer.getRandomIpv6RootServer;\n\nimport org.minidns.AbstractDnsClient;\nimport org.minidns.DnsCache;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.iterative.IterativeClientException.LoopDetected;\nimport org.minidns.iterative.IterativeClientException.NotAuthoritativeNorGlueRrFound;\nimport org.minidns.record.A;\nimport org.minidns.record.AAAA;\nimport org.minidns.record.RRWithTarget;\nimport org.minidns.record.Record;\nimport org.minidns.record.Record.TYPE;\nimport org.minidns.record.Data;\nimport org.minidns.record.InternetAddressRR;\nimport org.minidns.record.NS;\nimport org.minidns.util.MultipleIoException;\n\nimport java.io.IOException;\nimport java.net.Inet4Address;\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Random;\nimport java.util.logging.Level;\n\npublic class IterativeDnsClient extends AbstractDnsClient {\n\n    int maxSteps = 128;\n\n    /**\n     * Create a new recursive DNS client using the global default cache.\n     */\n    public IterativeDnsClient() {\n        super();\n    }\n\n    /**\n     * Create a new recursive DNS client with the given DNS cache.\n     *\n     * @param cache The backend DNS cache.\n     */\n    public IterativeDnsClient(DnsCache cache) {\n        super(cache);\n    }\n\n    /**\n     * Recursively query the DNS system for one entry.\n     *\n     * @param queryBuilder The query DNS message builder.\n     * @return The response (or null on timeout/error).\n     * @throws IOException if an IO error occurs.\n     */\n    @Override\n    protected DnsQueryResult query(DnsMessage.Builder queryBuilder) throws IOException {\n        DnsMessage q = queryBuilder.build();\n        ResolutionState resolutionState = new ResolutionState(this);\n        DnsQueryResult result = queryRecursive(resolutionState, q);\n        return result;\n    }\n\n    private static InetAddress[] getTargets(Collection<? extends InternetAddressRR<? extends InetAddress>> primaryTargets,\n            Collection<? extends InternetAddressRR<? extends InetAddress>> secondaryTargets) {\n        InetAddress[] res = new InetAddress[2];\n\n        for (InternetAddressRR<? extends InetAddress> arr : primaryTargets) {\n            if (res[0] == null) {\n                res[0] = arr.getInetAddress();\n                // If secondaryTargets is empty, then try to get the second target out of the set of primaryTargets.\n                if (secondaryTargets.isEmpty()) {\n                    continue;\n                }\n            }\n            if (res[1] == null) {\n                res[1] = arr.getInetAddress();\n            }\n            break;\n        }\n\n        for (InternetAddressRR<? extends InetAddress> arr : secondaryTargets) {\n            if (res[0] == null) {\n                res[0] = arr.getInetAddress();\n                continue;\n            }\n            if (res[1] == null) {\n                res[1] = arr.getInetAddress();\n            }\n            break;\n        }\n\n        return res;\n    }\n\n    private DnsQueryResult queryRecursive(ResolutionState resolutionState, DnsMessage q) throws IOException {\n        InetAddress primaryTarget = null, secondaryTarget = null;\n\n        Question question = q.getQuestion();\n        DnsName parent = question.name.getParent();\n\n        switch (ipVersionSetting) {\n        case v4only:\n            for (A a : getCachedIPv4NameserverAddressesFor(parent)) {\n                if (primaryTarget == null) {\n                    primaryTarget = a.getInetAddress();\n                    continue;\n                }\n                secondaryTarget = a.getInetAddress();\n                break;\n            }\n            break;\n        case v6only:\n            for (AAAA aaaa : getCachedIPv6NameserverAddressesFor(parent)) {\n                if (primaryTarget == null) {\n                    primaryTarget = aaaa.getInetAddress();\n                    continue;\n                }\n                secondaryTarget = aaaa.getInetAddress();\n                break;\n            }\n            break;\n        case v4v6:\n            InetAddress[] v4v6targets = getTargets(getCachedIPv4NameserverAddressesFor(parent), getCachedIPv6NameserverAddressesFor(parent));\n            primaryTarget = v4v6targets[0];\n            secondaryTarget = v4v6targets[1];\n            break;\n        case v6v4:\n            InetAddress[] v6v4targets = getTargets(getCachedIPv6NameserverAddressesFor(parent), getCachedIPv4NameserverAddressesFor(parent));\n            primaryTarget = v6v4targets[0];\n            secondaryTarget = v6v4targets[1];\n            break;\n        default:\n            throw new AssertionError();\n        }\n\n        DnsName authoritativeZone = parent;\n        if (primaryTarget == null) {\n            authoritativeZone = DnsName.ROOT;\n            switch (ipVersionSetting) {\n            case v4only:\n                primaryTarget = getRandomIpv4RootServer(insecureRandom);\n                break;\n            case v6only:\n                primaryTarget = getRandomIpv6RootServer(insecureRandom);\n                break;\n            case v4v6:\n                primaryTarget = getRandomIpv4RootServer(insecureRandom);\n                secondaryTarget = getRandomIpv6RootServer(insecureRandom);\n                break;\n            case v6v4:\n                primaryTarget = getRandomIpv6RootServer(insecureRandom);\n                secondaryTarget = getRandomIpv4RootServer(insecureRandom);\n                break;\n            }\n        }\n\n        List<IOException> ioExceptions = new ArrayList<>();\n\n        try {\n            return queryRecursive(resolutionState, q, primaryTarget, authoritativeZone);\n        } catch (IOException ioException) {\n            abortIfFatal(ioException);\n            ioExceptions.add(ioException);\n        }\n\n        if (secondaryTarget != null) {\n            try {\n                return queryRecursive(resolutionState, q, secondaryTarget, authoritativeZone);\n            } catch (IOException ioException) {\n                ioExceptions.add(ioException);\n            }\n        }\n\n        MultipleIoException.throwIfRequired(ioExceptions);\n        return null;\n    }\n\n    private DnsQueryResult queryRecursive(ResolutionState resolutionState, DnsMessage q, InetAddress address, DnsName authoritativeZone) throws IOException {\n        resolutionState.recurse(address, q);\n\n        DnsQueryResult dnsQueryResult = query(q, address);\n\n        DnsMessage resMessage = dnsQueryResult.response;\n        if (resMessage.authoritativeAnswer) {\n            return dnsQueryResult;\n        }\n\n        if (cache != null) {\n            cache.offer(q, dnsQueryResult, authoritativeZone);\n        }\n\n        List<Record<? extends Data>> authorities = resMessage.copyAuthority();\n\n        List<IOException> ioExceptions = new ArrayList<>();\n\n        // Glued NS first\n        for (Iterator<Record<? extends Data>> iterator = authorities.iterator(); iterator.hasNext(); ) {\n            Record<NS> record = iterator.next().ifPossibleAs(NS.class);\n            if (record == null) {\n                iterator.remove();\n                continue;\n            }\n            DnsName name = record.payloadData.target;\n            IpResultSet gluedNs = searchAdditional(resMessage, name);\n            for (Iterator<InetAddress> addressIterator = gluedNs.addresses.iterator(); addressIterator.hasNext(); ) {\n                InetAddress target = addressIterator.next();\n                DnsQueryResult recursive = null;\n                try {\n                    recursive = queryRecursive(resolutionState, q, target, record.name);\n                } catch (IOException e) {\n                   abortIfFatal(e);\n                   LOGGER.log(Level.FINER, \"Exception while recursing\", e);\n                   resolutionState.decrementSteps();\n                   ioExceptions.add(e);\n                   if (!addressIterator.hasNext()) {\n                       iterator.remove();\n                   }\n                   continue;\n                }\n                return recursive;\n            }\n        }\n\n        // Try non-glued NS\n        for (Record<? extends Data> record : authorities) {\n            final Question question = q.getQuestion();\n            DnsName name = ((NS) record.payloadData).target;\n\n            // Loop prevention: If this non-glued NS equals the name we question for and if the question is about a A or\n            // AAAA RR, then we should not continue here as it would result in an endless loop.\n            if (question.name.equals(name) && (question.type == TYPE.A || question.type == TYPE.AAAA))\n                continue;\n\n            IpResultSet res = null;\n            try {\n                res = resolveIpRecursive(resolutionState, name);\n            } catch (IOException e) {\n                resolutionState.decrementSteps();\n                ioExceptions.add(e);\n            }\n            if (res == null) {\n                continue;\n            }\n\n            for (InetAddress target : res.addresses) {\n                DnsQueryResult recursive = null;\n                try {\n                    recursive = queryRecursive(resolutionState, q, target, record.name);\n                } catch (IOException e) {\n                    resolutionState.decrementSteps();\n                    ioExceptions.add(e);\n                    continue;\n                }\n                return recursive;\n            }\n        }\n\n        MultipleIoException.throwIfRequired(ioExceptions);\n\n        // Reaching this point means we did not receive an authoritative answer, nor\n        // where we able to find glue records or the IPs of the next nameservers.\n        throw new NotAuthoritativeNorGlueRrFound(q, dnsQueryResult, authoritativeZone);\n    }\n\n    private IpResultSet resolveIpRecursive(ResolutionState resolutionState, DnsName name) throws IOException {\n        IpResultSet.Builder res = newIpResultSetBuilder();\n\n        if (ipVersionSetting.v4) {\n            // TODO Try to retrieve A records for name out from cache.\n            Question question = new Question(name, TYPE.A);\n            final DnsMessage query = getQueryFor(question);\n            DnsQueryResult aDnsQueryResult = queryRecursive(resolutionState, query);\n            // TODO: queryRecurisve() should probably never return null. Verify that and then remove the follwing null check.\n            DnsMessage aMessage = aDnsQueryResult != null ? aDnsQueryResult.response : null;\n            if (aMessage != null) {\n                for (Record<? extends Data> answer : aMessage.answerSection) {\n                    if (answer.isAnswer(question)) {\n                        InetAddress inetAddress = inetAddressFromRecord(name.ace, (A) answer.payloadData);\n                        res.ipv4Addresses.add(inetAddress);\n                    } else if (answer.type == TYPE.CNAME && answer.name.equals(name)) {\n                        return resolveIpRecursive(resolutionState, ((RRWithTarget) answer.payloadData).target);\n                    }\n                }\n            }\n        }\n\n        if (ipVersionSetting.v6) {\n            // TODO Try to retrieve AAAA records for name out from cache.\n            Question question = new Question(name, TYPE.AAAA);\n            final DnsMessage query = getQueryFor(question);\n            DnsQueryResult aDnsQueryResult = queryRecursive(resolutionState, query);\n            // TODO: queryRecurisve() should probably never return null. Verify that and then remove the follwing null check.\n            DnsMessage aMessage = aDnsQueryResult != null ? aDnsQueryResult.response : null;\n            if (aMessage != null) {\n                for (Record<? extends Data> answer : aMessage.answerSection) {\n                    if (answer.isAnswer(question)) {\n                        InetAddress inetAddress = inetAddressFromRecord(name.ace, (AAAA) answer.payloadData);\n                        res.ipv6Addresses.add(inetAddress);\n                    } else if (answer.type == TYPE.CNAME && answer.name.equals(name)) {\n                        return resolveIpRecursive(resolutionState, ((RRWithTarget) answer.payloadData).target);\n                    }\n                }\n            }\n        }\n\n        return res.build();\n    }\n\n    @SuppressWarnings(\"incomplete-switch\")\n    private IpResultSet searchAdditional(DnsMessage message, DnsName name) {\n        IpResultSet.Builder res = newIpResultSetBuilder();\n        for (Record<? extends Data> record : message.additionalSection) {\n            if (!record.name.equals(name)) {\n                continue;\n            }\n            switch (record.type) {\n            case A:\n                res.ipv4Addresses.add(inetAddressFromRecord(name.ace, (A) record.payloadData));\n                break;\n            case AAAA:\n                res.ipv6Addresses.add(inetAddressFromRecord(name.ace, (AAAA) record.payloadData));\n                break;\n            default:\n                break;\n            }\n        }\n        return res.build();\n    }\n\n    private static InetAddress inetAddressFromRecord(String name, A recordPayload) {\n        try {\n            return InetAddress.getByAddress(name, recordPayload.getIp());\n        } catch (UnknownHostException e) {\n            // This will never happen\n            throw new RuntimeException(e);\n        }\n    }\n\n    private static InetAddress inetAddressFromRecord(String name, AAAA recordPayload) {\n        try {\n            return InetAddress.getByAddress(name, recordPayload.getIp());\n        } catch (UnknownHostException e) {\n            // This will never happen\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static List<InetAddress> getRootServer(char rootServerId) {\n        return getRootServer(rootServerId, DEFAULT_IP_VERSION_SETTING);\n    }\n\n    public static List<InetAddress> getRootServer(char rootServerId, IpVersionSetting setting) {\n        Inet4Address ipv4Root = getIpv4RootServerById(rootServerId);\n        Inet6Address ipv6Root = getIpv6RootServerById(rootServerId);\n        List<InetAddress> res = new ArrayList<>(2);\n        switch (setting) {\n        case v4only:\n            if (ipv4Root != null) {\n                res.add(ipv4Root);\n            }\n            break;\n        case v6only:\n            if (ipv6Root != null) {\n                res.add(ipv6Root);\n            }\n            break;\n        case v4v6:\n            if (ipv4Root != null) {\n                res.add(ipv4Root);\n            }\n            if (ipv6Root != null) {\n                res.add(ipv6Root);\n            }\n            break;\n        case v6v4:\n            if (ipv6Root != null) {\n                res.add(ipv6Root);\n            }\n            if (ipv4Root != null) {\n                res.add(ipv4Root);\n            }\n            break;\n        }\n        return res;\n    }\n\n    @Override\n    protected boolean isResponseCacheable(Question q, DnsQueryResult result) {\n        return result.response.authoritativeAnswer;\n    }\n\n    @Override\n    protected DnsMessage.Builder newQuestion(DnsMessage.Builder message) {\n        message.setRecursionDesired(false);\n        message.getEdnsBuilder().setUdpPayloadSize(dataSource.getUdpPayloadSize());\n        return message;\n    }\n\n    private IpResultSet.Builder newIpResultSetBuilder() {\n        return new IpResultSet.Builder(this.insecureRandom);\n    }\n\n    private static final class IpResultSet {\n\n        final List<InetAddress> addresses;\n\n        private IpResultSet(List<InetAddress> ipv4Addresses, List<InetAddress> ipv6Addresses, Random random) {\n            int size;\n            switch (DEFAULT_IP_VERSION_SETTING) {\n            case v4only:\n                size = ipv4Addresses.size();\n                break;\n            case v6only:\n                size = ipv6Addresses.size();\n                break;\n            case v4v6:\n            case v6v4:\n            default:\n                size = ipv4Addresses.size() + ipv6Addresses.size();\n                break;\n            }\n\n            if (size == 0) {\n                // Fast-path in case there were no addresses, which could happen e.g., if the NS records where not\n                // glued.\n                addresses = Collections.emptyList();\n            } else {\n                // Shuffle the addresses first, so that the load is better balanced.\n                if (DEFAULT_IP_VERSION_SETTING.v4) {\n                    Collections.shuffle(ipv4Addresses, random);\n                }\n                if (DEFAULT_IP_VERSION_SETTING.v6) {\n                    Collections.shuffle(ipv6Addresses, random);\n                }\n\n                List<InetAddress> addresses = new ArrayList<>(size);\n\n                // Now add the shuffled addresses to the result list.\n                switch (DEFAULT_IP_VERSION_SETTING) {\n                case v4only:\n                    addresses.addAll(ipv4Addresses);\n                    break;\n                case v6only:\n                    addresses.addAll(ipv6Addresses);\n                    break;\n                case v4v6:\n                    addresses.addAll(ipv4Addresses);\n                    addresses.addAll(ipv6Addresses);\n                    break;\n                case v6v4:\n                    addresses.addAll(ipv6Addresses);\n                    addresses.addAll(ipv4Addresses);\n                    break;\n                }\n\n                this.addresses = Collections.unmodifiableList(addresses);\n            }\n        }\n\n        private static final class Builder {\n            private final Random random;\n            private final List<InetAddress> ipv4Addresses = new ArrayList<>(8);\n            private final List<InetAddress> ipv6Addresses = new ArrayList<>(8);\n\n            private Builder(Random random) {\n                this.random = random;\n            }\n\n            public IpResultSet build() {\n                return new IpResultSet(ipv4Addresses, ipv6Addresses, random);\n            }\n        }\n    }\n\n    protected static void abortIfFatal(IOException ioException) throws IOException {\n        if (ioException instanceof LoopDetected) {\n            throw ioException;\n        }\n    }\n\n}\n"
  },
  {
    "path": "minidns-iterative-resolver/src/main/java/org/minidns/iterative/ReliableDnsClient.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.iterative;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.logging.Level;\n\nimport org.minidns.AbstractDnsClient;\nimport org.minidns.DnsCache;\nimport org.minidns.DnsClient;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.source.DnsDataSource;\nimport org.minidns.util.MultipleIoException;\n\n/**\n * A DNS client using a reliable strategy. First the configured resolver of the\n * system are used, then, in case there is no answer, a fall back to iterative\n * resolving is performed.\n */\npublic class ReliableDnsClient extends AbstractDnsClient {\n\n    public enum Mode {\n        /**\n         * Try the recursive servers first and fallback to iterative resolving if it fails. This is the default mode.\n         */\n        recursiveWithIterativeFallback,\n\n        /**\n         * Only try the recursive servers. This makes {@code ReliableDnsClient} behave like a {@link DnsClient}.\n         */\n        recursiveOnly,\n\n        /**\n         * Only use iterative resolving.  This makes {@code ReliableDnsClient} behave like a {@link IterativeDnsClient}.\n         */\n        iterativeOnly,\n    }\n\n    private final IterativeDnsClient recursiveDnsClient;\n    private final DnsClient dnsClient;\n\n    private Mode mode = Mode.recursiveWithIterativeFallback;\n\n    public ReliableDnsClient(DnsCache dnsCache) {\n        super(dnsCache);\n        recursiveDnsClient = new IterativeDnsClient(dnsCache) {\n            @Override\n            protected DnsMessage.Builder newQuestion(DnsMessage.Builder questionMessage) {\n                questionMessage = super.newQuestion(questionMessage);\n                return ReliableDnsClient.this.newQuestion(questionMessage);\n            }\n            // TODO: Rename dnsMessage to result.\n            @Override\n            protected boolean isResponseCacheable(Question q, DnsQueryResult dnsMessage) {\n                boolean res = super.isResponseCacheable(q, dnsMessage);\n                return ReliableDnsClient.this.isResponseCacheable(q, dnsMessage) && res;\n            }\n        };\n        dnsClient = new DnsClient(dnsCache) {\n            @Override\n            protected DnsMessage.Builder newQuestion(DnsMessage.Builder questionMessage) {\n                questionMessage = super.newQuestion(questionMessage);\n                return ReliableDnsClient.this.newQuestion(questionMessage);\n            }\n            // TODO: Rename dnsMessage to result.\n            @Override\n            protected boolean isResponseCacheable(Question q, DnsQueryResult dnsMessage) {\n                boolean res = super.isResponseCacheable(q, dnsMessage);\n                return ReliableDnsClient.this.isResponseCacheable(q, dnsMessage) && res;\n            }\n        };\n    }\n\n    public ReliableDnsClient() {\n        this(DEFAULT_CACHE);\n    }\n\n    @Override\n    protected DnsQueryResult query(DnsMessage.Builder q) throws IOException {\n        DnsQueryResult dnsMessage = null;\n        String unacceptableReason = null;\n        List<IOException> ioExceptions = new ArrayList<>();\n\n        if (mode != Mode.iterativeOnly) {\n            // Try a recursive query.\n            try {\n                dnsMessage = dnsClient.query(q);\n                if (dnsMessage != null) {\n                    unacceptableReason = isResponseAcceptable(dnsMessage.response);\n                    if (unacceptableReason == null) {\n                        return dnsMessage;\n                    }\n                }\n            } catch (IOException ioException) {\n                ioExceptions.add(ioException);\n            }\n        }\n\n        // Abort if we the are in \"recursive only\" mode.\n        if (mode == Mode.recursiveOnly) return dnsMessage;\n\n        // Eventually log that we fall back to iterative mode.\n        final Level FALLBACK_LOG_LEVEL = Level.FINE;\n        if (LOGGER.isLoggable(FALLBACK_LOG_LEVEL) && mode != Mode.iterativeOnly) {\n            String logString = \"Resolution fall back to iterative mode because: \";\n            if (!ioExceptions.isEmpty()) {\n                logString += ioExceptions.get(0);\n            } else if (dnsMessage == null) {\n                logString += \" DnsClient did not return a response\";\n            } else if (unacceptableReason != null) {\n                logString += unacceptableReason + \". Response:\\n\" + dnsMessage;\n            } else {\n                throw new AssertionError(\"This should never been reached\");\n            }\n            LOGGER.log(FALLBACK_LOG_LEVEL, logString);\n        }\n\n        try {\n            dnsMessage = recursiveDnsClient.query(q);\n            assert dnsMessage != null;\n        } catch (IOException ioException) {\n            ioExceptions.add(ioException);\n        }\n\n        if (dnsMessage == null) {\n            assert !ioExceptions.isEmpty();\n            MultipleIoException.throwIfRequired(ioExceptions);\n        }\n\n        return dnsMessage;\n    }\n\n    @Override\n    protected DnsMessage.Builder newQuestion(DnsMessage.Builder questionMessage) {\n        return questionMessage;\n    }\n\n    @Override\n    protected boolean isResponseCacheable(Question q, DnsQueryResult result) {\n        return isResponseAcceptable(result.response) == null;\n    }\n\n    /**\n     * Check if the response from the system's nameserver is acceptable. Must return <code>null</code> if the response\n     * is acceptable, or a String describing why it is not acceptable. If the response is not acceptable then\n     * {@link ReliableDnsClient} will fall back to resolve the query iteratively.\n     *\n     * @param response the response we got from the system's nameserver.\n     * @return <code>null</code> if the response is acceptable, or a String if not.\n     */\n    protected String isResponseAcceptable(DnsMessage response) {\n        return null;\n    }\n\n    @Override\n    public void setDataSource(DnsDataSource dataSource) {\n        super.setDataSource(dataSource);\n        recursiveDnsClient.setDataSource(dataSource);\n        dnsClient.setDataSource(dataSource);\n    }\n\n    /**\n     * Set the mode used when resolving queries.\n     *\n     * @param mode the mode to use.\n     */\n    public void setMode(Mode mode) {\n        if (mode == null) {\n            throw new IllegalArgumentException(\"Mode must not be null.\");\n        }\n        this.mode = mode;\n    }\n\n    public void setUseHardcodedDnsServers(boolean useHardcodedDnsServers) {\n        dnsClient.setUseHardcodedDnsServers(useHardcodedDnsServers);\n    }\n\n}\n"
  },
  {
    "path": "minidns-iterative-resolver/src/main/java/org/minidns/iterative/ResolutionState.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.iterative;\n\nimport java.net.InetAddress;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsmessage.Question;\nimport org.minidns.iterative.IterativeClientException.LoopDetected;\nimport org.minidns.iterative.IterativeClientException.MaxIterativeStepsReached;\n\npublic class ResolutionState {\n\n    private final IterativeDnsClient recursiveDnsClient;\n    private final HashMap<InetAddress, Set<Question>> map = new HashMap<>();\n    private int steps;\n\n    ResolutionState(IterativeDnsClient recursiveDnsClient) {\n        this.recursiveDnsClient = recursiveDnsClient;\n    }\n\n    void recurse(InetAddress address, DnsMessage query) throws LoopDetected, MaxIterativeStepsReached {\n        Question question = query.getQuestion();\n        if (!map.containsKey(address)) {\n            map.put(address, new HashSet<Question>());\n        } else if (map.get(address).contains(question)) {\n            throw new IterativeClientException.LoopDetected(address, question);\n        }\n\n        if (++steps > recursiveDnsClient.maxSteps) {\n            throw new IterativeClientException.MaxIterativeStepsReached(); \n        }\n\n        boolean isNew = map.get(address).add(question);\n        assert isNew;\n    }\n\n    void decrementSteps() {\n        steps--;\n    }\n\n}\n"
  },
  {
    "path": "minidns-iterative-resolver/src/test/java/org/minidns/iterative/IterativeDnsClientTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.iterative;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport org.minidns.cache.LruCache;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.record.A;\nimport org.minidns.record.Data;\nimport org.minidns.record.Record;\nimport org.minidns.record.Record.TYPE;\nimport org.junit.jupiter.api.Test;\n\nimport static org.minidns.DnsWorld.a;\nimport static org.minidns.DnsWorld.applyZones;\nimport static org.minidns.DnsWorld.ns;\nimport static org.minidns.DnsWorld.record;\nimport static org.minidns.DnsWorld.rootZone;\nimport static org.minidns.DnsWorld.zone;\nimport static org.junit.jupiter.api.Assertions.assertArrayEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\npublic class IterativeDnsClientTest {\n\n    @Test\n    public void basicIterativeTest() throws IOException {\n        IterativeDnsClient client = new IterativeDnsClient(new LruCache(0));\n        applyZones(client,\n                rootZone(\n                        record(\"com\", ns(\"ns.com\")),\n                        record(\"ns.com\", a(\"1.1.1.1\"))\n                ), zone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        record(\"example.com\", ns(\"ns.example.com\")),\n                        record(\"ns.example.com\", a(\"1.1.1.2\"))\n                ), zone(\"example.com\", \"ns.example.com\", \"1.1.1.2\",\n                        record(\"www.example.com\", a(\"1.1.1.3\"))\n                )\n        );\n        DnsQueryResult result = client.query(\"www.example.com\", TYPE.A);\n        DnsMessage message = result.response;\n        List<Record<? extends Data>> answers = message.answerSection;\n        assertEquals(1, answers.size());\n        assertEquals(TYPE.A, answers.get(0).type);\n        assertArrayEquals(new byte[] {1, 1, 1, 3}, ((A) answers.get(0).payloadData).getIp());\n    }\n\n    @Test\n    public void loopIterativeTest() throws IOException {\n        IterativeDnsClient client = new IterativeDnsClient(new LruCache(0));\n        applyZones(client,\n                rootZone(\n                        record(\"a\", ns(\"a.ns\")),\n                        record(\"b\", ns(\"b.ns\")),\n                        record(\"a.ns\", a(\"1.1.1.1\")),\n                        record(\"b.ns\", a(\"1.1.1.2\"))\n                ), zone(\"a\", \"a.ns\", \"1.1.1.1\",\n                        record(\"test.a\", ns(\"a.test.b\"))\n                ), zone(\"b\", \"b.ns\", \"1.1.1.2\",\n                        record(\"test.b\", ns(\"b.test.a\"))\n                )\n        );\n\n        assertThrows(IterativeClientException.LoopDetected.class, () ->\n            client.query(\"www.test.a\", TYPE.A)\n        );\n    }\n\n    @Test\n    public void notGluedNsTest() throws IOException {\n        IterativeDnsClient client = new IterativeDnsClient(new LruCache(0));\n        applyZones(client,\n                rootZone(\n                        record(\"com\", ns(\"ns.com\")),\n                        record(\"net\", ns(\"ns.net\")),\n                        record(\"ns.com\", a(\"1.1.1.1\")),\n                        record(\"ns.net\", a(\"1.1.2.1\"))\n                ), zone(\"com\", \"ns.com\", \"1.1.1.1\",\n                        record(\"example.com\", ns(\"example.ns.net\"))\n                ), zone(\"net\", \"ns.net\", \"1.1.2.1\",\n                        record(\"example.ns.net\", a(\"1.1.2.2\"))\n                ), zone(\"example.com\", \"example.ns.net\", \"1.1.2.2\",\n                        record(\"www.example.com\", a(\"1.1.1.3\"))\n                )\n        );\n        DnsQueryResult result = client.query(\"www.example.com\", TYPE.A);\n        DnsMessage message = result.response;\n        List<Record<? extends Data>> answers = message.answerSection;\n        assertEquals(1, answers.size());\n        assertEquals(TYPE.A, answers.get(0).type);\n        assertArrayEquals(new byte[] {1, 1, 1, 3}, ((A) answers.get(0).payloadData).getIp());\n    }\n}\n"
  },
  {
    "path": "minidns-repl/build.gradle",
    "content": "plugins {\n\tid 'org.minidns.java-conventions'\n\tid \"com.github.alisiikh.scalastyle\" version \"3.5.0\"\n}\n\ndescription = \"A read-eval-print loop (REPL) for MiniDNS\"\n\napply plugin: 'scala'\napply plugin: 'com.github.alisiikh.scalastyle'\n\next {\n    scalaVersion = '2.13.13'\n}\n\ndependencies {\n    api project(':minidns-client')\n    api project(':minidns-iterative-resolver')\n    api project(':minidns-dnssec')\n    api project(':minidns-integration-test')\n    api project(':minidns-hla')\n    implementation \"com.lihaoyi:ammonite_$scalaVersion:3.0.0-M1\"\n    testImplementation project(path: \":minidns-client\", configuration: \"testRuntime\")\n}\n\nscalastyle {\n\tconfig = new File(rootConfigDir, 'scalaStyle.xml')\n\tverbose = true\n\tfailOnWarning = true\n}\n\ncheck.dependsOn(scalastyleCheck)\n\ntask printClasspath(dependsOn: assemble) {\n\tdoLast {\n\t\tprintln sourceSets.main.runtimeClasspath.asPath\n\t}\n}\n"
  },
  {
    "path": "minidns-repl/scala.repl",
    "content": "org.minidns.minidnsrepl.MiniDnsRepl.init()\n\nimport org.minidns._\nimport org.minidns.dnsmessage._\nimport org.minidns.record.Record.TYPE\nimport org.minidns.record._\n\nimport org.minidns.dnssec.DnssecClient\n\nimport org.minidns.minidnsrepl.MiniDnsRepl.clearCache\nimport org.minidns.minidnsrepl.MiniDnsRepl.writeToFile\n\nimport org.minidns.minidnsrepl.MiniDnsStats._\n\nimport org.minidns.jul.MiniDnsJul._\n\nimport java.net.InetAddress\n\nimport java.util.logging._\n\ndef debugLog() = {\n  val miniDnsLogger = Logger.getLogger(\"org.minidns\")\n  miniDnsLogger.setLevel(Level.FINE)\n  val consoleHandler = new ConsoleHandler()\n  consoleHandler.setLevel(Level.FINE)\n  miniDnsLogger.addHandler(consoleHandler)\n}\n\n// Some standard values\nPredef.println(\"Set value 'c' to DNSClient\")\nval c = org.minidns.minidnsrepl.MiniDnsRepl.DNSCLIENT\nPredef.println(\"Set value 'ic' to IterativeDnsClient\")\nval ic = org.minidns.minidnsrepl.MiniDnsRepl.ITERATIVEDNSCLIENT\nPredef.println(\"Set value 'dc' to DnssecClient\")\nval dc = org.minidns.minidnsrepl.MiniDnsRepl.DNSSECCLIENT\n// A normal resolver\nPredef.println(\"Set value 'r' to ResolverApi\")\nval r = org.minidns.hla.ResolverApi.INSTANCE\n// A DNSSEC resolver\nPredef.println(\"Set value 'dr' to DnssecResolverApi\")\nval dr = org.minidns.hla.DnssecResolverApi.INSTANCE\n\nPredef.println(\"Enjoy MiniDNS. Go ahead and try a query. For example:\")\nPredef.println(\"c query (\\\"geekplace.eu\\\", TYPE.A)\")\nPredef.println(\"dr resolveDnssecReliable (\\\"verteiltesysteme.net\\\", classOf[A])\")\nPredef.println(\"NOTE: You can enable debug log output by calling 'debugLog'\")\n\n"
  },
  {
    "path": "minidns-repl/src/main/java/org/minidns/minidnsrepl/DnssecStats.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.minidnsrepl;\n\nimport java.io.IOException;\n\nimport org.minidns.cache.ExtendedLruCache;\nimport org.minidns.dnsname.DnsName;\nimport org.minidns.dnssec.DnssecClient;\nimport org.minidns.dnssec.DnssecQueryResult;\nimport org.minidns.dnssec.DnssecUnverifiedReason;\nimport org.minidns.integrationtest.IntegrationTestTools.CacheConfig;\nimport org.minidns.iterative.ReliableDnsClient.Mode;\nimport org.minidns.jul.MiniDnsJul;\nimport org.minidns.record.Record.TYPE;\n\npublic class DnssecStats {\n\n    private static final DnsName DOMAIN = DnsName.from(\"verteiltesysteme.net\");\n    private static final TYPE RR_TYPE = TYPE.A;\n\n    public static void iterativeDnssecLookupNormalVsExtendedCache() throws IOException {\n        // iterativeDnssecLookup(CacheConfig.normal);\n        iterativeDnssecLookup(CacheConfig.extended);\n    }\n\n    private static void iterativeDnssecLookup(CacheConfig cacheConfig) throws IOException {\n        DnssecClient client = MiniDnsStats.getClient(cacheConfig);\n        client.setMode(Mode.iterativeOnly);\n        DnssecQueryResult secRes = client.queryDnssec(DOMAIN, RR_TYPE);\n\n        StringBuilder stats = MiniDnsStats.getStats(client);\n        stats.append('\\n');\n        stats.append(secRes);\n        stats.append('\\n');\n        for (DnssecUnverifiedReason r : secRes.getUnverifiedReasons()) {\n            stats.append(r);\n        }\n        stats.append(\"\\n\\n\");\n        // CHECKSTYLE:OFF\n        System.out.println(stats);\n        // CHECKSTYLE:ON\n    }\n\n    public static void iterativeDnsssecTest() throws SecurityException, IllegalArgumentException, IOException {\n        MiniDnsJul.enableMiniDnsTrace();\n        DnssecClient client = new DnssecClient(new ExtendedLruCache());\n        client.setMode(Mode.iterativeOnly);\n\n        DnssecQueryResult secRes = client.queryDnssec(\"verteiltesysteme.net\", TYPE.A);\n\n        // CHECKSTYLE:OFF\n        System.out.println(secRes);\n        // CHECKSTYLE:ON\n    }\n\n}\n"
  },
  {
    "path": "minidns-repl/src/main/java/org/minidns/minidnsrepl/MiniDnsRepl.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.minidnsrepl;\n\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.lang.reflect.Field;\n\nimport org.minidns.AbstractDnsClient;\nimport org.minidns.DnsClient;\nimport org.minidns.cache.LruCache;\nimport org.minidns.dnsmessage.DnsMessage;\nimport org.minidns.dnssec.DnssecClient;\nimport org.minidns.hla.DnssecResolverApi;\nimport org.minidns.hla.ResolverResult;\nimport org.minidns.iterative.IterativeDnsClient;\nimport org.minidns.jul.MiniDnsJul;\nimport org.minidns.record.A;\n\npublic class MiniDnsRepl {\n\n    public static final DnsClient DNSCLIENT = new DnsClient();\n    public static final IterativeDnsClient ITERATIVEDNSCLIENT = new IterativeDnsClient();\n    public static final DnssecClient DNSSECCLIENT = new DnssecClient();\n\n    static {\n        LruCache cache = null;\n        try {\n            Field defaultCacheField = AbstractDnsClient.class.getDeclaredField(\"DEFAULT_CACHE\");\n            defaultCacheField.setAccessible(true);\n            cache = (LruCache) defaultCacheField.get(null);\n        } catch (IllegalAccessException | NoSuchFieldException | SecurityException e) {\n            throw new IllegalStateException(e);\n        }\n        DEFAULT_CACHE = cache;\n    }\n\n    public static final LruCache DEFAULT_CACHE;\n\n    public static void init() {\n        // CHECKSTYLE:OFF\n        System.out.println(\"MiniDNS REPL\");\n        // CHECKSTYLE:ON\n    }\n\n    public static void clearCache() throws SecurityException, IllegalArgumentException {\n        DEFAULT_CACHE.clear();\n    }\n\n    public static void main(String[] args) throws IOException, SecurityException, IllegalArgumentException {\n        MiniDnsJul.enableMiniDnsTrace();\n\n        ResolverResult<A> res = DnssecResolverApi.INSTANCE.resolveDnssecReliable(\"verteiltesysteme.net\", A.class);\n        /*\n        DnssecStats.iterativeDnssecLookupNormalVsExtendedCache();\n        DnssecClient client = new DNSSECClient(new LRUCache(1024));\n        DnssecMessage secRes = client.queryDnssec(\"verteiltesysteme.net\", TYPE.A);\n        */\n\n        /*\n        DnssecStats.iterativeDnssecLookupNormalVsExtendedCache();\n        Nsid nsid = NSIDTest.testNsidLRoot();\n        DnsMessage res = RECURSIVEDNSCLIENT.query(\"mate.geekplace.eu\", TYPE.A);\n        */\n        // CHECKSTYLE:OFF\n        System.out.println(res);\n//        System.out.println(nsid);\n//      System.out.println(secRes);\n//        System.out.println(res);\n        // CHCECKSTYLE:ON\n    }\n\n    public static void writeToFile(DnsMessage dnsMessage, String path) throws IOException {\n        try (FileOutputStream fos = new FileOutputStream(path)) {\n            dnsMessage.writeTo(fos, true);\n        }\n    }\n}\n"
  },
  {
    "path": "minidns-repl/src/main/java/org/minidns/minidnsrepl/MiniDnsStats.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.minidnsrepl;\n\nimport static java.lang.System.out;\n\nimport java.io.IOException;\nimport java.util.Arrays;\n\nimport org.minidns.AbstractDnsClient;\nimport org.minidns.DnsCache;\nimport org.minidns.dnsqueryresult.DnsQueryResult;\nimport org.minidns.dnssec.DnssecClient;\nimport org.minidns.integrationtest.IntegrationTestTools;\nimport org.minidns.integrationtest.IntegrationTestTools.CacheConfig;\nimport org.minidns.record.Record.TYPE;\nimport org.minidns.source.NetworkDataSourceWithAccounting;\n\npublic class MiniDnsStats {\n\n    public static void main(String[] args) throws IOException {\n        showDnssecStats();\n    }\n\n    public static void showDnssecStats() throws IOException {\n        showDnssecStats(\"siccegge.de\", TYPE.A);\n    }\n\n    public static void showDnssecStats(String name, TYPE type) throws IOException {\n        DnssecClient client;\n\n        client = getClient(CacheConfig.without);\n        // CHECKSTYLE:OFF\n        out.println(gatherStatsFor(client, \"Without Cache\", name, type));\n        // CHECKSTYLE:ON\n\n        client = getClient(CacheConfig.normal);\n        // CHECKSTYLE:OFF\n        out.println(gatherStatsFor(client, \"With Cache\", name, type));\n        // CHECKSTYLE:ON\n\n        client = getClient(CacheConfig.extended);\n        // CHECKSTYLE:OFF\n        out.println(gatherStatsFor(client, \"With Extended Cache\", name, type));\n        // CHECKSTYLE:ON\n\n        client = getClient(CacheConfig.full);\n        // CHECKSTYLE:OFF\n        out.println(gatherStatsFor(client, \"With Full Cache\", name, type));\n        // CHECKSTYLE:ON\n    }\n\n    public static StringBuilder gatherStatsFor(DnssecClient client, String testName, String name, TYPE type) throws IOException {\n        DnsQueryResult result;\n        long start, stop;\n\n        start = System.currentTimeMillis();\n        result = client.query(name, type);\n        stop = System.currentTimeMillis();\n\n        StringBuilder sb = new StringBuilder();\n        sb.append(testName).append('\\n');\n        char[] headline = new char[testName.length()];\n        Arrays.fill(headline, '#');\n        sb.append(headline).append('\\n');\n        sb.append(result).append('\\n');\n        sb.append(\"Took \").append(stop - start).append(\"ms\").append('\\n');\n        sb.append(getStats(client)).append('\\n');\n        sb.append('\\n');\n\n        return sb;\n    }\n\n    public static DnssecClient getClient(CacheConfig cacheConfig) {\n        return IntegrationTestTools.getClient(cacheConfig);\n    }\n\n    public static StringBuilder getStats(AbstractDnsClient client) {\n        StringBuilder sb = new StringBuilder();\n\n        NetworkDataSourceWithAccounting ndswa = NetworkDataSourceWithAccounting.from(client);\n        if (ndswa != null) {\n            sb.append(ndswa.getStats().toString());\n        } else {\n            sb.append(\"Client is not using \" + NetworkDataSourceWithAccounting.class.getSimpleName());\n        }\n\n        DnsCache dnsCache = client.getCache();\n        if (dnsCache != null) {\n            sb.append(dnsCache);\n        } else {\n            sb.append(\"Client is not using a Cache\");\n        }\n\n        return sb;\n    }\n}\n"
  },
  {
    "path": "minidns-repl/src/test/java/org/minidns/minidnsrepl/ReplTest.java",
    "content": "/*\n * Copyright 2015-2024 the original author or authors\n *\n * This software is licensed under the Apache License, Version 2.0,\n * the GNU Lesser General Public License version 2 or later (\"LGPL\")\n * and the WTFPL.\n * You may choose either license to govern your use of this software only\n * upon the condition that you accept all of the terms of either\n * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.\n */\npackage org.minidns.minidnsrepl;\n\nimport org.junit.jupiter.api.Test;\n\npublic class ReplTest {\n    /**\n     * Just here to ensure jacoco is not complaining.\n     */\n    @Test\n    public void emptyTest() {\n    }\n}\n"
  },
  {
    "path": "misc/resolve.pl",
    "content": "use Net::DNS;\n\nmy $r = Net::DNS::Resolver->new();\n\n# This script is provided as a reference. It's not really part of the\n# minidns source code. Rather, the files it downloaded when _I_ ran it\n# today form part of the source, and the junit tests verify that\n# minidns interprets those exact packets the same way that I do.\n\n# If you rerun the script, you may get other results than I did in\n# January 2015. Several of them likely depend on geolocation and load\n# balancing.\n\n# In short: If you update the packets you'll have to interpret them\n# correctly and update the interpretation tests appropriately.\n\n\n# do one DNS lookup and write the query packet to a file\n\nsub lookup() {\n    my ($domain, $type, $filename) = @_;\n\n    my $p = $r->query($domain, $type);\n\n    # print for visibility during test analysis\n    print $p->string;\n\n    open F, \">$filename\" or die \"$!\\n\";\n    print F $p->data;\n    close F;\n}\n\n\n# SRV and MX are near and dear to me\n\n&lookup(\"gmail.com\", \"mx\", \"gmail-mx\");\n&lookup(\"_xmpp-client._tcp.gulbrandsen.priv.no\", \"srv\", \"gpn-srv\");\n\n# A matters to everyone\n\n# sunracle uses a CNAME now, which affords an opportunity to test for\n# an attack\n&lookup(\"www.sun.com\", \"a\", \"sun-a\");\n# we'll modify the answer and change one AD RR, and then check that a\n# direct lookup is not affected (ie. our cache isn't poisoned)\n&lookup(\"legacy-sun.oraclegha.com\", \"a\", \"sun-real-a\");\n\n# AAAA doubles in importance every few months\n&lookup(\"google.com\", \"aaaa\", \"google-aaaa\");\n\n&lookup(\"oracle.com\", \"soa\", \"oracle-soa\");\n"
  },
  {
    "path": "misc/sbt/.gitignore",
    "content": "/project/\n/target/\n"
  },
  {
    "path": "misc/sbt/build.sbt",
    "content": "name := \"MiniDNS Playground for Scala\"\n\nversion := \"1.0\"\n\nresolvers += Resolver.sonatypeRepo(\"snapshots\")\nresolvers += Resolver.mavenLocal\n\nlibraryDependencies += \"org.minidns\" % \"minidns-client\" % \"latest.integration\"\nlibraryDependencies += \"org.minidns\" % \"minidns-dnssec\" % \"latest.integration\"\n\ninitialCommands in console += \"import org.minidns._;\"\ninitialCommands in console += \"import org.minidns.Record.TYPE;\"\ninitialCommands in console += \"val client = new DnsClient(new java.util.HashMap[Question,DnsMessage]())\"\n\n"
  },
  {
    "path": "repl",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nJDWP=false\nJDWP_PORT=8000\n\nwhile getopts djp: OPTION \"$@\"; do\n    case $OPTION in\n\td)\n\t    set -x\n\t    ;;\n\tj)\n\t\tJDWP=true\n\t\t;;\n\tp)\n\t\tJDWP_PORT=$OPTARG\n\t\t;;\n    esac\ndone\n\nEXTRA_JAVA_ARGS=()\nif $JDWP; then\n\tEXTRA_JAVA_ARGS+=(\"-Xdebug\")\n\tEXTRA_JAVA_ARGS+=(\"-Xrunjdwp:server=y,transport=dt_socket,address=${JDWP_PORT},suspend=n\")\nfi\n\nPROJECT_ROOT=$(dirname \"${BASH_SOURCE[0]}\")\ncd \"${PROJECT_ROOT}\"\n\necho \"Compiling and computing classpath (May take a while)\"\n# Sadly even with the --quiet option Gradle (or some component of)\n# will print the number of warnings/errors to stdout if there are\n# any. So the result could look like\n# 52 warnings\\n1 warning\\n12 warnings\\n\n# /smack/smack-repl/build/classes/main:/smack/smack-repl/build/\n# resources/main:/smack/smack-tcp/build/libs/smack-tcp-4.2.0-alpha4-SNAPSHOT.jar\n# So perform a \"tail -n1\" on the output of gradle\nGRADLE_CLASSPATH=\"$(${GRADLE_BIN:-./gradlew} :minidns-repl:printClasspath --quiet |\\\n\ttail -n1)\"\necho \"Finished, starting REPL\"\n\nexec java \\\n\t\"${EXTRA_JAVA_ARGS[@]}\" \\\n\t-Dscala.usejavacp=true \\\n\t-classpath \"${GRADLE_CLASSPATH}\" \\\n\tammonite.Main \\\n\t--predef minidns-repl/scala.repl\n"
  },
  {
    "path": "settings.gradle",
    "content": "pluginManagement {\n    includeBuild('build-logic')\n}\n\n// The name of the root project.\n// If we would not set the name, then gradle would use the directory\n// name of the root directory\nrootProject.name = 'MiniDNS'\n\ninclude 'minidns-core'\ninclude 'minidns-client'\ninclude 'minidns-async'\ninclude 'minidns-iterative-resolver'\ninclude 'minidns-dnssec'\ninclude 'minidns-dane'\ninclude 'minidns-integration-test'\ninclude 'minidns-repl'\ninclude 'minidns-hla'\ninclude 'minidns-android23'\n"
  },
  {
    "path": "version",
    "content": "1.1.2-SNAPSHOT\n"
  }
]